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";
|
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 formData = await request.formData();
|
||||||
|
|
||||||
const text = (formData.get("text") as string) || "";
|
const text = (formData.get("text") as string) || "";
|
||||||
const instanceDomain =
|
const instanceHost =
|
||||||
(formData.get("instance") as string) || "mastodon.social";
|
(formData.get("instance") as string) || "mastodon.social";
|
||||||
|
|
||||||
const publishUrl = new URL("/share", `https://${instanceDomain}/`);
|
try {
|
||||||
publishUrl.search = new URLSearchParams({
|
const response = await fetch(new URL(`/api/detect/${instanceHost}`, url));
|
||||||
text,
|
const { host, publishEndpoint, params } = await response.json();
|
||||||
}).toString();
|
const publishUrl = new URL(publishEndpoint, `https://${host}/`);
|
||||||
|
publishUrl.search = new URLSearchParams([[params.text, text]]).toString();
|
||||||
return redirect(publishUrl.toString(), 303);
|
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