···11+# Description: Dockerfile for the Skywatch Tools
22+FROM node:lts
33+RUN curl -fsSL https://bun.sh/install | bash
44+ENV PATH="/root/.bun/bin:${PATH}"
55+66+# Create app directory
77+WORKDIR /app
88+COPY package*.json bun.lockb ./
99+1010+# Install app dependencies
1111+RUN bun i
1212+1313+# Bundle app source
1414+COPY . .
1515+1616+# Expose the port the app runs
1717+EXPOSE 4101
1818+1919+# Serve the app
2020+CMD ["bun", "run", "start"]
+17
LICENSE
···11+Permission is hereby granted, free of charge, to any person obtaining a copy
22+of this software and associated documentation files (the "Software"), to deal
33+in the Software without restriction, including without limitation the rights
44+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
55+copies of the Software, and to permit persons to whom the Software is
66+furnished to do so, subject to the following conditions:
77+88+The above copyright notice and this permission notice shall be included in all
99+copies or substantial portions of the Software.
1010+1111+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1212+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1313+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1414+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1515+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1616+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1717+SOFTWARE.
+42
README.md
···11+# skywatch-tools
22+33+This is a rewrite of the original skywatch-tools project in TypeScript. The original project was written in Bash. The purpose of this project is to automate the moderation by the Bluesky independent labeler skywatch.blue
44+55+## Installation and Setup
66+77+To install dependencies:
88+99+```bash
1010+bun i
1111+```
1212+1313+Modify .env.example with your own values and rename it to .env
1414+1515+```bash
1616+bun run start
1717+```
1818+1919+To run in docker:
2020+2121+```bash
2222+docker build -pull -t skywatch-automod .
2323+docker run -d -p 4101:4101 skywatch-automod
2424+```
2525+2626+## Brief overview
2727+2828+Currently this tooling does one thing. It monitors the bluesky firehose and analyzes content for phrases which fit Skywatch's criteria for moderation. If the criteria is met, it can automatically label the content with the appropriate label.
2929+3030+In certain cases, where regexp will create too many false positives, it will flag content as a report against related to the account, so that it can be reviewed later.
3131+3232+For information on how to set-up your own checks, please see the [developing_checks.md](./src/developing_checks.md) file.
3333+3434+_TODO_:
3535+3636+- [ ] Remove unused types
3737+- [ ] Update the types needed to be more specific to the checks rather than bluesky content types
3838+- [ ] Consider how to write directly to OzonePDS database rather than using the API. May require running the same instance as Ozone to allow for direct database access.
3939+- [ ] Add compose.yaml for easy deployment
4040+- [ ] Make the metrics server work (or remove it)
4141+4242+Create a seperate program to watch OZONE_PDS firehose labels, and update the lists as needed. This will remove dependency on broken ruby tools created by aegis.
···11+import { Checks } from "./types.js";
22+33+// rename this to constants.ts
44+55+export const PROFILE_CHECKS: Checks[] = [
66+ {
77+ label: "skub",
88+ comment: "Pro-skub language found in profile",
99+ description: true,
1010+ displayName: true,
1111+ reportOnly: false,
1212+ commentOnly: false,
1313+ check: new RegExp(
1414+ "(only|pro)[ -]skub|we love skub|skub is (good|god|king)|\\bskub\\b",
1515+ "i",
1616+ ),
1717+ whitelist: new RegExp("(anti|[🚫]|DNI)[ -:]?skub", "i"),
1818+ ignoredDIDs: [
1919+ "did:plc:example", //Parody account
2020+ ],
2121+ },
2222+ {
2323+ label: "skub-adjacent",
2424+ comment: "skub-adjacent language found in profile",
2525+ description: true,
2626+ displayName: true,
2727+ reportOnly: true,
2828+ commentOnly: false,
2929+ check: new RegExp(
3030+ "skubbe",
3131+ "i",
3232+ ),
3333+ },
3434+];
3535+3636+export const HANDLE_CHECKS: Checks[] = [
3737+ {
3838+ label: "skub",
3939+ comment: "Pro-skub language found in handle",
4040+ reportOnly: false,
4141+ commentOnly: false,
4242+ check: new RegExp(
4343+ "(only|pro)[-]skub|we love skub|skub[-]?is[-]?(good|god|king)|skub\\.(pro|com|org)",
4444+ "i",
4545+ ),
4646+ },
4747+];
4848+4949+export const POST_CHECKS: Checks[] = [
5050+ {
5151+ label: "pro-skub-link",
5252+ comment: "Pro Skub link found in post",
5353+ reportOnly: true,
5454+ commentOnly: false,
5555+ check: new RegExp(
5656+ "skubbe\\.com|skub\\.(me|pro|tech)",
5757+ "i",
5858+ ),
5959+ },
6060+];
+24
src/developing_checks.md
···11+# How to build checks for skywatch-automod
22+33+## Introduction
44+Constants.ts defines three types of types of checks: `HANDLE_CHECKS`, `POST_CHECKS`, and `PROFILE_CHECKS`.
55+66+For each check, users need to define a set of regular expressions that will be used to match against the content of the post, handle, or profile. A maximal example of a check is as follows:
77+88+```typescript
99+export const HANDLE_CHECKS: Checks[] = [
1010+ {
1111+ label: "example",
1212+ comment: "Example found in handle",
1313+ description: true, // Optional, only used in handle checks
1414+ displayName: true, // Optional, only used in handle checks
1515+ reportOnly: false, // it true, the check will only report the content against the account, not label.
1616+ commentOnly: false, // Poorly named, if true, will generate an account level comment from flagged posts, rather than a report. Intended for use when reportOnly is false, and on posts only where the flag may generate a high volume of reports..
1717+ check: new RegExp("example", "i"), // Regular expression to match against the content
1818+ whitelist: new RegExp("example.com", "i"), // Optional, regular expression to whitelist content
1919+ ignoredDIDs: ["did:plc:example"] // Optional, array of DIDs to ignore if they match the check. Useful for folks who reclaim words.
2020+ }
2121+];
2222+```
2323+2424+In the above example, any handle that contains the word "example" will be labeled with the label "example" unless the handle is `example.com` or the handle belongs to the user with the DID `did:plc:example`.
+11
src/limits.ts
···11+import { pRateLimit } from "p-ratelimit"; // TypeScript
22+33+// create a rate limiter that allows up to 30 API calls per second,
44+// with max concurrency of 10
55+66+export const limit = pRateLimit({
77+ interval: 30000, // 1000 ms == 1 second
88+ rate: 280, // 30 API calls per interval
99+ concurrency: 48, // no more than 10 running at once
1010+ maxDelay: 0, // an API call delayed > 30 sec is rejected
1111+});