128 lines
3.9 KiB
TypeScript
128 lines
3.9 KiB
TypeScript
import * as path from 'https://deno.land/std@0.102.0/path/mod.ts';
|
|
|
|
const MASTODON_INSTANCE_URL = Deno.env.get("MASTODON_INSTANCE_URL"); // e.g. https://botsin.space
|
|
const MASTODON_ACCESS_TOKEN = Deno.env.get("MASTODON_ACCESS_TOKEN"); // you can get this at $INSTANCE_URL/settings/applications
|
|
|
|
if (
|
|
MASTODON_INSTANCE_URL == null || MASTODON_INSTANCE_URL === "" ||
|
|
MASTODON_ACCESS_TOKEN == null || MASTODON_ACCESS_TOKEN === ""
|
|
) {
|
|
console.error("Please set MASTODON_INSTANE_URL and MASTODON_ACCESS_TOKEN!");
|
|
Deno.exit(1);
|
|
}
|
|
|
|
// some helpers to work with the mastodon api
|
|
|
|
const handleResponse = async (res: Response) => {
|
|
if (res.ok) return { response: res, data: await res.json() };
|
|
throw new Error(await res.text());
|
|
};
|
|
|
|
const api = (
|
|
path: string,
|
|
params: { body?: FormData | string; method?: string },
|
|
) =>
|
|
fetch(
|
|
`${MASTODON_INSTANCE_URL.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`,
|
|
{
|
|
method: "POST",
|
|
headers: {
|
|
Authorization: `Bearer ${MASTODON_ACCESS_TOKEN}`,
|
|
},
|
|
...params,
|
|
},
|
|
).then(handleResponse);
|
|
|
|
// first we need to find out which files we can post, and which ones we already
|
|
// have posted
|
|
const scriptDir = path.dirname(path.fromFileUrl(Deno.mainModule))
|
|
const imgDir = path.resolve(scriptDir, Deno.args[0] || 'posts')
|
|
|
|
console.log("Picking file to post…");
|
|
const alreadyPostedFiles = new Set();
|
|
for await (const dirEntry of Deno.readDir(`${imgDir}/.posted`)) {
|
|
if (dirEntry.isSymlink) {
|
|
alreadyPostedFiles.add(dirEntry.name);
|
|
}
|
|
}
|
|
|
|
const availableFiles = [];
|
|
for await (const dirEntry of Deno.readDir(imgDir)) {
|
|
if (dirEntry.name === ".gitignore") continue;
|
|
|
|
if (dirEntry.isFile && !alreadyPostedFiles.has(dirEntry.name)) {
|
|
availableFiles.push(dirEntry.name);
|
|
}
|
|
}
|
|
|
|
console.log(`Found ${availableFiles.length} files which are not yet posted`);
|
|
|
|
if (availableFiles.length === 0) {
|
|
console.log("Nothing left to post at the moment. Bye!");
|
|
Deno.exit();
|
|
}
|
|
|
|
const fileToPost =
|
|
availableFiles[Math.floor(Math.random() * availableFiles.length)];
|
|
const bytes = await Deno.readFile(`${imgDir}/${fileToPost}`);
|
|
|
|
console.log(`Will post file ${fileToPost}`);
|
|
|
|
// we need two requests
|
|
// 1. upload media
|
|
// 2. create status with media id
|
|
|
|
// see https://docs.joinmastodon.org/methods/media/#v2
|
|
const mediaData = new FormData();
|
|
mediaData.append("file", new Blob([bytes]));
|
|
|
|
console.log("Sending post request to", `${MASTODON_INSTANCE_URL}/api/v2/media`);
|
|
const { data: mediaRequest } = await api("/api/v2/media", {
|
|
body: mediaData,
|
|
}) as unknown as { data: { id: string } };
|
|
console.log("mediaRequest", mediaRequest);
|
|
|
|
// now we have to wait until the media file is processed
|
|
const sleep = (milliseconds: number) =>
|
|
new Promise((resolve, reject) => {
|
|
setTimeout(resolve, milliseconds);
|
|
});
|
|
|
|
const startTime = Date.now();
|
|
while (true) {
|
|
if ((Date.now() - startTime) / 1000 >= 60) {
|
|
console.error("Timeout waiting for media to be processed.");
|
|
Deno.exit(1);
|
|
}
|
|
|
|
const { response } = await api(`/api/v1/media/${mediaRequest.id}`, {
|
|
method: "GET",
|
|
});
|
|
if (response.status === 200) {
|
|
console.log("Media finished processing!");
|
|
break;
|
|
} else {
|
|
console.log("Media still processing…");
|
|
await sleep(5000);
|
|
}
|
|
}
|
|
|
|
// see https://docs.joinmastodon.org/methods/statuses/#create
|
|
const statusData = new FormData();
|
|
statusData.append("media_ids[]", mediaRequest.id);
|
|
|
|
console.log(
|
|
"Sending post request to",
|
|
`${MASTODON_INSTANCE_URL}/api/v1/statuses`,
|
|
);
|
|
const { data: statusRequest } = await api("/api/v1/statuses", {
|
|
body: statusData,
|
|
});
|
|
console.log("statusRequest", statusRequest);
|
|
|
|
console.log("Linking posted file so we can skip it in the future…");
|
|
// FIXME: This requires unrestricted `--allow-read` and `--allow-write`;
|
|
// deno should provide a finer-grained permission prompt here
|
|
// see https://github.com/denoland/deno/issues/9607
|
|
await Deno.symlink(`../${fileToPost}`, `${imgDir}/.posted/${fileToPost}`);
|
|
console.log("Done!");
|