import 'dotenv/config';
import { stripHtml } from "string-strip-html";
import { LabelerServer } from "@skyware/labeler";
import { declareLabeler, getLabelerLabelDefinitions } from '@skyware/labeler/scripts';
import WebSocket from 'ws';
import { CronJob } from 'cron';
const server = new LabelerServer({
did: process.env.LABELER_DID ?? '',
signingKey: process.env.SIGNING_KEY ?? ''
});
const credentials = {
pds: process.env.BSKY_PDS ?? '',
identifier: process.env.LABELER_DID ?? '',
password: process.env.WAFRN_PASSWORD ?? ''
}
export class WafrnApi {
_token = '';
_instance = '';
_recentPosts = [];
constructor() { }
async init(server, email, password) {
this._instance = server;
const res = await fetch(`https://${server}/api/login`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
email: email,
password: password
})
})
if (!res.ok) {
throw new Error('Could not sign in, ' + await res.text());
}
const json = await res.json();
if (!json.success || !res.ok) {
throw new Error('Could not sign in, ' + await res.text());
}
this._token = json.token;
this.loadWs();
new CronJob(
'*/1 * * * *',
() => {
this.checkIfMatches()
},
null,
true,
'America/Los_Angeles'
);
}
loadWs() {
const ws = new WebSocket(`wss://${this._instance}/api/notifications/socket`);
ws.on('open', () => {
ws.send(JSON.stringify({
type: "auth",
object: this._token
}))
console.log('connected');
})
ws.on('message', async (msg) => {
const message = msg.toString();
const msgJson = JSON.parse(message);
console.log(msgJson);
if (msgJson.type === "MENTION") {
setTimeout(() => this.checkIfMatches(), 15000);
}
})
ws.on('close', () => {
console.log('reconnecting in 10s...');
setTimeout(() => { this.loadWs(); }, 10000);
})
}
async checkIfMatches() {
const notifs = await this.getNotifs();
const firstNotif = notifs.notifications[0];
if (this._recentPosts.length === 0) this._recentPosts.push(`${firstNotif.userId}${firstNotif.postId}`);
if (this._recentPosts.includes(`${firstNotif.userId}${firstNotif.postId}`)) return;
for (let i = 0; i < notifs.notifications.length; i++) {
console.log(this._recentPosts);
const thisNotif = notifs.notifications[i];
if (this._recentPosts.includes(`${thisNotif.userId}${thisNotif.postId}`)) return;
else this._recentPosts.push(`${thisNotif.userId}${thisNotif.postId}`);
const postNotif = await this.getPost(thisNotif.postId);
const mdContent = postNotif.posts[0].markdownContent ?? stripHtml(postNotif.posts[0].content).result;
const contSpl = mdContent.split(' ');
const mention = contSpl[0];
const rest = '' + contSpl.slice(1).join(' ').toLowerCase();
const restSpl = rest.split(' ');
const command = '' + restSpl[0];
const args = '' + restSpl.slice(1).join(' ').toLowerCase();
let user = thisNotif.user.url;
let u2 = user;
if (!user.startsWith('@')) user = '@' + user;
if (!user.replace(/^\@/, '').includes('@')) user = user + '@wf.jbc.lol'
console.log(contSpl, mention, rest);
console.log(postNotif);
console.log(thisNotif);
if (postNotif.posts[0].privacy !== 10) {
return;
}
console.log(mention, command, args);
if (command === 'is-wafrn') {
const ifWafrn = await this.checkIfWafrn(!!args ? args : user);
await this.send("", `${!!args ? args + ' is' : 'You are'} ${ifWafrn.isWafrn ? 'in' : 'not in'} a Wafrn instance (${ifWafrn.httpError ? 'either not a Fediverse instance or HTTP/fetch error getting nodeinfo, try again later' : `${ifWafrn.software.name} ${ifWafrn.software.version}`})`, postNotif.posts[0].privacy, "", postNotif.users.map(t => t.id), postNotif.posts[0].id);
console.log(`sent post in woot ${postNotif.posts[0].id}`);
return;
} else if (command === 'setup') {
const ifWafrn = await this.checkIfWafrn(user);
if (!ifWafrn.isWafrn) {
await this.send("", 'You are not in a Wafrn instance.', postNotif.posts[0].privacy, "", postNotif.users.map(t => t.id), postNotif.posts[0].id);
return;
}
const res = await fetch(`https://${ifWafrn.instHost}/api/user?id=${ifWafrn.name}`, {
method: 'GET'
})
if (!res.ok) {
console.log(await res.text());
await this.send("", 'There is an error while getting your user info. You might want to wait a bit. You also need to make sure your profile is visible to public as it\'s the only way Wadge can detect it, sadly, but you can just reinstate it later.', postNotif.posts[0].privacy, "", postNotif.users.map(t => t.id), postNotif.posts[0].id);
return;
}
const resJson = await res.json();
console.log(resJson);
const bskyDid = await resJson.bskyDid;
if (!bskyDid) {
await this.send("", `You don't have Bluesky enabled. Enable it on https://${ifWafrn.instHost}/settings/account or import your existing one on https://${ifWafrn.instHost}/profile/migrate-bluesky`, postNotif.posts[0].privacy, "", postNotif.users.map(t => t.id), postNotif.posts[0].id);
return;
}
function onlyUnique(value, index, array) {
return array.indexOf(value) === index;
}
try {
const extLabels = await getLabelerLabelDefinitions(credentials);
if (!extLabels.find(x => x.identifier === ifWafrn.instHost?.toLowerCase().replaceAll('.', '-'))) {
const labels = [
...(extLabels ?? []),
{
identifier: ifWafrn.instHost?.toLowerCase().replaceAll('.', '-'),
name: ifWafrn.instHost,
description: `Main account of this Bluesky user is on ${ifWafrn.instHost} Wafrn instance`,
adultOnly: false,
severity: 'inform',
blurs: "none",
defaultSetting: "warn",
locales: [{ lang: "en", name: ifWafrn.instHost, description: `Main account of this Bluesky user is on ${ifWafrn.instHost} Wafrn instance` }]
}
]
await declareLabeler(
credentials,
labels.filter(onlyUnique),
true
)
}
let label = await server.createLabel({
uri: bskyDid,
val: ifWafrn.instHost?.toLowerCase().replaceAll('.', '-')
})
console.log(label);
await this.send("", `Done! Added your instance on your account label list! Next up is to like the bot to actually see it!`, postNotif.posts[0].privacy, "", postNotif.users.map(t => t.id), postNotif.posts[0].id);
console.log('added user to list');
return;
} catch (e) {
console.error(e);
await this.send("", `An error occured, pinging @jbcrn\n\`\`\`\n${e}\n\`\`\``, postNotif.posts[0].privacy, "", postNotif.users.map(t => t.id), postNotif.posts[0].id);
return;
}
}
await this.send("", 'Set up by mentioning this bot and adding `setup`!', postNotif.posts[0].privacy, "", postNotif.users.map(t => t.id), postNotif.posts[0].id);
}
}
async saveMembers() {
writeFileSync('./members.json', JSON.stringify(WAFRING_JSON));
}
async checkIfWafrn(uname) {
const nameSpl = uname.split('@');
const prov = nameSpl[2] ?? 'wf.jbc.lol';
console.log(prov)
const nfRes = await fetch(`https://${prov}/.well-known/nodeinfo`);
if (!nfRes.ok) return {
isWafrn: false,
httpError: true
};
const link = await nfRes.json();
console.log(link);
const nfInst = await fetch(link.links[0].href);
if (!nfInst.ok) return {
isWafrn: false,
httpError: true
};
const nfJson = await nfInst.json();
if (nfJson.software.name.toLowerCase() !== 'wafrn') return {
isWafrn: false,
instHost: prov,
name: nameSpl[1],
software: nfJson.software,
metadata: nfJson.metadata
};
return {
isWafrn: true,
instHost: prov,
name: nameSpl[1],
software: nfJson.software,
metadata: nfJson.metadata
};
}
async send(username, html, type = 10, warning = '', mentions = [], parent = '') {
if (!this._token) {
throw new Error('Please log in first');
}
const res = await fetch(`https://${this._instance}/api/v3/createPost`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${this._token}`
},
body: JSON.stringify({
content: `${!!username ? `@${username}\n\n` : ''}${html}`,
"content_warning": warning,
medias: [],
mentionedUserIds: mentions,
parent: parent,
privacy: type,
tags: ""
})
})
if (!res.ok) {
throw new Error('Could not create dm');
}
}
async getNotifs() {
const res = await fetch(`https://${this._instance}/api/v3/notificationsScroll?date=${new Date().getTime()}&page=0`, {
method: 'GET',
headers: {
Authorization: `Bearer ${this._token}`
},
});
if (!res.ok) {
throw new Error('Could not get notifs');
}
const resJson = await res.json();
return resJson;
}
async getPost(postId) {
const res = await fetch(`https://${this._instance}/api/v2/post/${postId}`, {
method: 'GET',
headers: {
Authorization: `Bearer ${this._token}`
},
});
const text = await res.text();
if (!res.ok || !text) {
throw new Error('Could not get post, ' + text);
}
let resJson = JSON.parse(text);
return resJson;
}
}
const w = new WafrnApi();
w.init(process.env.WAFRN_INSTANCE ?? 'app.wafrn.net', process.env.WAFRN_EMAIL ?? '', process.env.WAFRN_PASSWORD ?? '');
server.start(2343, (error, address) => {
if (error) {
console.error("Failed to start server:", error);
} else {
console.log("Labeler server running on port", address);
}
if (!!process.env.LABEL_ITSELF) {
setTimeout(async () => {
let label = await server.createLabel({
uri: process.env.LABELER_DID,
val: process.env.WAFRN_INSTANCE?.toLowerCase().replaceAll('.', '-')
})
console.log('done');
}, 2000)
}
});