commit 451ece34908356102f878b01b6b0a914a6cc0034 Author: arne Date: Tue Jan 3 20:59:23 2023 +0100 Initial commit diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d8239c --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Created by https://www.toptal.com/developers/gitignore/api/deno +# Edit at https://www.toptal.com/developers/gitignore?templates=deno + +### Deno ### +/.idea/ +/.vscode/ + +/node_modules + +.env +*.orig +*.pyc +*.swp + +# End of https://www.toptal.com/developers/gitignore/api/deno diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..564385a --- /dev/null +++ b/flake.lock @@ -0,0 +1,26 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1672770591, + "narHash": "sha256-5GtjEo7vcvxngkXR+rC3u0kP3VNSeSpkf2vJz9J+1co=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "a736c4a4fada64012313256202f035133282c2e9", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..ba41308 --- /dev/null +++ b/flake.nix @@ -0,0 +1,17 @@ +{ + description = "kidpixbot"; + inputs.nixpkgs.url = github:NixOS/nixpkgs; + + outputs = { self, nixpkgs }: + let + system = "x86_64-linux"; + pkgs = import nixpkgs { inherit system; }; + lib = pkgs.lib; + in { + devShell.${system} = pkgs.mkShell rec { + buildInputs = with pkgs; [ + deno + ]; + }; + }; +} diff --git a/main.ts b/main.ts new file mode 100644 index 0000000..3657815 --- /dev/null +++ b/main.ts @@ -0,0 +1,97 @@ +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!') diff --git a/posts/.gitignore b/posts/.gitignore new file mode 100644 index 0000000..e01dbe7 --- /dev/null +++ b/posts/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!.posted diff --git a/posts/.posted/.gitignore b/posts/.posted/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/posts/.posted/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore