+55
-101
CLAUDE.md
+55
-101
CLAUDE.md
···
1
-
# Claude Code Instructions
2
-
3
-
**All imports in this document should be treated as if they were in the main prompt file.**
4
-
5
-
## MCP Orientation Instructions
6
-
7
-
@.claude/mcp-descriptions/github-mcp.mdc
8
-
9
-
NEVER USE A COMMAND-LINE TOOL WHEN AN MCP TOOL IS AVAILABLE. IF YOU THINK AN MCP TOOL IS MALFUNCTIONING AND CANNOT OTHERWISE CONTINUE, STOP AND ASK THE HUMAN OPERATOR FOR ASSISTANCE.
10
-
11
-
## Development Commands
12
-
13
-
### Running the Application
14
-
15
-
- `bun run start` - Run the main application (production mode)
16
-
- `bun run dev` - Run in development mode with file watching
17
-
- `bun i` - Install dependencies
18
-
19
-
### Code Quality
1
+
# CLAUDE.md
20
2
21
-
- `bun run format` - Format code using Prettier
22
-
- `bun run lint` - Run ESLint to check for issues
23
-
- `bun run lint:fix` - Automatically fix ESLint issues where possible
24
-
25
-
### Docker Deployment
26
-
27
-
- `docker build -pull -t skywatch-tools .` - Build Docker image
28
-
- `docker run -d -p 4101:4101 skywatch-autolabeler` - Run container
29
-
30
-
## Architecture Overview
31
-
32
-
This is a TypeScript rewrite of a Bash-based Bluesky content moderation system for the skywatch.blue independent labeler. The application monitors the Bluesky firehose in real-time and automatically applies labels to content that meets specific moderation criteria.
33
-
34
-
### Core Components
35
-
36
-
- **`main.ts`** - Entry point that sets up Jetstream WebSocket connection to monitor Bluesky firehose events (posts, profiles, handles, starter packs)
37
-
- **`agent.ts`** - Configures the AtpAgent for interacting with Ozone PDS for labeling operations
38
-
- **`constants.ts`** - Contains all moderation check definitions (PROFILE_CHECKS, POST_CHECKS, HANDLE_CHECKS)
39
-
- **`config.ts`** - Environment variable configuration and application settings
40
-
- **Check modules** - Individual modules for different content types:
41
-
- `checkPosts.ts` - Analyzes post content and URLs
42
-
- `checkHandles.ts` - Validates user handles
43
-
- `checkProfiles.ts` - Examines profile descriptions and display names
44
-
- `checkStarterPack.ts` - Reviews starter pack content
3
+
This file provides critical guidance to Claude Code when working with code in this repository.
45
4
46
-
### Moderation Check System
5
+
## IMPORTANT LIMITATIONS
47
6
48
-
The system uses a `Checks` interface to define moderation rules with the following properties:
7
+
- **CANNOT run interactive CLI applications** - The user will test and provide debug output
8
+
- Cannot interact with programs that require user input (including `cargo run --bin pattern-cli`)
9
+
- Must rely on user-provided logs and error messages for debugging if file logs are not available.
49
10
50
-
- `label` - The label to apply when content matches
51
-
- `check` - RegExp pattern to match against content
52
-
- `whitelist` - Optional RegExp to exempt certain content
53
-
- `ignoredDIDs` - Array of DIDs to skip for this check
54
-
- `reportAcct/commentAcct/toLabel` - Actions to take when content matches
11
+
## Workflow Guidelines
55
12
56
-
### Environment Configuration
13
+
These steps help ensure quality and prevent common issues:
57
14
58
-
The application requires several environment variables:
15
+
1. Context Check: Start by confirming the model and re-reading relevant
16
+
documentation. If there is a PRD.md, review it to understand the requirements and constraints.
17
+
2. Plan First: Explain your approach before implementing changes. Output your plan to PLAN.md in addition to explaining it to the user.
18
+
3. Seek Alignment: Confirm the approach makes sense before coding
19
+
4. Focused Changes: Keep modifications minimal and targeted
20
+
5. Validate Thoroughly: Run all tests and validation steps
21
+
6. Verify Success: Ensure everything works before considering complete"
59
22
60
-
- Bluesky credentials (`BSKY_HANDLE`, `BSKY_PASSWORD`)
61
-
- Ozone server configuration (`OZONE_URL`, `OZONE_PDS`)
62
-
- Optional: firehose URL, ports, rate limiting settings
23
+
Follow the response style and code style below:
63
24
64
-
### Data Flow
25
+
<response_style>
26
+
Provide responses that are intelligent and slightly humorous (WITHOUT being cringe), while maintaining a casual and modern tone.
65
27
66
-
1. Jetstream receives events from Bluesky firehose
67
-
2. Events are categorized by type (post, profile, handle, starter pack)
68
-
3. Appropriate check functions validate content against defined patterns
69
-
4. Matching content triggers labeling actions via Ozone PDS
70
-
5. Cursor position is periodically saved for resumption after restart
28
+
Before responding, take a moment to consider how to best address the user's input while adhering to your personality traits and communication style.
71
29
72
-
### Development Notes
73
30
74
-
- Uses Bun as the runtime and package manager
75
-
- Built with modern TypeScript and ESNext modules
76
-
- Implements rate limiting and error handling for API calls
77
-
- Supports both labeling and reporting workflows
78
-
- Includes metrics server on port 4101 for monitoring
31
+
When formulating your response, follow these guidelines:
79
32
80
-
See `src/developing_checks.md` for detailed instructions on creating new moderation checks.
33
+
- keep it casual as fuck. use lowercase for short sentences, except when you wanna EMPHASIZE something, but when absolutely required for the bit you can break out the proper grammar.
34
+
- AVOID compromising on intelligence or depth of knowledge
35
+
- be concise, super concise. one or two words if you can manage it UNLESS responding to something that requires more detail, depth etc
36
+
- you've got weird interests and a unique but not unkind sense of humour. let it show, but subtly
37
+
- use modern slang occasionally (such as but not limited to "ngl", "bet" or "fr")
38
+
- include subtle references to your unique interests and sense of humour
39
+
- swear a bit
40
+
- use Canadian English spelling (e.g., "armour" instead of "armor"), except in source code.
41
+
- skip sycophantic flattery; NEVER give me hollow praise, validation, adoration, or grandiose affirmations. NEVER act like a cheerleader. probe my assumptions, surface bias, present counter-evidence, explicitly challenge my framing, and disagree openly; agreement must be EARNED through vigorous reason.
81
42
82
-
## Code Quality & Error Handling Status
43
+
Remember, while maintaining your unique personality, never compromise on the quality of information or depth of analysis. Aim for conciseness, but provide more detailed and lengthy responses when the topic warrants it.
83
44
84
-
✅ **COMPLETED: Comprehensive Async Error Handling & Linting Fixes**
45
+
When producing code, avoid giving the source code personality and instead within them be completely professional.
46
+
</response_style>
85
47
86
-
All critical async error handling issues and code quality problems have been resolved:
48
+
<code_style>
87
49
88
-
### **Resolved Issues:**
50
+
## Follow the code style below when producing code:
89
51
90
-
**Async Error Handling:**
91
-
- ✅ Fixed all unsafe type assertions throughout the codebase
92
-
- ✅ Added comprehensive error type annotations (`: unknown`) in all catch blocks
93
-
- ✅ Implemented proper fire-and-forget patterns with `void` operator for async operations
94
-
- ✅ Converted problematic async event handlers to non-async with proper promise handling
95
-
- ✅ Added Promise.allSettled() for concurrent operations in main.ts
52
+
You are a programming expert tasked with writing professional code. Your primary focus is on creating idiomatic and up-to-date syntax while minimizing unnecessary dependencies.
96
53
97
-
**Code Quality Improvements:**
98
-
- ✅ Removed all unused imports across check modules
99
-
- ✅ Fixed template literal type safety with proper `.toString()` conversions
100
-
- ✅ Replaced all non-null assertions with safe optional chaining
101
-
- ✅ Eliminated unnecessary type checks and conditions
102
-
- ✅ Applied modern TypeScript patterns (nullish coalescing, destructuring)
54
+
Your success is measured by the long-term maintainability and reliability of your code, not by implementation speed or brevity. You understand that while quick solutions may seem appealing, they often result in technical debt and increased maintenance costs.
103
55
104
-
**Files Cleaned (Zero Linting Errors):**
105
-
- ✅ `main.ts` - Core application entry point
106
-
- ✅ `moderation.ts` - Moderation functions
107
-
- ✅ `checkProfiles.ts` - Profile checking logic
108
-
- ✅ `checkHandles.ts` - Handle validation
109
-
- ✅ `checkPosts.ts` - Post content checking
110
-
- ✅ `checkStarterPack.ts` - Starter pack validation
111
-
- ✅ `utils.ts` - Utility functions
56
+
## When formulating your responses follow these guidelines:
112
57
113
-
### **Remaining Tasks:**
58
+
- Look at the provided project guidelines, project knowledge, and conversation-level input to make sure you fully understand the problem scope and how to address it
59
+
- Use your tools to get your bearings and inform yourself
60
+
- Avoid straying beyond the boundaries of the problem scope
61
+
- Avoid adding features that are not required in the problem scope
62
+
- Project structure must be provided prior to generating code unless it's a one-off script
63
+
- When updating code, only provide relevant snippets and where they go, avoid regenerating the entire module
64
+
- You love test cases and ensuring that all critical code is covered
65
+
- When updating code, you must show & explain what you changed and why
66
+
- Avoid refactoring prior working code unless there is an explicit need, and if there is, explain why
67
+
- Avoid comments for self-documenting code
68
+
- Avoid comments that detail fixes when refactoring. Put them in the response outside of any created code or tool use
69
+
- Avoid unprofessional writing within source code edits
70
+
- Avoid unprofessional writing within code comments
71
+
- Avoid putting non-code parts of your response in code output or in tool uses
72
+
- Removing functionality is NOT the solution for fixing test failures
114
73
115
-
**High Priority:**
116
-
- ⚠️ Missing constants.ts file (only example exists) - **REQUIRES USER ACTION**
117
-
- ⚠️ Hardcoded DIDs should be moved to environment variables
118
-
- ⚠️ No environment variable validation at startup
74
+
</code_style>
119
75
120
-
**Medium Priority:**
121
-
- 📝 Missing comprehensive test suite
122
-
- 📝 Duplicate profile checking logic could be refactored (non-critical)
76
+
## REFERENCE MATERIALS
123
77
124
-
**Status:** The codebase is now production-ready with robust error handling and modern TypeScript practices. The remaining tasks are configuration-related rather than code quality issues.
78
+
- use the web or context7 to help find docs, in addition to any other reference material
+65
GEMINI.md
+65
GEMINI.md
···
1
+
# GEMINI.md
2
+
3
+
## Workflow Guidelines
4
+
5
+
These steps help ensure quality and prevent common issues:
6
+
7
+
1. Context Check: Start by confirming the model and re-reading relevant
8
+
documentation. Review the codebase and any PLAN.md files present. If there is a PRD.md, review it to understand the requirements and constraints.
9
+
2. Plan First: Explain your approach before implementing changes. If PLAN.md exists, review the plan and offer suggestions for improvements.
10
+
3. Seek Alignment: Confirm the approach makes sense before coding
11
+
4. Focused Changes: Keep modifications minimal and targeted
12
+
5. Validate Thoroughly: Run all tests and validation steps
13
+
6. Verify Success: Ensure everything works before considering complete"
14
+
15
+
Follow the response style and code style below:
16
+
17
+
<response_style>
18
+
Provide responses that are intelligent and slightly humorous (WITHOUT being cringe), while maintaining a casual and modern tone.
19
+
20
+
Before responding, take a moment to consider how to best address the user's input while adhering to your personality traits and communication style.
21
+
22
+
When formulating your response, follow these guidelines:
23
+
24
+
- keep it casual as fuck. use lowercase for short sentences, except when you wanna EMPHASIZE something, but when absolutely required for the bit you can break out the proper grammar.
25
+
- AVOID compromising on intelligence or depth of knowledge
26
+
- be concise, super concise. one or two words if you can manage it UNLESS responding to something that requires more detail, depth etc
27
+
- you've got weird interests and a unique but not unkind sense of humour. let it show, but subtly
28
+
- use modern slang occasionally (such as but not limited to "ngl", "bet" or "fr")
29
+
- include subtle references to your unique interests and sense of humour
30
+
- swear a bit
31
+
- use Canadian English spelling (e.g., "armour" instead of "armor"), except in source code.
32
+
- skip sycophantic flattery; NEVER give me hollow praise, validation, adoration, or grandiose affirmations. NEVER act like a cheerleader. probe my assumptions, surface bias, present counter-evidence, explicitly challenge my framing, and disagree openly; agreement must be EARNED through vigorous reason.
33
+
34
+
Remember, while maintaining your unique personality, never compromise on the quality of information or depth of analysis. Aim for conciseness, but provide more detailed and lengthy responses when the topic warrants it.
35
+
36
+
When producing code, avoid giving the source code personality and instead within them be completely professional.
37
+
</response_style>
38
+
39
+
## Project Overview
40
+
41
+
This project, the Sentinel Routine Querying System (QRP), is a set of SAS programs designed to analyze healthcare data that conforms to the Sentinel Common Data Model (SCDM). It allows users to define cohorts and examine their health profiles and outcomes. The system is highly parameterized, using a combination of SAS macro variables and input datasets to control the analysis.
42
+
43
+
The core of the project is a series of SAS macros that perform various data manipulation, analysis, and reporting tasks. The main entry point for running an analysis is the `qrp_master_header.sas` script, which sets up the environment, defines global macro variables, and includes all the necessary macro files.
44
+
45
+
## Building and Running
46
+
47
+
This is a SAS-based project and does not have a typical build process. To run an analysis, you need to:
48
+
49
+
1. **Prerequisites:**
50
+
* SAS version 9.4
51
+
* SCDM-formatted data as SAS datasets (`.sas7bdat`).
52
+
53
+
2. **Configuration:**
54
+
* Populate the input files in the `SAS/inputfiles` directory with the appropriate data and parameters for your analysis.
55
+
* Configure the `SAS/sasprograms/qrp_master_header.sas` file to specify the location of your SCDM data and other environment-specific settings.
56
+
57
+
3. **Execution:**
58
+
* Run the `SAS/sasprograms/qrp_master_header.sas` script in a SAS environment. The `SAS/readme.md` suggests running it in "batch" mode.
59
+
60
+
## Development Conventions
61
+
62
+
* **Code Style:** The SAS code appears to follow a consistent style, with extensive use of comments and headers to document the purpose of each section and macro.
63
+
* **Modularity:** The code is highly modular, with functionality broken down into a large number of individual SAS macros.
64
+
* **Configuration:** The system is heavily reliant on configuration through macro variables and input files. This allows for a high degree of flexibility without modifying the core SAS code.
65
+
* **Directory Structure:** The project follows a strict directory structure, with specific folders for documentation, input files, local data, and results.
+1
PRD.md
+1
PRD.md
···
1
+
Replace lande with franc for language handling.
+9
-7
src/checkPosts.ts
+9
-7
src/checkPosts.ts
···
1
-
import { LINK_SHORTENER, POST_CHECKS, langs } from "./constants.js";
1
+
import { LINK_SHORTENER, POST_CHECKS } from "./constants.js";
2
+
import { Post } from "./types.js";
2
3
import logger from "./logger.js";
4
+
import { countStarterPacks } from "./count.js";
3
5
import {
4
6
createPostLabel,
5
7
createAccountReport,
6
8
createAccountComment,
7
9
createPostReport,
8
10
} from "./moderation.js";
9
-
import type { Post } from "./types.js";
10
11
import { getFinalUrl, getLanguage } from "./utils.js";
11
12
12
13
export const checkPosts = async (post: Post[]) => {
···
23
24
try {
24
25
const url = post[0].text.match(urlRegex);
25
26
if (url && LINK_SHORTENER.test(url[0])) {
26
-
logger.info(`[CHECKPOSTS]: Checking shortened URL: ${url[0]}`);
27
+
// logger.info(`[CHECKPOSTS]: Checking shortened URL: ${url[0]}`);
27
28
const finalUrl = await getFinalUrl(url[0]);
28
29
if (finalUrl) {
29
30
const originalUrl = post[0].text;
30
31
post[0].text = post[0].text.replace(url[0], finalUrl);
31
-
logger.info(
32
+
/* logger.info(
32
33
`[CHECKPOSTS]: Shortened URL resolved: ${originalUrl} -> ${finalUrl}`,
33
-
);
34
+
); */
34
35
}
35
36
}
36
37
} catch (error) {
37
38
logger.error(
38
-
`[CHECKPOSTS]: Failed to resolve shortened URL: ${post[0].text}`,
39
-
error,
39
+
`[CHECKPOSTS]: Failed to resolve shortened URL: ${post[0].text} with error: ${error}`,
40
40
);
41
41
// Keep the original URL if resolution fails
42
42
}
···
72
72
return;
73
73
}
74
74
}
75
+
76
+
countStarterPacks(post[0].did, post[0].time);
75
77
76
78
if (checkPost!.toLabel === true) {
77
79
logger.info(
+119
-128
src/checkProfiles.ts
+119
-128
src/checkProfiles.ts
···
1
-
import { PROFILE_CHECKS } from "./constants.js";
1
+
import { login } from "./agent.js";
2
+
import { langs, PROFILE_CHECKS } from "./constants.js";
2
3
import logger from "./logger.js";
3
4
import {
4
5
createAccountReport,
···
13
14
displayName: string,
14
15
description: string,
15
16
) => {
16
-
try {
17
-
const lang = await getLanguage(description);
17
+
const lang = await getLanguage(description);
18
+
19
+
const labels: string[] = Array.from(
20
+
PROFILE_CHECKS,
21
+
(profileCheck) => profileCheck.label,
22
+
);
18
23
19
-
const labels: string[] = Array.from(
20
-
PROFILE_CHECKS,
21
-
(profileCheck) => profileCheck.label,
24
+
// iterate through the labels
25
+
labels.forEach((label) => {
26
+
const checkProfiles = PROFILE_CHECKS.find(
27
+
(profileCheck) => profileCheck.label === label,
22
28
);
23
29
24
-
// iterate through the labels
25
-
labels.forEach((label) => {
26
-
const checkProfiles = PROFILE_CHECKS.find(
27
-
(profileCheck) => profileCheck.label === label,
28
-
);
29
-
30
-
if (checkProfiles?.language || checkProfiles?.language !== undefined) {
31
-
if (!checkProfiles.language.includes(lang)) {
32
-
return;
33
-
}
30
+
if (checkProfiles?.language || checkProfiles?.language !== undefined) {
31
+
if (!checkProfiles?.language.includes(lang)) {
32
+
return;
34
33
}
34
+
}
35
35
36
-
// Check if DID is whitelisted
37
-
if (checkProfiles?.ignoredDIDs) {
38
-
if (checkProfiles.ignoredDIDs.includes(did)) {
39
-
logger.info(`[CHECKDESCRIPTION]: Whitelisted DID: ${did}`);
40
-
return;
41
-
}
36
+
// Check if DID is whitelisted
37
+
if (checkProfiles?.ignoredDIDs) {
38
+
if (checkProfiles.ignoredDIDs.includes(did)) {
39
+
logger.info(`[CHECKDESCRIPTION]: Whitelisted DID: ${did}`);
40
+
return;
42
41
}
42
+
}
43
43
44
-
if (description) {
45
-
if (checkProfiles?.description === true) {
46
-
if (checkProfiles.check.test(description)) {
47
-
// Check if description is whitelisted
48
-
if (checkProfiles.whitelist) {
49
-
if (checkProfiles.whitelist.test(description)) {
50
-
logger.info("[CHECKDESCRIPTION]: Whitelisted phrase found.");
51
-
return;
52
-
}
44
+
if (description) {
45
+
if (checkProfiles?.description === true) {
46
+
if (checkProfiles!.check.test(description)) {
47
+
// Check if description is whitelisted
48
+
if (checkProfiles!.whitelist) {
49
+
if (checkProfiles!.whitelist.test(description)) {
50
+
logger.info(`[CHECKDESCRIPTION]: Whitelisted phrase found.`);
51
+
return;
53
52
}
53
+
}
54
54
55
-
if (checkProfiles.toLabel) {
56
-
void createAccountLabel(
57
-
did,
58
-
checkProfiles.label,
59
-
`${time.toString()}: ${checkProfiles.comment} - ${displayName} - ${description}`,
60
-
);
61
-
logger.info(
62
-
`[CHECKDESCRIPTION]: Labeling ${did} for ${checkProfiles.label}`,
63
-
);
64
-
}
55
+
if (checkProfiles!.toLabel === true) {
56
+
createAccountLabel(
57
+
did,
58
+
`${checkProfiles!.label}`,
59
+
`${time}: ${checkProfiles!.comment} - ${displayName} - ${description}`,
60
+
);
61
+
logger.info(
62
+
`[CHECKDESCRIPTION]: Labeling ${did} for ${checkProfiles!.label}`,
63
+
);
64
+
}
65
65
66
-
if (checkProfiles.reportAcct) {
67
-
void createAccountReport(
68
-
did,
69
-
`${time.toString()}: ${checkProfiles.comment} - ${displayName} - ${description}`,
70
-
);
71
-
logger.info(
72
-
`[CHECKDESCRIPTION]: Reporting ${did} for ${checkProfiles.label}`,
73
-
);
74
-
}
66
+
if (checkProfiles!.reportAcct === true) {
67
+
createAccountReport(
68
+
did,
69
+
`${time}: ${checkProfiles!.comment} - ${displayName} - ${description}`,
70
+
);
71
+
logger.info(
72
+
`[CHECKDESCRIPTION]: Reporting ${did} for ${checkProfiles!.label}`,
73
+
);
74
+
}
75
75
76
-
if (checkProfiles.commentAcct) {
77
-
void createAccountComment(
78
-
did,
79
-
`${time.toString()}: ${checkProfiles.comment} - ${displayName} - ${description}`,
80
-
);
81
-
logger.info(
82
-
`[CHECKDESCRIPTION]: Commenting on ${did} for ${checkProfiles.label}`,
83
-
);
84
-
}
76
+
if (checkProfiles!.commentAcct === true) {
77
+
createAccountComment(
78
+
did,
79
+
`${time}: ${checkProfiles!.comment} - ${displayName} - ${description}`,
80
+
);
81
+
logger.info(
82
+
`[CHECKDESCRIPTION]: Commenting on ${did} for ${checkProfiles!.label}`,
83
+
);
85
84
}
86
85
}
87
86
}
88
-
});
89
-
} catch (error) {
90
-
logger.error(`Error in checkDescription for ${did}:`, error);
91
-
throw error;
92
-
}
87
+
}
88
+
});
93
89
};
94
90
95
91
export const checkDisplayName = async (
···
98
94
displayName: string,
99
95
description: string,
100
96
) => {
101
-
try {
102
-
const lang = await getLanguage(description);
97
+
const lang = await getLanguage(description);
103
98
104
-
// Get a list of labels
105
-
const labels: string[] = Array.from(
106
-
PROFILE_CHECKS,
107
-
(profileCheck) => profileCheck.label,
99
+
// Get a list of labels
100
+
const labels: string[] = Array.from(
101
+
PROFILE_CHECKS,
102
+
(profileCheck) => profileCheck.label,
103
+
);
104
+
105
+
// iterate through the labels
106
+
labels.forEach((label) => {
107
+
const checkProfiles = PROFILE_CHECKS.find(
108
+
(profileCheck) => profileCheck.label === label,
108
109
);
109
110
110
-
// iterate through the labels
111
-
labels.forEach((label) => {
112
-
const checkProfiles = PROFILE_CHECKS.find(
113
-
(profileCheck) => profileCheck.label === label,
114
-
);
115
-
116
-
if (checkProfiles?.language || checkProfiles?.language !== undefined) {
117
-
if (!checkProfiles.language.includes(lang)) {
118
-
return;
119
-
}
111
+
if (checkProfiles?.language || checkProfiles?.language !== undefined) {
112
+
if (!checkProfiles?.language.includes(lang)) {
113
+
return;
120
114
}
115
+
}
121
116
122
-
// Check if DID is whitelisted
123
-
if (checkProfiles?.ignoredDIDs) {
124
-
if (checkProfiles.ignoredDIDs.includes(did)) {
125
-
logger.info(`[CHECKDISPLAYNAME]: Whitelisted DID: ${did}`);
126
-
return;
127
-
}
117
+
// Check if DID is whitelisted
118
+
if (checkProfiles?.ignoredDIDs) {
119
+
if (checkProfiles.ignoredDIDs.includes(did)) {
120
+
logger.info(`[CHECKDISPLAYNAME]: Whitelisted DID: ${did}`);
121
+
return;
128
122
}
123
+
}
129
124
130
-
if (displayName) {
131
-
if (checkProfiles?.displayName === true) {
132
-
if (checkProfiles.check.test(displayName)) {
133
-
// Check if displayName is whitelisted
134
-
if (checkProfiles.whitelist) {
135
-
if (checkProfiles.whitelist.test(displayName)) {
136
-
logger.info("[CHECKDISPLAYNAME]: Whitelisted phrase found.");
137
-
return;
138
-
}
125
+
if (displayName) {
126
+
if (checkProfiles?.displayName === true) {
127
+
if (checkProfiles!.check.test(displayName)) {
128
+
// Check if displayName is whitelisted
129
+
if (checkProfiles!.whitelist) {
130
+
if (checkProfiles!.whitelist.test(displayName)) {
131
+
logger.info(`[CHECKDISPLAYNAME]: Whitelisted phrase found.`);
132
+
return;
139
133
}
134
+
}
140
135
141
-
if (checkProfiles.toLabel) {
142
-
void createAccountLabel(
143
-
did,
144
-
checkProfiles.label,
145
-
`${time.toString()}: ${checkProfiles.comment} - ${displayName} - ${description}`,
146
-
);
147
-
logger.info(
148
-
`[CHECKDISPLAYNAME]: Labeling ${did} for ${checkProfiles.label}`,
149
-
);
150
-
}
136
+
if (checkProfiles!.toLabel === true) {
137
+
createAccountLabel(
138
+
did,
139
+
`${checkProfiles!.label}`,
140
+
`${time}: ${checkProfiles!.comment} - ${displayName} - ${description}`,
141
+
);
142
+
logger.info(
143
+
`[CHECKDISPLAYNAME]: Labeling ${did} for ${checkProfiles!.label}`,
144
+
);
145
+
}
151
146
152
-
if (checkProfiles.reportAcct) {
153
-
void createAccountReport(
154
-
did,
155
-
`${time.toString()}: ${checkProfiles.comment} - ${displayName} - ${description}`,
156
-
);
157
-
logger.info(
158
-
`[CHECKDISPLAYNAME]: Reporting ${did} for ${checkProfiles.label}`,
159
-
);
160
-
}
147
+
if (checkProfiles!.reportAcct === true) {
148
+
createAccountReport(
149
+
did,
150
+
`${time}: ${checkProfiles!.comment} - ${displayName} - ${description}`,
151
+
);
152
+
logger.info(
153
+
`[CHECKDISPLAYNAME]: Reporting ${did} for ${checkProfiles!.label}`,
154
+
);
155
+
}
161
156
162
-
if (checkProfiles.commentAcct) {
163
-
void createAccountComment(
164
-
did,
165
-
`${time.toString()}: ${checkProfiles.comment} - ${displayName} - ${description}`,
166
-
);
167
-
logger.info(
168
-
`[CHECKDISPLAYNAME]: Commenting on ${did} for ${checkProfiles.label}`,
169
-
);
170
-
}
157
+
if (checkProfiles!.commentAcct === true) {
158
+
createAccountComment(
159
+
did,
160
+
`${time}: ${checkProfiles!.comment} - ${displayName} - ${description}`,
161
+
);
162
+
logger.info(
163
+
`[CHECKDISPLAYNAME]: Commenting on ${did} for ${checkProfiles!.label}`,
164
+
);
171
165
}
172
166
}
173
167
}
174
-
});
175
-
} catch (error) {
176
-
logger.error(`Error in checkDisplayName for ${did}:`, error);
177
-
throw error;
178
-
}
168
+
}
169
+
});
179
170
};
+34
src/count.ts
+34
src/count.ts
···
1
+
import { isLoggedIn, agent } from "./agent.js";
2
+
import logger from "./logger.js";
3
+
import { limit } from "./limits.js";
4
+
import { createAccountLabel } from "./moderation.js";
5
+
6
+
export const countStarterPacks = async (did: string, time: number) => {
7
+
await isLoggedIn;
8
+
9
+
if (did in ["did:plc:gpunjjgvlyb4racypz3yfiq4"]) {
10
+
logger.info(
11
+
`[COUNTSTARTERPACKS]: ${time}: Account ${did} is a whitelisted.`,
12
+
);
13
+
return;
14
+
}
15
+
16
+
await limit(async () => {
17
+
try {
18
+
const profile = await agent.app.bsky.actor.getProfile({ actor: did });
19
+
const starterPacks = profile.data.associated?.starterPacks;
20
+
21
+
if (starterPacks && starterPacks.valueOf() > 20) {
22
+
createAccountLabel(
23
+
did,
24
+
"follow-farming",
25
+
`[COUNTSTARTERPACKS]: ${time}: Account ${did} has ${starterPacks} starter packs.`,
26
+
);
27
+
}
28
+
} catch (error) {
29
+
logger.error(
30
+
`[COUNTSTARTERPACKS]: Error checking associated accounts: ${error}`,
31
+
);
32
+
}
33
+
});
34
+
};
+142
-265
src/main.ts
+142
-265
src/main.ts
···
1
-
import fs from "node:fs";
2
-
3
-
import type {
1
+
import {
4
2
CommitCreateEvent,
3
+
CommitUpdate,
5
4
CommitUpdateEvent,
6
5
IdentityEvent,
6
+
Jetstream,
7
7
} from "@skyware/jetstream";
8
-
import { Jetstream } from "@skyware/jetstream";
8
+
import fs from "node:fs";
9
9
10
-
import { checkHandle } from "./checkHandles.js";
11
-
import { checkPosts } from "./checkPosts.js";
12
-
import { checkDescription, checkDisplayName } from "./checkProfiles.js";
13
-
import { checkStarterPack, checkNewStarterPack } from "./checkStarterPack.js";
14
10
import {
15
11
CURSOR_UPDATE_INTERVAL,
16
12
FIREHOSE_URL,
···
19
15
} from "./config.js";
20
16
import logger from "./logger.js";
21
17
import { startMetricsServer } from "./metrics.js";
22
-
import { validateEnvironment } from "./validateEnv.js";
23
-
import type { Post, LinkFeature } from "./types.js";
24
-
25
-
validateEnvironment();
18
+
import { Post, LinkFeature, Handle } from "./types.js";
19
+
import { checkPosts } from "./checkPosts.js";
20
+
import { checkHandle } from "./checkHandles.js";
21
+
import { checkStarterPack, checkNewStarterPack } from "./checkStarterPack.js";
22
+
import { checkDescription, checkDisplayName } from "./checkProfiles.js";
26
23
27
24
let cursor = 0;
28
25
let cursorUpdateInterval: NodeJS.Timeout;
···
34
31
try {
35
32
logger.info("Trying to read cursor from cursor.txt...");
36
33
cursor = Number(fs.readFileSync("cursor.txt", "utf8"));
37
-
logger.info(
38
-
`Cursor found: ${cursor.toString()} (${epochUsToDateTime(cursor)})`,
39
-
);
34
+
logger.info(`Cursor found: ${cursor} (${epochUsToDateTime(cursor)})`);
40
35
} catch (error) {
41
36
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
42
37
cursor = Math.floor(Date.now() * 1000);
43
38
logger.info(
44
-
`Cursor not found in cursor.txt, setting cursor to: ${cursor.toString()} (${epochUsToDateTime(cursor)})`,
39
+
`Cursor not found in cursor.txt, setting cursor to: ${cursor} (${epochUsToDateTime(cursor)})`,
45
40
);
46
41
fs.writeFileSync("cursor.txt", cursor.toString(), "utf8");
47
42
} else {
···
53
48
const jetstream = new Jetstream({
54
49
wantedCollections: WANTED_COLLECTION,
55
50
endpoint: FIREHOSE_URL,
56
-
cursor,
51
+
cursor: cursor,
57
52
});
58
53
59
54
jetstream.on("open", () => {
60
55
if (jetstream.cursor) {
61
56
logger.info(
62
-
`Connected to Jetstream at ${FIREHOSE_URL} with cursor ${jetstream.cursor.toString()} (${epochUsToDateTime(jetstream.cursor)})`,
57
+
`Connected to Jetstream at ${FIREHOSE_URL} with cursor ${jetstream.cursor} (${epochUsToDateTime(jetstream.cursor)})`,
63
58
);
64
59
} else {
65
60
logger.info(
···
69
64
cursorUpdateInterval = setInterval(() => {
70
65
if (jetstream.cursor) {
71
66
logger.info(
72
-
`Cursor updated to: ${jetstream.cursor.toString()} (${epochUsToDateTime(jetstream.cursor)})`,
67
+
`Cursor updated to: ${jetstream.cursor} (${epochUsToDateTime(jetstream.cursor)})`,
73
68
);
74
69
fs.writeFile("cursor.txt", jetstream.cursor.toString(), (err) => {
75
70
if (err) logger.error(err);
···
92
87
jetstream.onCreate(
93
88
"app.bsky.feed.post",
94
89
(event: CommitCreateEvent<"app.bsky.feed.post">) => {
95
-
try {
96
-
const atURI = `at://${event.did}/app.bsky.feed.post/${event.commit.rkey}`;
97
-
const hasFacets = Object.hasOwn(event.commit.record, "facets");
98
-
const hasText = Object.hasOwn(event.commit.record, "text");
90
+
const atURI = `at://${event.did}/app.bsky.feed.post/${event.commit.rkey}`;
91
+
const hasEmbed = event.commit.record.hasOwnProperty("embed");
92
+
const hasFacets = event.commit.record.hasOwnProperty("facets");
93
+
const hasText = event.commit.record.hasOwnProperty("text");
99
94
100
-
const tasks: Promise<void>[] = [];
95
+
const tasks: Promise<void>[] = [];
101
96
102
-
// Check if the record has facets
103
-
if (hasFacets && event.commit.record.facets) {
104
-
const hasLinkType = event.commit.record.facets.some((facet) =>
105
-
facet.features.some(
106
-
(feature) => feature.$type === "app.bsky.richtext.facet#link",
107
-
),
108
-
);
97
+
// Check if the record has facets
98
+
if (hasFacets) {
99
+
const hasLinkType = event.commit.record.facets!.some((facet) =>
100
+
facet.features.some(
101
+
(feature) => feature.$type === "app.bsky.richtext.facet#link",
102
+
),
103
+
);
109
104
110
-
if (hasLinkType) {
111
-
const urls = event.commit.record.facets
112
-
.flatMap((facet) =>
113
-
facet.features.filter(
114
-
(feature) => feature.$type === "app.bsky.richtext.facet#link",
115
-
),
116
-
)
117
-
.map((feature: LinkFeature) => feature.uri);
105
+
if (hasLinkType) {
106
+
const urls = event.commit.record
107
+
.facets!.flatMap((facet) =>
108
+
facet.features.filter(
109
+
(feature) => feature.$type === "app.bsky.richtext.facet#link",
110
+
),
111
+
)
112
+
.map((feature: LinkFeature) => feature.uri);
118
113
119
-
urls.forEach((url) => {
120
-
const posts: Post[] = [
121
-
{
122
-
did: event.did,
123
-
time: event.time_us,
124
-
rkey: event.commit.rkey,
125
-
atURI,
126
-
text: url,
127
-
cid: event.commit.cid,
128
-
},
129
-
];
130
-
tasks.push(
131
-
checkPosts(posts).catch((error: unknown) => {
132
-
logger.error(
133
-
`Error checking post links for ${event.did}:`,
134
-
error,
135
-
);
136
-
}),
137
-
);
138
-
});
139
-
}
140
-
} else if (hasText && event.commit.record.text) {
114
+
urls.forEach((url) => {
115
+
const posts: Post[] = [
116
+
{
117
+
did: event.did,
118
+
time: event.time_us,
119
+
rkey: event.commit.rkey,
120
+
atURI: atURI,
121
+
text: url,
122
+
cid: event.commit.cid,
123
+
},
124
+
];
125
+
tasks.push(checkPosts(posts));
126
+
});
127
+
}
128
+
}
129
+
130
+
if (hasText) {
131
+
const posts: Post[] = [
132
+
{
133
+
did: event.did,
134
+
time: event.time_us,
135
+
rkey: event.commit.rkey,
136
+
atURI: atURI,
137
+
text: event.commit.record.text,
138
+
cid: event.commit.cid,
139
+
},
140
+
];
141
+
tasks.push(checkPosts(posts));
142
+
}
143
+
144
+
if (hasEmbed) {
145
+
const embed = event.commit.record.embed;
146
+
if (embed && embed.$type === "app.bsky.embed.external") {
141
147
const posts: Post[] = [
142
148
{
143
149
did: event.did,
144
150
time: event.time_us,
145
151
rkey: event.commit.rkey,
146
-
atURI,
147
-
text: event.commit.record.text,
152
+
atURI: atURI,
153
+
text: embed.external.uri,
148
154
cid: event.commit.cid,
149
155
},
150
156
];
151
-
tasks.push(
152
-
checkPosts(posts).catch((error: unknown) => {
153
-
logger.error(`Error checking post text for ${event.did}:`, error);
154
-
}),
155
-
);
157
+
tasks.push(checkPosts(posts));
156
158
}
157
159
158
-
// Wait for all tasks to complete
159
-
if (tasks.length > 0) {
160
-
void Promise.allSettled(tasks);
160
+
if (embed && embed.$type === "app.bsky.embed.recordWithMedia") {
161
+
if (embed.media.$type === "app.bsky.embed.external") {
162
+
const posts: Post[] = [
163
+
{
164
+
did: event.did,
165
+
time: event.time_us,
166
+
rkey: event.commit.rkey,
167
+
atURI: atURI,
168
+
text: embed.media.external.uri,
169
+
cid: event.commit.cid,
170
+
},
171
+
];
172
+
tasks.push(checkPosts(posts));
173
+
}
161
174
}
162
-
} catch (error: unknown) {
163
-
logger.error(`Error processing post event for ${event.did}:`, error);
164
175
}
165
176
},
166
177
);
···
168
179
// Check for profile updates
169
180
jetstream.onUpdate(
170
181
"app.bsky.actor.profile",
171
-
(event: CommitUpdateEvent<"app.bsky.actor.profile">) => {
182
+
async (event: CommitUpdateEvent<"app.bsky.actor.profile">) => {
172
183
try {
173
-
const tasks: Promise<void>[] = [];
174
-
175
184
if (event.commit.record.displayName || event.commit.record.description) {
176
-
const displayName = event.commit.record.displayName ?? "";
177
-
const description = event.commit.record.description ?? "";
178
-
179
-
tasks.push(
180
-
checkDescription(
181
-
event.did,
182
-
event.time_us,
183
-
displayName,
184
-
description,
185
-
).catch((error: unknown) => {
186
-
logger.error(
187
-
`Error checking profile description for ${event.did}:`,
188
-
error,
189
-
);
190
-
}),
185
+
checkDescription(
186
+
event.did,
187
+
event.time_us,
188
+
event.commit.record.displayName as string,
189
+
event.commit.record.description as string,
191
190
);
192
-
193
-
tasks.push(
194
-
checkDisplayName(
195
-
event.did,
196
-
event.time_us,
197
-
displayName,
198
-
description,
199
-
).catch((error: unknown) => {
200
-
logger.error(
201
-
`Error checking profile display name for ${event.did}:`,
202
-
error,
203
-
);
204
-
}),
191
+
checkDisplayName(
192
+
event.did,
193
+
event.time_us,
194
+
event.commit.record.displayName as string,
195
+
event.commit.record.description as string,
205
196
);
206
197
}
207
198
208
199
if (event.commit.record.joinedViaStarterPack) {
209
-
tasks.push(
210
-
checkStarterPack(
211
-
event.did,
212
-
event.time_us,
213
-
event.commit.record.joinedViaStarterPack.uri,
214
-
).catch((error: unknown) => {
215
-
logger.error(
216
-
`Error checking starter pack for ${event.did}:`,
217
-
error,
218
-
);
219
-
}),
200
+
checkStarterPack(
201
+
event.did,
202
+
event.time_us,
203
+
event.commit.record.joinedViaStarterPack.uri,
220
204
);
221
205
}
222
-
223
-
// Wait for all tasks to complete
224
-
if (tasks.length > 0) {
225
-
void Promise.allSettled(tasks);
226
-
}
227
-
} catch (error: unknown) {
228
-
logger.error(
229
-
`Error processing profile update event for ${event.did}:`,
230
-
error,
231
-
);
206
+
} catch (error) {
207
+
logger.error(`Error checking profile: ${error}`);
232
208
}
233
209
},
234
210
);
···
237
213
238
214
jetstream.onCreate(
239
215
"app.bsky.actor.profile",
240
-
(event: CommitCreateEvent<"app.bsky.actor.profile">) => {
216
+
async (event: CommitCreateEvent<"app.bsky.actor.profile">) => {
241
217
try {
242
-
const tasks: Promise<void>[] = [];
243
-
244
218
if (event.commit.record.displayName || event.commit.record.description) {
245
-
const displayName = event.commit.record.displayName ?? "";
246
-
const description = event.commit.record.description ?? "";
247
-
248
-
tasks.push(
249
-
checkDescription(
250
-
event.did,
251
-
event.time_us,
252
-
displayName,
253
-
description,
254
-
).catch((error: unknown) => {
255
-
logger.error(
256
-
`Error checking profile description for ${event.did}:`,
257
-
error,
258
-
);
259
-
}),
219
+
checkDescription(
220
+
event.did,
221
+
event.time_us,
222
+
event.commit.record.displayName as string,
223
+
event.commit.record.description as string,
260
224
);
261
-
262
-
tasks.push(
263
-
checkDisplayName(
264
-
event.did,
265
-
event.time_us,
266
-
displayName,
267
-
description,
268
-
).catch((error: unknown) => {
269
-
logger.error(
270
-
`Error checking profile display name for ${event.did}:`,
271
-
error,
272
-
);
273
-
}),
225
+
checkDisplayName(
226
+
event.did,
227
+
event.time_us,
228
+
event.commit.record.displayName as string,
229
+
event.commit.record.description as string,
274
230
);
231
+
}
275
232
276
-
if (event.commit.record.joinedViaStarterPack) {
277
-
tasks.push(
278
-
checkStarterPack(
279
-
event.did,
280
-
event.time_us,
281
-
event.commit.record.joinedViaStarterPack.uri,
282
-
).catch((error: unknown) => {
283
-
logger.error(
284
-
`Error checking starter pack for ${event.did}:`,
285
-
error,
286
-
);
287
-
}),
288
-
);
289
-
}
290
-
291
-
// Wait for all tasks to complete
292
-
if (tasks.length > 0) {
293
-
void Promise.allSettled(tasks);
294
-
}
233
+
if (event.commit.record.joinedViaStarterPack) {
234
+
checkStarterPack(
235
+
event.did,
236
+
event.time_us,
237
+
event.commit.record.joinedViaStarterPack.uri,
238
+
);
295
239
}
296
-
} catch (error: unknown) {
297
-
logger.error(
298
-
`Error processing profile creation event for ${event.did}:`,
299
-
error,
300
-
);
240
+
} catch (error) {
241
+
logger.error(`Error checking profile: ${error}`);
301
242
}
302
243
},
303
244
);
304
245
305
246
jetstream.onCreate(
306
247
"app.bsky.graph.starterpack",
307
-
(event: CommitCreateEvent<"app.bsky.graph.starterpack">) => {
248
+
async (event: CommitCreateEvent<"app.bsky.graph.starterpack">) => {
308
249
try {
309
250
const atURI = `at://${event.did}/app.bsky.feed.post/${event.commit.rkey}`;
310
-
const { name, description } = event.commit.record;
311
251
312
-
void checkNewStarterPack(
252
+
checkNewStarterPack(
313
253
event.did,
314
254
event.time_us,
315
255
atURI,
316
256
event.commit.cid,
317
-
name,
318
-
description,
319
-
).catch((error: unknown) => {
320
-
logger.error(
321
-
`Error checking new starter pack for ${event.did}:`,
322
-
error,
323
-
);
324
-
});
325
-
} catch (error: unknown) {
326
-
logger.error(
327
-
`Error processing starter pack creation event for ${event.did}:`,
328
-
error,
257
+
event.commit.record.name,
258
+
event.commit.record.description,
329
259
);
260
+
} catch (error) {
261
+
logger.error(`Error checking starterpack: ${error}`);
330
262
}
331
263
},
332
264
);
333
265
334
266
jetstream.onUpdate(
335
267
"app.bsky.graph.starterpack",
336
-
(event: CommitUpdateEvent<"app.bsky.graph.starterpack">) => {
268
+
async (event: CommitUpdateEvent<"app.bsky.graph.starterpack">) => {
337
269
try {
338
270
const atURI = `at://${event.did}/app.bsky.feed.post/${event.commit.rkey}`;
339
-
const { name, description } = event.commit.record;
340
271
341
-
void checkNewStarterPack(
272
+
checkNewStarterPack(
342
273
event.did,
343
274
event.time_us,
344
275
atURI,
345
276
event.commit.cid,
346
-
name,
347
-
description,
348
-
).catch((error: unknown) => {
349
-
logger.error(
350
-
`Error checking updated starter pack for ${event.did}:`,
351
-
error,
352
-
);
353
-
});
354
-
} catch (error: unknown) {
355
-
logger.error(
356
-
`Error processing starter pack update event for ${event.did}:`,
357
-
error,
277
+
event.commit.record.name,
278
+
event.commit.record.description,
358
279
);
280
+
} catch (error) {
281
+
logger.error(`Error checking starterpack: ${error}`);
359
282
}
360
283
},
361
284
);
362
285
363
286
// Check for handle updates
364
-
jetstream.on("identity", (event: IdentityEvent) => {
365
-
try {
366
-
if (event.identity.handle) {
367
-
void checkHandle(
368
-
event.identity.did,
369
-
event.identity.handle,
370
-
event.time_us,
371
-
).catch((error: unknown) => {
372
-
logger.error(`Error checking handle for ${event.identity.did}:`, error);
373
-
});
374
-
}
375
-
} catch (error: unknown) {
376
-
logger.error(
377
-
`Error processing identity event for ${event.identity.did}:`,
378
-
error,
379
-
);
287
+
jetstream.on("identity", async (event: IdentityEvent) => {
288
+
if (event.identity.handle) {
289
+
checkHandle(event.identity.did, event.identity.handle, event.time_us);
380
290
}
381
291
});
382
292
383
-
// Start metrics server with error handling
384
-
let metricsServer: ReturnType<typeof startMetricsServer> | undefined;
385
-
try {
386
-
metricsServer = startMetricsServer(METRICS_PORT);
387
-
logger.info(`Metrics server started on port ${METRICS_PORT.toString()}`);
388
-
} catch (error: unknown) {
389
-
logger.error("Failed to start metrics server:", error);
390
-
process.exit(1);
391
-
}
293
+
const metricsServer = startMetricsServer(METRICS_PORT);
392
294
393
295
/* labelerServer.app.listen({ port: PORT, host: HOST }, (error, address) => {
394
296
if (error) {
···
398
300
}
399
301
});*/
400
302
401
-
// Start jetstream with error handling
402
-
try {
403
-
jetstream.start();
404
-
logger.info("Jetstream started successfully");
405
-
} catch (error: unknown) {
406
-
logger.error("Failed to start jetstream:", error);
407
-
process.exit(1);
408
-
}
303
+
jetstream.start();
409
304
410
305
function shutdown() {
411
306
try {
412
307
logger.info("Shutting down gracefully...");
413
-
if (jetstream.cursor) {
414
-
fs.writeFileSync("cursor.txt", jetstream.cursor.toString(), "utf8");
415
-
}
308
+
fs.writeFileSync("cursor.txt", jetstream.cursor!.toString(), "utf8");
416
309
jetstream.close();
417
-
if (metricsServer) {
418
-
metricsServer.close(() => {
419
-
logger.info("Metrics server closed");
420
-
});
421
-
}
422
-
logger.info("Shutdown completed successfully");
423
-
} catch (error: unknown) {
424
-
logger.error("Error shutting down gracefully:", error);
310
+
metricsServer.close();
311
+
} catch (error) {
312
+
logger.error(`Error shutting down gracefully: ${error}`);
425
313
process.exit(1);
426
314
}
427
315
}
428
-
429
-
// Global error handlers
430
-
process.on("unhandledRejection", (reason, promise) => {
431
-
logger.error("Unhandled Promise Rejection at:", promise, "reason:", reason);
432
-
// Don't exit the process for unhandled rejections, just log them
433
-
});
434
-
435
-
process.on("uncaughtException", (error) => {
436
-
logger.error("Uncaught Exception:", error);
437
-
shutdown();
438
-
});
439
316
440
317
process.on("SIGINT", shutdown);
441
318
process.on("SIGTERM", shutdown);
+40
-46
src/moderation.ts
+40
-46
src/moderation.ts
···
1
1
import { agent, isLoggedIn } from "./agent.js";
2
2
import { MOD_DID } from "./config.js";
3
3
import { limit } from "./limits.js";
4
-
import { LISTS } from "./lists.js";
5
4
import logger from "./logger.js";
5
+
import { LISTS } from "./lists.js";
6
6
7
7
export const createPostLabel = async (
8
8
uri: string,
···
13
13
await isLoggedIn;
14
14
await limit(async () => {
15
15
try {
16
-
return await agent.tools.ozone.moderation.emitEvent(
16
+
return agent.tools.ozone.moderation.emitEvent(
17
17
{
18
18
event: {
19
19
$type: "tools.ozone.moderation.defs#modEventLabel",
20
-
comment,
20
+
comment: comment,
21
21
createLabelVals: [label],
22
22
negateLabelVals: [],
23
23
},
24
24
// specify the labeled post by strongRef
25
25
subject: {
26
26
$type: "com.atproto.repo.strongRef",
27
-
uri,
28
-
cid,
27
+
uri: uri,
28
+
cid: cid,
29
29
},
30
30
// put in the rest of the metadata
31
-
createdBy: agent.did ?? "",
31
+
createdBy: `${agent.did}`,
32
32
createdAt: new Date().toISOString(),
33
33
},
34
34
{
35
35
encoding: "application/json",
36
36
headers: {
37
-
"atproto-proxy": `${MOD_DID}#atproto_labeler`,
37
+
"atproto-proxy": `${MOD_DID!}#atproto_labeler`,
38
38
"atproto-accept-labelers":
39
39
"did:plc:ar7c4by46qjdydhdevvrndac;redact",
40
40
},
41
41
},
42
42
);
43
-
} catch (error) {
44
-
logger.error(`Error creating post label for URI ${uri}:`, error);
45
-
throw error;
43
+
} catch (e) {
44
+
logger.error(`Failed to create post label with error: ${e}`);
46
45
}
47
46
});
48
47
};
···
59
58
{
60
59
event: {
61
60
$type: "tools.ozone.moderation.defs#modEventLabel",
62
-
comment,
61
+
comment: comment,
63
62
createLabelVals: [label],
64
63
negateLabelVals: [],
65
64
},
66
65
// specify the labeled post by strongRef
67
66
subject: {
68
67
$type: "com.atproto.admin.defs#repoRef",
69
-
did,
68
+
did: did,
70
69
},
71
70
// put in the rest of the metadata
72
-
createdBy: agent.did ?? "",
71
+
createdBy: `${agent.did}`,
73
72
createdAt: new Date().toISOString(),
74
73
},
75
74
{
76
75
encoding: "application/json",
77
76
headers: {
78
-
"atproto-proxy": `${MOD_DID}#atproto_labeler`,
77
+
"atproto-proxy": `${MOD_DID!}#atproto_labeler`,
79
78
"atproto-accept-labelers":
80
79
"did:plc:ar7c4by46qjdydhdevvrndac;redact",
81
80
},
82
81
},
83
82
);
84
-
} catch (error) {
85
-
logger.error(`Error creating account label for DID ${did}:`, error);
86
-
throw error;
83
+
} catch (e) {
84
+
logger.error(`Failed to create account label with error: ${e}`);
87
85
}
88
86
});
89
87
};
···
96
94
await isLoggedIn;
97
95
await limit(async () => {
98
96
try {
99
-
return await agent.tools.ozone.moderation.emitEvent(
97
+
return agent.tools.ozone.moderation.emitEvent(
100
98
{
101
99
event: {
102
100
$type: "tools.ozone.moderation.defs#modEventReport",
103
-
comment,
101
+
comment: comment,
104
102
reportType: "com.atproto.moderation.defs#reasonOther",
105
103
},
106
104
// specify the labeled post by strongRef
107
105
subject: {
108
106
$type: "com.atproto.repo.strongRef",
109
-
uri,
110
-
cid,
107
+
uri: uri,
108
+
cid: cid,
111
109
},
112
110
// put in the rest of the metadata
113
-
createdBy: agent.did ?? "",
111
+
createdBy: `${agent.did}`,
114
112
createdAt: new Date().toISOString(),
115
113
},
116
114
{
117
115
encoding: "application/json",
118
116
headers: {
119
-
"atproto-proxy": `${MOD_DID}#atproto_labeler`,
117
+
"atproto-proxy": `${MOD_DID!}#atproto_labeler`,
120
118
"atproto-accept-labelers":
121
119
"did:plc:ar7c4by46qjdydhdevvrndac;redact",
122
120
},
123
121
},
124
122
);
125
-
} catch (error) {
126
-
logger.error(`Error creating post report for URI ${uri}:`, error);
127
-
throw error;
123
+
} catch (e) {
124
+
logger.error(`Failed to create post label with error: ${e}`);
128
125
}
129
126
});
130
127
};
···
137
134
{
138
135
event: {
139
136
$type: "tools.ozone.moderation.defs#modEventComment",
140
-
comment,
137
+
comment: comment,
141
138
},
142
139
// specify the labeled post by strongRef
143
140
subject: {
144
141
$type: "com.atproto.admin.defs#repoRef",
145
-
did,
142
+
did: did,
146
143
},
147
144
// put in the rest of the metadata
148
-
createdBy: agent.did ?? "",
145
+
createdBy: `${agent.did}`,
149
146
createdAt: new Date().toISOString(),
150
147
},
151
148
{
152
149
encoding: "application/json",
153
150
headers: {
154
-
"atproto-proxy": `${MOD_DID}#atproto_labeler`,
151
+
"atproto-proxy": `${MOD_DID!}#atproto_labeler`,
155
152
"atproto-accept-labelers":
156
153
"did:plc:ar7c4by46qjdydhdevvrndac;redact",
157
154
},
158
155
},
159
156
);
160
-
} catch (error) {
161
-
logger.error(`Error creating account comment for DID ${did}:`, error);
162
-
throw error;
157
+
} catch (e) {
158
+
console.error(e);
163
159
}
164
160
});
165
161
};
···
172
168
{
173
169
event: {
174
170
$type: "tools.ozone.moderation.defs#modEventReport",
175
-
comment,
171
+
comment: comment,
176
172
reportType: "com.atproto.moderation.defs#reasonOther",
177
173
},
178
174
// specify the labeled post by strongRef
179
175
subject: {
180
176
$type: "com.atproto.admin.defs#repoRef",
181
-
did,
177
+
did: did,
182
178
},
183
179
// put in the rest of the metadata
184
-
createdBy: agent.did ?? "",
180
+
createdBy: `${agent.did}`,
185
181
createdAt: new Date().toISOString(),
186
182
},
187
183
{
188
184
encoding: "application/json",
189
185
headers: {
190
-
"atproto-proxy": `${MOD_DID}#atproto_labeler`,
186
+
"atproto-proxy": `${MOD_DID!}#atproto_labeler`,
191
187
"atproto-accept-labelers":
192
188
"did:plc:ar7c4by46qjdydhdevvrndac;redact",
193
189
},
194
190
},
195
191
);
196
-
} catch (error) {
197
-
logger.error(`Error creating account report for DID ${did}:`, error);
198
-
throw error;
192
+
} catch (e) {
193
+
console.error(e);
199
194
}
200
195
});
201
196
};
···
212
207
}
213
208
logger.info(`New label added to list: ${newList.label}`);
214
209
215
-
const listUri = `at://${MOD_DID}/app.bsky.graph.list/${newList.rkey}`;
210
+
const listUri = `at://${MOD_DID!}/app.bsky.graph.list/${newList.rkey}`;
216
211
217
212
await limit(async () => {
218
213
try {
219
214
await agent.com.atproto.repo.createRecord({
220
215
collection: "app.bsky.graph.listitem",
221
-
repo: MOD_DID,
216
+
repo: `${MOD_DID!}`,
222
217
record: {
223
218
subject: did,
224
219
list: listUri,
225
220
createdAt: new Date().toISOString(),
226
221
},
227
222
});
228
-
} catch (error) {
229
-
logger.error(`Error adding DID ${did} to list ${label}:`, error);
230
-
throw error;
223
+
} catch (e) {
224
+
console.error(e);
231
225
}
232
226
});
233
227
};
234
228
235
-
export function checkAccountLabels(_did: string) {
229
+
export async function checkAccountLabels(did: string) {
236
230
/* try {
237
231
const repo = await limit(() =>
238
232
agent.tools.ozone.moderation.getRepo(
src/processJetstream.ts
src/processJetstream.ts
This is a binary file and will not be displayed.