A work-in-progress, horribly overpowered CLI for Ozone

Initial

+1
.gitignore
··· 1 + .env
+23
.vscode/settings.json
··· 1 + { 2 + "workbench.colorCustomizations": { 3 + "activityBar.activeBackground": "#08beda", 4 + "activityBar.background": "#08beda", 5 + "activityBar.foreground": "#15202b", 6 + "activityBar.inactiveForeground": "#15202b99", 7 + "activityBarBadge.background": "#d708bc", 8 + "activityBarBadge.foreground": "#e7e7e7", 9 + "commandCenter.border": "#e7e7e799", 10 + "sash.hoverBorder": "#08beda", 11 + "statusBar.background": "#0693a9", 12 + "statusBar.foreground": "#e7e7e7", 13 + "statusBarItem.hoverBackground": "#08beda", 14 + "statusBarItem.remoteBackground": "#0693a9", 15 + "statusBarItem.remoteForeground": "#e7e7e7", 16 + "titleBar.activeBackground": "#0693a9", 17 + "titleBar.activeForeground": "#e7e7e7", 18 + "titleBar.inactiveBackground": "#0693a999", 19 + "titleBar.inactiveForeground": "#e7e7e799" 20 + }, 21 + "peacock.color": "#0693a9", 22 + "deno.enable": true 23 + }
+3
README.md
··· 1 + ## Cursory attempt to build a script that rapidly actions appeals 2 + 3 + This is a work in progress. :pray:
+20
appeals.ts
··· 1 + import { AtpAgent } from "@atproto/api"; 2 + export const getAllAppeals = async (agent: AtpAgent) => { 3 + let appeals: any[] = []; // Y U NO LET ME IMPORT TYPEDEF DENO???? 4 + let cursor; 5 + do { 6 + const { data } = await agent.tools.ozone.moderation.queryStatuses({ 7 + limit: 100, 8 + includeMuted: false, 9 + appealed: true, 10 + sortField: "lastReportedAt", 11 + sortDirection: "desc", 12 + cursor, 13 + }); 14 + if (!data.subjectStatuses.length) break; 15 + cursor = data.cursor; 16 + appeals = appeals.concat(data.subjectStatuses); 17 + } while (cursor); 18 + 19 + return appeals; 20 + };
+11
deno.json
··· 1 + { 2 + "tasks": { 3 + "dev": "deno run --watch main.ts" 4 + }, 5 + "imports": { 6 + "@atproto/api": "npm:@atproto/api@^0.17.3", 7 + "@std/assert": "jsr:@std/assert@1", 8 + "@std/cli": "jsr:@std/cli@^1.0.23", 9 + "@std/dotenv": "jsr:@std/dotenv@^0.225.5" 10 + } 11 + }
+156
deno.lock
··· 1 + { 2 + "version": "5", 3 + "specifiers": { 4 + "jsr:@std/assert@1": "1.0.15", 5 + "jsr:@std/cli@^1.0.23": "1.0.23", 6 + "jsr:@std/dotenv@~0.225.5": "0.225.5", 7 + "jsr:@std/internal@^1.0.12": "1.0.12", 8 + "npm:@atproto/api@~0.17.3": "0.17.3" 9 + }, 10 + "jsr": { 11 + "@std/assert@1.0.15": { 12 + "integrity": "d64018e951dbdfab9777335ecdb000c0b4e3df036984083be219ce5941e4703b", 13 + "dependencies": [ 14 + "jsr:@std/internal" 15 + ] 16 + }, 17 + "@std/cli@1.0.23": { 18 + "integrity": "bf95b7a9425ba2af1ae5a6359daf58c508f2decf711a76ed2993cd352498ccca", 19 + "dependencies": [ 20 + "jsr:@std/internal" 21 + ] 22 + }, 23 + "@std/dotenv@0.225.5": { 24 + "integrity": "9ce6f9d0ec3311f74a32535aa1b8c62ed88b1ab91b7f0815797d77a6f60c922f" 25 + }, 26 + "@std/internal@1.0.12": { 27 + "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" 28 + } 29 + }, 30 + "npm": { 31 + "@atproto/api@0.17.3": { 32 + "integrity": "sha512-pdQXhUAapNPdmN00W0vX5ta/aMkHqfgBHATt20X02XwxQpY2AnrPm2Iog4FyjsZqoHooAtCNV/NWJ4xfddJzsg==", 33 + "dependencies": [ 34 + "@atproto/common-web", 35 + "@atproto/lexicon", 36 + "@atproto/syntax", 37 + "@atproto/xrpc", 38 + "await-lock", 39 + "multiformats", 40 + "tlds", 41 + "zod" 42 + ] 43 + }, 44 + "@atproto/common-web@0.4.3": { 45 + "integrity": "sha512-nRDINmSe4VycJzPo6fP/hEltBcULFxt9Kw7fQk6405FyAWZiTluYHlXOnU7GkQfeUK44OENG1qFTBcmCJ7e8pg==", 46 + "dependencies": [ 47 + "graphemer", 48 + "multiformats", 49 + "uint8arrays", 50 + "zod" 51 + ] 52 + }, 53 + "@atproto/lexicon@0.5.1": { 54 + "integrity": "sha512-y8AEtYmfgVl4fqFxqXAeGvhesiGkxiy3CWoJIfsFDDdTlZUC8DFnZrYhcqkIop3OlCkkljvpSJi1hbeC1tbi8A==", 55 + "dependencies": [ 56 + "@atproto/common-web", 57 + "@atproto/syntax", 58 + "iso-datestring-validator", 59 + "multiformats", 60 + "zod" 61 + ] 62 + }, 63 + "@atproto/syntax@0.4.1": { 64 + "integrity": "sha512-CJdImtLAiFO+0z3BWTtxwk6aY5w4t8orHTMVJgkf++QRJWTxPbIFko/0hrkADB7n2EruDxDSeAgfUGehpH6ngw==" 65 + }, 66 + "@atproto/xrpc@0.7.5": { 67 + "integrity": "sha512-MUYNn5d2hv8yVegRL0ccHvTHAVj5JSnW07bkbiaz96UH45lvYNRVwt44z+yYVnb0/mvBzyD3/ZQ55TRGt7fHkA==", 68 + "dependencies": [ 69 + "@atproto/lexicon", 70 + "zod" 71 + ] 72 + }, 73 + "await-lock@2.2.2": { 74 + "integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==" 75 + }, 76 + "graphemer@1.4.0": { 77 + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" 78 + }, 79 + "iso-datestring-validator@2.2.2": { 80 + "integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==" 81 + }, 82 + "multiformats@9.9.0": { 83 + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==" 84 + }, 85 + "tlds@1.260.0": { 86 + "integrity": "sha512-78+28EWBhCEE7qlyaHA9OR3IPvbCLiDh3Ckla593TksfFc9vfTsgvH7eS+dr3o9qr31gwGbogcI16yN91PoRjQ==", 87 + "bin": true 88 + }, 89 + "uint8arrays@3.0.0": { 90 + "integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==", 91 + "dependencies": [ 92 + "multiformats" 93 + ] 94 + }, 95 + "zod@3.25.76": { 96 + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==" 97 + } 98 + }, 99 + "remote": { 100 + "https://deno.land/x/crayon@3.3.3/mod.ts": "82ad225583a483c4837577971629cddaa22614093af8353da6426b9366de9780", 101 + "https://deno.land/x/crayon@3.3.3/src/conversions.ts": "9bfd3b1fbe412bcba092890ac558b6beaad4c3aa399cd99d45fadb324d28afd6", 102 + "https://deno.land/x/crayon@3.3.3/src/crayon.ts": "6b237baa08a31c903436e040afd2228a7fffaa5d11dddc58e3c402f79b3c1d04", 103 + "https://deno.land/x/crayon@3.3.3/src/styles.ts": "aa588b57b2c0482dc5c6f53109b4287831a9827c0aeef9a88129beae1172c1ee", 104 + "https://deno.land/x/crayon@3.3.3/src/util.ts": "af8884a917488de76ac0c2b92482093ade74514ece77a4c64e5eb5b0f6ed68e6", 105 + "https://deno.land/x/tui@2.1.11/mod.ts": "b32347385f2efc7dd90a9e5f6386fded9bde505e3f1c4b07b8509c77206d15a6", 106 + "https://deno.land/x/tui@2.1.11/src/canvas/box.ts": "3f6a41af65942b069795d895c2a73d039a83ba35c01b5236ea88d1de65ba3ebf", 107 + "https://deno.land/x/tui@2.1.11/src/canvas/canvas.ts": "31daa080882956f19f5bf8a6b52f7a946ebc34bdba103b007ce4e73c2b922409", 108 + "https://deno.land/x/tui@2.1.11/src/canvas/draw_object.ts": "2b8a48f10c343aee61d9dc7f00ea7917119ea41374a994e1db7b5b1ee188f0f8", 109 + "https://deno.land/x/tui@2.1.11/src/canvas/mod.ts": "9da429bfb1d4f53eed661c3e2a2ae4b610380526597aa5f35a6604b810a83ac7", 110 + "https://deno.land/x/tui@2.1.11/src/canvas/text.ts": "e6c0f5ecb985a037e55397810bc212bae206240acd3e2b15fb786ae2746abdea", 111 + "https://deno.land/x/tui@2.1.11/src/component.ts": "d7a35cd1a4bc8a6177b047595c86e1df3418b340701e343ee83a08ac7a8005de", 112 + "https://deno.land/x/tui@2.1.11/src/controls.ts": "e0e91f61a8a73fb044ce980fd3d070407c2f950635cbce73d97f060ef19a780e", 113 + "https://deno.land/x/tui@2.1.11/src/event_emitter.ts": "e4ee1de2037c89952c5c4c54dc336b7992024f0cb41c9e49229944e773386824", 114 + "https://deno.land/x/tui@2.1.11/src/input.ts": "4ddc4049718847b2f76c2d022b740694ed50f958c2c42bb3a2eacfe0b0d83b0d", 115 + "https://deno.land/x/tui@2.1.11/src/input_reader/decoders/keyboard.ts": "70d4f54605a650612601e654bc28a5d4260374a893e27b8a86fe7181cb626377", 116 + "https://deno.land/x/tui@2.1.11/src/input_reader/decoders/mouse.ts": "a119c6261a27fb9b5e1711ba3d5161c45eac5615ea0aa739e4e6bca759434e6e", 117 + "https://deno.land/x/tui@2.1.11/src/input_reader/mod.ts": "4213a920c69fd51badd1091fe7c61eff1bd1ead32659785c16f976ae377491cc", 118 + "https://deno.land/x/tui@2.1.11/src/input_reader/types.ts": "c940a6d656b2fbdc051c2e0508f8eeefa322dd16e60fc6a3bac8d1e9337f7d6c", 119 + "https://deno.land/x/tui@2.1.11/src/layout/errors.ts": "3df98a8f7747657fd252f602b26e9b5ee130dbfb236bcb4b2d2c912d7ae1eef9", 120 + "https://deno.land/x/tui@2.1.11/src/layout/grid_layout.ts": "7b2fca94d1a074c61d838c36800dca47f518f720c6539a33d0ad674081a29a76", 121 + "https://deno.land/x/tui@2.1.11/src/layout/horizontal_layout.ts": "e2ea27519c44c124cb228f19d4b14aa314e7db2cc92ef83fccfa146ced29d4e4", 122 + "https://deno.land/x/tui@2.1.11/src/layout/mod.ts": "65381a8dd7e8f6287742ebeffef866ae98932d16daf3ea3325f0180b37e327b8", 123 + "https://deno.land/x/tui@2.1.11/src/layout/types.ts": "ebc78ef4adbf802a49f5319474d81183a2b9edc2947443d9f40f23c4730e16f3", 124 + "https://deno.land/x/tui@2.1.11/src/layout/vertical_layout.ts": "41f1ec4b897a0f45478b6392f0c74d0c010a74693b99f36f532c85d5525dfca0", 125 + "https://deno.land/x/tui@2.1.11/src/signals/computed.ts": "824428cfda7c9e4a6aed8caee32a9aa62cd257fac9e7ca64d54c99a15ea56019", 126 + "https://deno.land/x/tui@2.1.11/src/signals/dependency_tracking.ts": "b89e933a912e8c32881e13ba1555201ec22cc0be69600862ff824f72f1bd6d55", 127 + "https://deno.land/x/tui@2.1.11/src/signals/effect.ts": "b478ca6469a151e93970679108e01a786b6e121023294d15b7293476161ec049", 128 + "https://deno.land/x/tui@2.1.11/src/signals/flusher.ts": "b47eeca9547a39487bef56ab82272f050883474995d5e92191296aec936180cb", 129 + "https://deno.land/x/tui@2.1.11/src/signals/lazy_computed.ts": "b4653efd855ce920657510b4cd086c58a8c72be6511465e3234f4e0b134bb9c6", 130 + "https://deno.land/x/tui@2.1.11/src/signals/lazy_effect.ts": "b8bdebf29d4b5ae204bc8c0300f03d9c60ff097aef28e0df6515665497b919f0", 131 + "https://deno.land/x/tui@2.1.11/src/signals/mod.ts": "e76b1303c74c257956d29f9169f3f12af410a0e8540c7a467e5405ba839cc431", 132 + "https://deno.land/x/tui@2.1.11/src/signals/reactivity.ts": "8520655a0b948d979b1b3450139695726c1dfeb46b46d49b8e4159fe4651368d", 133 + "https://deno.land/x/tui@2.1.11/src/signals/signal.ts": "5f351000a3316196ac3836fd9f3ef2a708aec85713df720a01c853d4859060be", 134 + "https://deno.land/x/tui@2.1.11/src/signals/types.ts": "e04d088a849a58d184c30420fec9ab3726985e3bcbe2cdc1b4bbc218fb299dbe", 135 + "https://deno.land/x/tui@2.1.11/src/theme.ts": "87f3da7e6a5bbbeb04c3c5384069225c244bcce0bac833a87828c5208af07cfc", 136 + "https://deno.land/x/tui@2.1.11/src/tui.ts": "9b9bb4da296e96aadbe846520446d76570cf6105dcbc3c59e26a4301b4c6d2f4", 137 + "https://deno.land/x/tui@2.1.11/src/types.ts": "7e39e8c59a1d28e379e54455dd7d7bb1a349191970d9af8d47a74b590e0de401", 138 + "https://deno.land/x/tui@2.1.11/src/utils/ansi_codes.ts": "be83f249ab09d33b64540adda5191c58580d386cc7627b8d10ea3363c9a6d23d", 139 + "https://deno.land/x/tui@2.1.11/src/utils/async.ts": "3f3bfe526c05467313237c434bc951a02344935dba61fe7b4e14d818d03ba1ca", 140 + "https://deno.land/x/tui@2.1.11/src/utils/component.ts": "a7f5396b480c7ed437c5e08ec56c3e5c85a55963ee8520c2e5cc7a185577f1a3", 141 + "https://deno.land/x/tui@2.1.11/src/utils/mod.ts": "4daef102e2406625f32faf08d238b702a26b3dc87232c35e97a215b9dbf9e781", 142 + "https://deno.land/x/tui@2.1.11/src/utils/numbers.ts": "9d4322b7a184e111184f37507ccdf0d4a29ee7eb8f2ce8f942f078bd165f8941", 143 + "https://deno.land/x/tui@2.1.11/src/utils/signals.ts": "a78893aa2e379468df65045a4d1f45e0111237b24e18c19bf5a92b6ee9074c4a", 144 + "https://deno.land/x/tui@2.1.11/src/utils/sorted_array.ts": "d7b55e9c6489102fbee46d632040449f960f2ed3ae0d6bc3326e6d6ec1450d97", 145 + "https://deno.land/x/tui@2.1.11/src/utils/strings.ts": "0f7d24072e3d2ed4993f0f2692f148b0af4d9b7a665193dec835101af4dabf7b", 146 + "https://deno.land/x/tui@2.1.11/src/view.ts": "77c9dfdb3632f95c2456036c5e30f90cfbbad62cbb4f370cba48b992523aa619" 147 + }, 148 + "workspace": { 149 + "dependencies": [ 150 + "jsr:@std/assert@1", 151 + "jsr:@std/cli@^1.0.23", 152 + "jsr:@std/dotenv@~0.225.5", 153 + "npm:@atproto/api@~0.17.3" 154 + ] 155 + } 156 + }
+39
login.ts
··· 1 + import { AtpAgent } from "@atproto/api"; 2 + import { parseArgs } from "@std/cli"; 3 + 4 + export const loginToService = async () => { 5 + const { 6 + username = Deno.env.get("ATP_USERNAME"), 7 + password = Deno.env.get("ATP_PASSWORD"), 8 + service = Deno.env.get("SERVICE_URI") ?? "https://bsky.social", 9 + } = parseArgs(Deno.args); 10 + 11 + const agent = new AtpAgent({ service }); 12 + agent.configureProxy("did:plc:newitj5jo3uel7o4mnf3vj2o#atproto_labeler"); 13 + 14 + try { 15 + await agent.login({ identifier: username, password }); 16 + } catch (e) { 17 + console.dir(e); 18 + if (e instanceof Error && e.name === "AuthFactorTokenRequiredError") { 19 + let authFactorToken; 20 + 21 + do { 22 + authFactorToken = prompt( 23 + `Check your email for a login token and enter it below:` 24 + ); 25 + } while (!authFactorToken); 26 + 27 + try { 28 + await agent.login({ 29 + identifier: username, 30 + password, 31 + authFactorToken, 32 + }); 33 + } catch (e) { 34 + console.error(e); 35 + } 36 + } 37 + } 38 + return agent; 39 + };
+65
main.ts
··· 1 + import { loginToService } from "./login.ts"; 2 + import "@std/dotenv/load"; 3 + import { parseArgs } from "@std/cli"; 4 + import { getAllAppeals } from "./appeals.ts"; 5 + 6 + if (import.meta.main) { 7 + const { 8 + _: [command], 9 + } = parseArgs(Deno.args); 10 + switch (command) { 11 + case "autoappeal": { 12 + const agent = await loginToService(); 13 + const appeals = await getAllAppeals(agent); 14 + const { 15 + data: { records }, 16 + } = await agent.tools.ozone.moderation.getRecords({ 17 + uris: appeals.map((d) => d.subject.uri), 18 + }); 19 + 20 + for (const record of records) { 21 + const evt = { 22 + subject: { 23 + $type: "com.atproto.repo.strongRef", 24 + uri: record.uri, 25 + cid: record.cid, 26 + }, 27 + createdBy: agent.did!, 28 + subjectBlobCids: record.blobCids, 29 + modTool: { name: "ozone-cli/autoappeal" }, 30 + }; 31 + 32 + // Remove ALL labels from each post 33 + // @TODO add some sort of NLP to determine what action to take (i.e., replace label X with label Y) 34 + await agent.tools.ozone.moderation.emitEvent({ 35 + ...evt, 36 + event: { 37 + $type: "tools.ozone.moderation.defs#modEventLabel", 38 + createLabelVals: [], 39 + negateLabelVals: record.labels, 40 + comment: `Labels removed by ozone-cli at ${new Date().toISOString()}`, 41 + }, 42 + }); 43 + 44 + // Acknowledge appeal 45 + await agent.tools.ozone.moderation.emitEvent({ 46 + ...evt, 47 + event: { 48 + $type: "tools.ozone.moderation.defs#modEventAcknowledge", 49 + comment: "", 50 + acknowledgeAccountSubjects: false, 51 + }, 52 + }); 53 + 54 + // Resolve appeal 55 + await agent.tools.ozone.moderation.emitEvent({ 56 + ...evt, 57 + event: { 58 + $type: "tools.ozone.moderation.defs#modEventResolveAppeal", 59 + comment: "Resolving", 60 + }, 61 + }); 62 + } 63 + } 64 + } 65 + }
+6
main_test.ts
··· 1 + import { assertEquals } from "@std/assert"; 2 + import { add } from "./main.ts"; 3 + 4 + Deno.test(function addTest() { 5 + assertEquals(add(2, 3), 5); 6 + });