Compare commits

...

9 Commits

Author SHA1 Message Date
lamp c48f109d15 message queue 2024-12-04 23:44:08 -08:00
lamp d71e509e0a /invite 2024-12-04 22:35:36 -08:00
lamp c983e2ac47 use own member list, join/leave msgs 2024-12-04 22:09:19 -08:00
lamp 43df6863e4 escape csv value 2024-11-30 23:32:15 -08:00
lamp fda682c302 support embed 2024-11-30 22:48:30 -08:00
lamp cd026cbd8c handle ratelimit 2024-11-24 01:22:13 -08:00
lamp 663cd505be work around facet bug 2024-11-24 00:33:38 -08:00
lamp a5c23411ec handle blocking 2024-11-22 22:56:49 -08:00
lamp da34c10121 fetch-retry 2024-11-04 13:41:26 -08:00
4 changed files with 153 additions and 31 deletions
+2 -1
View File
@@ -1,3 +1,4 @@
chatlog.csv *.csv
node_modules node_modules
.env .env
memberlist.json
+135 -21
View File
@@ -1,6 +1,28 @@
import 'dotenv/config'; import 'dotenv/config';
import {Bot, IncomingChatPreference, RichText} from "@skyware/bot"; import {Bot, IncomingChatPreference, RichText} from "@skyware/bot";
import { appendFileSync } from "fs"; import fetchRetry from "fetch-retry";
import { appendFileSync, readFileSync, writeFileSync } from "fs";
var _fetch = fetchRetry(global.fetch);
global.fetch = function fetch(url, options) {
//console.debug("fetch", String(url));
return _fetch(url, options);
}
var memberlist = []; // list of conversation ids
try {
memberlist = JSON.parse(readFileSync("memberlist.json", "utf8"));
} catch (error) {}
function saveMemberlist() {
writeFileSync("memberlist.json", JSON.stringify(memberlist));
}
var bot = new Bot({ var bot = new Bot({
emitEvents: false, emitEvents: false,
@@ -18,29 +40,60 @@ await bot.setChatPreference(IncomingChatPreference.All);
bot.on("message", async message => { bot.on("message", async message => {
console.debug(new Date().toISOString(), message.senderDid, message.text);
try { try {
var sender = await message.getSender(); var sender = await message.getSender();
var conversation = await message.getConversation(); var conversation = await message.getConversation();
if (!conversation) { console.error("missing conversation"); } var respond = text => sendMessage({conversationId: conversation.id, text});
if (message.text.startsWith('/')) { if (message.text.startsWith('/')) {
let respond = text => conversation.sendMessage({text}); let args = message.text.split(' ');
let cmd = message.text.split(' ')[0].slice(1).toLowerCase(); let cmd = args[0].slice(1).toLowerCase();
let commandList = `/list, /leave, /join, /ping`; let commandList = `/list, /leave, /join, /invite, /ping`;
switch(cmd) { switch(cmd) {
case "ping": case "ping":
await respond("pong"); await respond("pong");
return; return;
case "list": case "list":
let {conversations} = await bot.listConversations(); let memberConversations = await Promise.all(memberlist.map(convoid => bot.getConversation(convoid)));
await respond(`${conversations.length} members in group chat: ${conversations.map(c => '@' + c.members.find(m => m.did != bot.profile.did).handle).join(', ')}`); await respond(`${memberConversations.length} members in group chat: ${memberConversations.map(c => {
let handle = c.members.find(m => m.did != bot.profile.did).handle;
if (handle != "missing.invalid") { // work around https://github.com/skyware-js/bot/issues/19
handle = `@${handle}`;
}
return handle;
}).join(', ')}`);
return; return;
case "leave": case "leave":
await respond(`You left the group chat and will no longer receive messages! Send any message to join again, or send /join to re-join silently.`); broadcast(new RichText().addMention(sender.displayName || sender.handle || message.senderDid, message.senderDid).addText(" left the group chat.")).catch(console.error);
await conversation.leave(); memberlist = memberlist.filter(convoid => convoid != conversation.id);
saveMemberlist();
await respond(`You left the group chat. Rejoin at any time with /join`);
return; return;
case "join": case "join":
await respond(`You joined the group chat`); if (memberlist.includes(conversation.id)) {
await respond(`You're already in the group chat.`);
} else {
broadcast(new RichText().addMention(sender.displayName || sender.handle || message.senderDid, message.senderDid).addText(" joined the group chat.")).catch(console.error);
memberlist.push(conversation.id);
saveMemberlist();
await respond(`Welcome to the group chat; you are now chatting with ${memberlist.length} other people. Use /list to see them.`);
}
return;
case "invite":
if (!memberlist.includes(conversation.id)) {
await respond(`You are not allowed to invite people to the group chat when you are not in the group chat yourself. Use /join first.`);
return;
}
let handle = args[1];
if (!handle) {
await respond(`Usage: /invite <handle>`);
return;
}
handle = handle.replace('@','');
let profile = await bot.getProfile(handle);
await profile.sendMessage({text: `@${sender.handle} invited you to the group chat! Use /join to accept!`});
await broadcast(new RichText().addMention(sender.displayName || sender.handle || message.senderDid, message.senderDid).addText(" invited ").addMention(profile.displayName||profile.handle,profile.did).addText(" to the group chat."));
return; return;
case "help": case "help":
await respond(`Commands: ${commandList}`); await respond(`Commands: ${commandList}`);
@@ -51,7 +104,12 @@ bot.on("message", async message => {
} }
} }
var logline = `${message.sentAt.toISOString()},${message.senderDid},${sender.displayName},${sender.handle},${message.text}`; if (!memberlist.includes(conversation.id)) {
await respond("Use /join to join the group chat.");
return;
}
var logline = `${message.sentAt.toISOString()},${message.senderDid},${sender.displayName},${sender.handle},${JSON.stringify(message.text)}`;
console.log(logline); console.log(logline);
appendFileSync(`chatlog.csv`, logline + '\n'); appendFileSync(`chatlog.csv`, logline + '\n');
@@ -81,20 +139,17 @@ bot.on("message", async message => {
}] }]
}); });
var {conversations} = await bot.listConversations(); var otherconvoids = memberlist.filter(x => x != conversation.id);
var otherConservations = conversations.filter(c => c.id != conversation?.id); var messages = otherconvoids.map(conversationId => ({
//otherConservations = otherConservations.filter(c => c.members.length > 1); // doesn't work, bsky doesnt let you know when they leave? conversationId,
var messages = otherConservations.map(c => ({
conversationId: c.id,
text, text,
facets facets,
embed: message.embed
})); }));
await sendMessages(messages, {resolveFacets: false});
await bot.sendMessages(messages, {resolveFacets: false});
} catch (error) { } catch (error) {
console.error(error); console.error(error);
conversation?.sendMessage({text: error.message}).catch(error => console.error(error)); if (conversation) sendMessage({conversationId: conversation.id, text: `${error.message}\ncause: ${error.cause.message}`}).catch(error => console.error(error));
} }
}); });
@@ -102,4 +157,63 @@ bot.on("message", async message => {
async function broadcast(text) {
var logline = `${new Date().toISOString()},,,,${JSON.stringify(text.text||text)}`;
console.log(logline);
appendFileSync(`chatlog.csv`, logline + '\n');
var messages = memberlist.map(conversationId => ({
conversationId,
text
}));
await sendMessages(messages);
}
// presere order
var queue = [];
var ratelimited = false;
function sendMessages(messages, options) {
queue.push([messages, options]);
if (!ratelimited) return flushQueue();
}
async function flushQueue() {
if (!queue.length) return;
var [messages, options] = queue.shift();
try {
await bot.sendMessages(messages, options);
ratelimited = false;
} catch (error) {
if (error.cause?.kind == "RateLimitExceeded") {
console.warn("ratelimited");
ratelimited = true;
queue.unshift([messages, options]);
} else throw error;
}
}
(function flushQueueLoop() {
flushQueue().catch(console.error).then(() => {
setTimeout(flushQueueLoop, 10000);
});
})();
// no preserve order
async function sendMessage(message, options) {
try {
return await bot.sendMessage(message, options);
} catch (error) {
if (error.cause?.kind == "RateLimitExceeded") {
console.warn("ratelimited");
await new Promise(r => setTimeout(r, 10000));
return await sendMessage(message, options);
} else throw error;
}
}
global.bot = bot; global.bot = bot;
+11 -5
View File
@@ -5,8 +5,9 @@
"packages": { "packages": {
"": { "": {
"dependencies": { "dependencies": {
"@skyware/bot": "^0.3.7-beta.1", "@skyware/bot": "^0.3.7",
"dotenv": "^16.4.5" "dotenv": "^16.4.5",
"fetch-retry": "^6.0.0"
} }
}, },
"node_modules/@atcute/base32": { "node_modules/@atcute/base32": {
@@ -93,9 +94,9 @@
"optional": true "optional": true
}, },
"node_modules/@skyware/bot": { "node_modules/@skyware/bot": {
"version": "0.3.7-beta.1", "version": "0.3.7",
"resolved": "https://registry.npmjs.org/@skyware/bot/-/bot-0.3.7-beta.1.tgz", "resolved": "https://registry.npmjs.org/@skyware/bot/-/bot-0.3.7.tgz",
"integrity": "sha512-flH5DzHKBuLfSSJsHguh3ZcsTaillUo5sq1llrXEU6xY9Btwe2B97dvlvTNdDOHJeyXse5Mlwyq5CE4aeF/xHw==", "integrity": "sha512-27k7r4/YA+h8FobXokMa3iv8fm/850a7NmajOHP3/CaMbCYicXXBDnAIY4kIPP58Zl/A/JvdcFLpQBpfFkiQHQ==",
"dependencies": { "dependencies": {
"@atcute/bluesky": "^1.0.7", "@atcute/bluesky": "^1.0.7",
"@atcute/bluesky-richtext-builder": "^1.0.1", "@atcute/bluesky-richtext-builder": "^1.0.1",
@@ -156,6 +157,11 @@
"url": "https://github.com/sponsors/mysticatea" "url": "https://github.com/sponsors/mysticatea"
} }
}, },
"node_modules/fetch-retry": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-6.0.0.tgz",
"integrity": "sha512-BUFj1aMubgib37I3v4q78fYo63Po7t4HUPTpQ6/QE6yK6cIQrP+W43FYToeTEyg5m2Y7eFUtijUuAv/PDlWuag=="
},
"node_modules/partysocket": { "node_modules/partysocket": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/partysocket/-/partysocket-1.0.2.tgz", "resolved": "https://registry.npmjs.org/partysocket/-/partysocket-1.0.2.tgz",
+3 -2
View File
@@ -1,6 +1,7 @@
{ {
"dependencies": { "dependencies": {
"@skyware/bot": "^0.3.7-beta.1", "@skyware/bot": "^0.3.7",
"dotenv": "^16.4.5" "dotenv": "^16.4.5",
"fetch-retry": "^6.0.0"
} }
} }