Skip to content
This repository was archived by the owner on May 21, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@ Here's an example of the `config.json` file:
"forceDescriptionEmbed": false,
"removeDuplicate": false,
"descriptionClearHTML": false,
"titleClearHTML": false
"titleClearHTML": false,
"adaptiveSpacing": false,
"spacingWindow": 600,
"minSpacing": 1,
"maxSpacing": 60
}
```

Expand All @@ -108,6 +112,10 @@ Here's an example of the `config.json` file:
- `removeDuplicate`: Instead of using the last date to track which items need to be published, use a text-based database to track duplicate items.
- `descriptionClearHTML`: Remove HTML from the description of the Open Graph description and RSS-provided description (to make it more readable).
- `titleClearHTML`: Remove HTML from the title of the post (to make it more readable).
- `adaptiveSpacing`: Enable adaptive spacing between posts based on the queue size.
- `spacingWindow`: Time window (in seconds) used when calculating adaptive spacing.
- `minSpacing`: Minimum number of seconds between posts when adaptive spacing is enabled.
- `maxSpacing`: Maximum number of seconds between posts when adaptive spacing is enabled.

A `docker-compose.yml` file can be found in the root directory as `docker-compose.example.yml`, which you can use to set up the RSS poster using Docker.

Expand Down
4 changes: 4 additions & 0 deletions app/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ interface Config {
imageAlt?: string;
removeDuplicate?: boolean;
titleClearHTML?: boolean;
adaptiveSpacing?: boolean;
spacingWindow?: number;
minSpacing?: number;
maxSpacing?: number;
}

interface Item {
Expand Down
2 changes: 1 addition & 1 deletion app/utils/bskyHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ async function post({

let post: any;
try {
post = await bskyAgent.post(record);
post = await bskyAgent.post(record as any);
} catch (error: any) {
// if (error instanceof XRPCError) {
if (error.constructor.name == XRPCError.name) {
Expand Down
45 changes: 45 additions & 0 deletions app/utils/queueHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ let queue: QueueItems[] = [];
let rateLimited: boolean = false;
let queueRunning: boolean = false;
let queueSnapshot: QueueItems[] = [];
let lastPostTimestamp = 0;

let config: Config = {
string: "",
Expand All @@ -22,6 +23,10 @@ let config: Config = {
imageAlt: "",
removeDuplicate: false,
titleClearHTML: false,
adaptiveSpacing: false,
spacingWindow: 600,
minSpacing: 1,
maxSpacing: 60,
};

async function start() {
Expand Down Expand Up @@ -66,6 +71,17 @@ async function runQueue() {
queue.splice(i, 1);
queueSnapshot.splice(i, 1);
i--;
if (config.minSpacing && lastPostTimestamp) {
const elapsed = Date.now() - lastPostTimestamp;
const waitMs = config.minSpacing * 1000 - elapsed;
if (waitMs > 0) {
const waitSec = Math.ceil(waitMs / 1000);
console.log(
`[${new Date().toUTCString()}] - [bsky.rss QUEUE] Waiting ${waitSec} seconds before next post`
);
await sleep(waitMs);
}
}
let post = await bsky.post({
content: item.content,
embed: item.embed,
Expand All @@ -89,6 +105,18 @@ async function runQueue() {
})`
);
db.writeDate(new Date(item.date));
lastPostTimestamp = Date.now();
if (config.adaptiveSpacing && queueSnapshot.length > 0) {
const remaining = queueSnapshot.length;
const delaySec = computeDelay(remaining + 1);

if (delaySec > 0) {
console.log(
`[${new Date().toUTCString()}] - [bsky.rss QUEUE] Waiting ${delaySec} seconds before next post`
);
await sleep(delaySec * 1000);
}
}
if (i === queueSnapshot.length - 1) {
queueRunning = false;
queueSnapshot = [];
Expand Down Expand Up @@ -121,4 +149,21 @@ async function writeQueue({
return queue;
}

function clamp(x: number, lo: number, hi: number) {
return Math.max(lo, Math.min(hi, x));
}

function computeDelay(q: number) {
if (!config.adaptiveSpacing) return 0;
if (q <= 1) return 0;
const window = config.spacingWindow || 600;
const min = config.minSpacing || 1;
const max = config.maxSpacing || 60;
return clamp(window / q, min, max);
}

function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}

export default { writeQueue, start };
4 changes: 4 additions & 0 deletions app/utils/rssHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ let config: Config = {
forceDescriptionEmbed: false,
removeDuplicate: false,
titleClearHTML: false,
adaptiveSpacing: false,
spacingWindow: 600,
minSpacing: 1,
maxSpacing: 60,
};

async function start() {
Expand Down
6 changes: 5 additions & 1 deletion data/config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,9 @@
"forceDescriptionEmbed": false,
"removeDuplicate": false,
"descriptionClearHTML": false,
"titleClearHTML": false
"titleClearHTML": false,
"adaptiveSpacing": false,
"spacingWindow": 600,
"minSpacing": 1,
"maxSpacing": 60
}
5 changes: 3 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"extends": "gts/tsconfig-google",
"compilerOptions": {
"lib": ["ESNext"],
"lib": ["ESNext", "DOM"],
"outDir": "lib",
"removeComments": true,
"target": "ES6",
Expand All @@ -18,7 +18,8 @@
"noImplicitReturns": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true
"noUnusedParameters": true,
"skipLibCheck": true
},
"include": ["./**/*.ts"],
"exclude": ["node_modules/**/*"]
Expand Down