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 // first we need to find out which files we can post, and which ones we already // have posted console.log('Picking file to post…') const alreadyPostedFiles = new Set() for await (const dirEntry of Deno.readDir("./posts/.posted")) { if (dirEntry.isSymlink) { alreadyPostedFiles.add(dirEntry.name) } } const availableFiles = [] for await (const dirEntry of Deno.readDir("./posts/")) { 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(`./posts/${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])) type MediaResponse = { 'id': string 'type': string 'url': string 'preview_url': string 'remote_url': null, 'text_url': string 'meta': { 'focus': { 'x': number 'y': number }, 'original': { 'width': number 'height': number 'size': string 'aspect': number }, 'small': { 'width': number 'height': number 'size': string 'aspect': number } }, 'description': string 'blurhash': string } console.log('Sending post request to', `${MASTODON_INSTANCE_URL}/api/v2/media`) const mediaRequest = await fetch(`${MASTODON_INSTANCE_URL}/api/v2/media`, { method: 'POST', headers: { Authorization: `Bearer ${MASTODON_ACCESS_TOKEN}` }, body: mediaData }).then(res => res.json()) as unknown as MediaResponse console.log('mediaRequest', mediaRequest) // 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 statusRequest = await fetch(`${MASTODON_INSTANCE_URL}/api/v1/statuses`, { method: 'POST', headers: { Authorization: `Bearer ${MASTODON_ACCESS_TOKEN}` }, body: statusData }).then(res => res.json()) console.log('statusRequest', statusRequest) console.log('Linking posted file so we can skip it in the future…') await Deno.symlink(`./posts/${fileToPost}`, `./posts/.posted/${fileToPost}`) console.log('Done!')