Add host detection
This re-implements the posting that was achieved in v3.
This commit is contained in:
parent
2301f2dc32
commit
378351a5d8
3 changed files with 94 additions and 110 deletions
102
api/share.js
102
api/share.js
|
@ -1,102 +0,0 @@
|
|||
/*!
|
||||
share2fedi - Instance-agnostic share page for the Fediverse.
|
||||
Copyright (C) 2020-2023 Nikita Karamov <me@kytta.dev>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import http from "http";
|
||||
import https from "https";
|
||||
|
||||
const pathsMap = {
|
||||
mastodon: {
|
||||
checkUrl: "/api/v1/instance/rules",
|
||||
postUrl: "share",
|
||||
textParam: "text",
|
||||
},
|
||||
gnuSocial: {
|
||||
checkUrl: "/api/statusnet/version.xml",
|
||||
postUrl: "/notice/new",
|
||||
textParam: "status_textarea",
|
||||
},
|
||||
pleroma: {
|
||||
checkUrl: "/api/v1/pleroma/federation_status",
|
||||
postUrl: "share",
|
||||
textParam: "message",
|
||||
},
|
||||
};
|
||||
|
||||
const queryUrl = (url, service) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const get = url.protocol === "https:" ? https.get : http.get;
|
||||
get(url, ({ statusCode }) => {
|
||||
if (statusCode === 200) {
|
||||
console.debug(url.href, "is", service);
|
||||
resolve(service);
|
||||
} else {
|
||||
reject(url);
|
||||
}
|
||||
}).on("error", (error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const detectService = async (instanceURL) => {
|
||||
const checkPromises = Object.entries(pathsMap).map(
|
||||
([service, { checkUrl }]) =>
|
||||
queryUrl(new URL(checkUrl, instanceURL), service),
|
||||
);
|
||||
|
||||
return await Promise.any(checkPromises);
|
||||
};
|
||||
|
||||
const requestListener = async (request, response) => {
|
||||
if (request.method !== "POST") {
|
||||
response.writeHead(405).end();
|
||||
return;
|
||||
}
|
||||
|
||||
let data = "";
|
||||
request.on("data", (chunk) => {
|
||||
data += chunk.toString();
|
||||
});
|
||||
|
||||
request.on("end", () => {
|
||||
const requestBody = new URLSearchParams(data);
|
||||
const postText = requestBody.get("text") || "";
|
||||
const instanceURL =
|
||||
requestBody.get("instance") || "https://mastodon.social/";
|
||||
|
||||
detectService(instanceURL)
|
||||
.then((service) => {
|
||||
const publishUrl = new URL(pathsMap[service].postUrl, instanceURL);
|
||||
publishUrl.search = new URLSearchParams([
|
||||
[pathsMap[service].textParam, postText],
|
||||
]);
|
||||
response.writeHead(303, { Location: publishUrl.toString() }).end();
|
||||
})
|
||||
.catch((error) => {
|
||||
response.writeHead(400).end(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
if (!import.meta.env || import.meta.env.PROD) {
|
||||
http.createServer(requestListener).listen(8080);
|
||||
}
|
||||
|
||||
export const viteNodeApp = requestListener;
|
78
src/pages/api/detect/[host].ts
Normal file
78
src/pages/api/detect/[host].ts
Normal file
|
@ -0,0 +1,78 @@
|
|||
import { APIRoute } from "astro";
|
||||
import { normalizeURL } from "../../../util";
|
||||
|
||||
const PROJECTS = {
|
||||
mastodon: {
|
||||
checkUrl: "/api/v1/instance/rules",
|
||||
publishEndpoint: "share",
|
||||
params: {
|
||||
text: "text",
|
||||
},
|
||||
},
|
||||
gnuSocial: {
|
||||
checkUrl: "/api/statusnet/version.xml",
|
||||
publishEndpoint: "/notice/new",
|
||||
params: {
|
||||
text: "status_textarea",
|
||||
},
|
||||
},
|
||||
pleroma: {
|
||||
checkUrl: "/api/v1/pleroma/federation_status",
|
||||
publishEndpoint: "share",
|
||||
params: {
|
||||
text: "message",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const checkProjectUrl = (
|
||||
urlToCheck: URL,
|
||||
projectId: string,
|
||||
): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(urlToCheck)
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
resolve(projectId);
|
||||
} else {
|
||||
reject(urlToCheck);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error.toString());
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const get: APIRoute = async ({ params }) => {
|
||||
const host = params.host;
|
||||
|
||||
const promises = Object.entries(PROJECTS).map(([service, { checkUrl }]) =>
|
||||
checkProjectUrl(new URL(checkUrl, normalizeURL(host)), service),
|
||||
);
|
||||
try {
|
||||
const project = await Promise.any(promises);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
host,
|
||||
project,
|
||||
publishEndpoint: PROJECTS[project].publishEndpoint,
|
||||
params: PROJECTS[project].params,
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
"Cache-Control": "s-maxage=86400, max-age=86400, public",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
);
|
||||
} catch {
|
||||
return new Response(JSON.stringify({ error: "Couldn't detect instance" }), {
|
||||
status: 400,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
|
@ -1,16 +1,24 @@
|
|||
import { APIRoute } from "astro";
|
||||
|
||||
export const post: APIRoute = async ({ redirect, request }) => {
|
||||
export const post: APIRoute = async ({ redirect, request, url }) => {
|
||||
const formData = await request.formData();
|
||||
|
||||
const text = (formData.get("text") as string) || "";
|
||||
const instanceDomain =
|
||||
const instanceHost =
|
||||
(formData.get("instance") as string) || "mastodon.social";
|
||||
|
||||
const publishUrl = new URL("/share", `https://${instanceDomain}/`);
|
||||
publishUrl.search = new URLSearchParams({
|
||||
text,
|
||||
}).toString();
|
||||
|
||||
return redirect(publishUrl.toString(), 303);
|
||||
try {
|
||||
const response = await fetch(new URL(`/api/detect/${instanceHost}`, url));
|
||||
const { host, publishEndpoint, params } = await response.json();
|
||||
const publishUrl = new URL(publishEndpoint, `https://${host}/`);
|
||||
publishUrl.search = new URLSearchParams([[params.text, text]]).toString();
|
||||
return redirect(publishUrl.toString(), 303);
|
||||
} catch {
|
||||
return new Response(JSON.stringify({ error: "Couldn't detect instance" }), {
|
||||
status: 400,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
Reference in a new issue