This commit is contained in:
Ashley Graves 2024-10-13 13:35:10 +02:00
parent 6357080619
commit 880ae5a359
8 changed files with 232 additions and 138 deletions

View file

@ -1,9 +1,10 @@
PREFIX=!
BASE_URL=https://example.com
OWNER_ID=@root:example.com
USER_ID=@bot:example.com
ACCESS_TOKEN=1234567890
DEVICE_ID=Bot-Device
PREFIX=!
LOG_CHANNEL=!1234567890:example.com
AP_FETCH_PORT=3000
AP_FETCH_DOMAIN=example.com
OWNER_ID=@nyx:nyx.ftp.sh

View file

@ -1,15 +1,46 @@
import { exec as ossl_exec } from "openssl-wrapper";
import express from 'express';
import fs from 'fs';
import env from 'dotenv';
env.config();
const password = "penis";
function openssl(func, args) {
return new Promise((resolve, reject) => {
ossl_exec(func, args, function (err, buffer) {
if (err)
reject(err);
resolve(buffer);
});
})
}
const domain = process.env.AP_FETCH_DOMAIN;
const pubkey = fs.readFileSync('data/publickey.crt', 'utf8');
if (!fs.existsSync("data/server-crt.pem")) {
console.log("Generating certificate...");
await openssl('req', {
new: true,
newkey: 'rsa:4096',
days: 365,
nodes: true,
x509: true,
subj: '/C=',
keyout: "data/server-key.pem",
out: "data/server-crt.pem"
});
console.log("Certificate generated!");
}
const app = express();
const pubkey = fs.readFileSync('data/server-crt.pem', 'utf8');
const notice = fs.readFileSync('auth-fetch-notice.txt', 'utf8');
const userId = process.env.USER_ID;
const username = userId.slice(1, userId.indexOf(":"));
const actor = {
'@context': [
@ -19,7 +50,7 @@ const actor = {
'id': 'https://' + domain + '/actor',
'type': 'Person',
'preferredUsername': 'possumbot',
'preferredUsername': username,
'inbox': 'https://' + domain + '/inbox',
'publicKey': {
@ -30,7 +61,7 @@ const actor = {
}
const webfinger = {
'subject': 'acct:possumbot@' + domain + '',
'subject': 'acct:' + userId,
'links': [
{
@ -41,24 +72,24 @@ const webfinger = {
]
}
app.get('/', (req, res) => {
res.setHeader('content-type', 'text/plain');
res.write(notice);
res.end();
app.get('/', (_, res) => {
res.setHeader('content-type', 'text/plain');
res.write(notice);
res.end();
});
app.get('/actor', (req, res) => {
res.writeHead(200, { 'Content-Type': 'application/activity+json' });
res.write(JSON.stringify(actor));
res.end();
app.get('/actor', (_, res) => {
res.writeHead(200, { 'Content-Type': 'application/activity+json' });
res.write(JSON.stringify(actor));
res.end();
});
app.get('/.well-known/webfinger', (req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.write(JSON.stringify(webfinger));
res.end();
app.get('/.well-known/webfinger', (_, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.write(JSON.stringify(webfinger));
res.end();
});
app.listen(process.env.AP_FETCH_PORT);
export default true;
export default function (port) {
app.listen(port);
};

View file

@ -4,7 +4,6 @@ import * as sdk from "matrix-js-sdk";
import sdkExt from "./lib/ext.js";
import { resolve } from "node:path";
import fs from "node:fs";
import util from "util";
import { LocalStorage } from 'node-localstorage';
import fetch from "node-fetch";
@ -31,6 +30,8 @@ const client = sdk.createClient({
sdkExt(client);
apfetch(process.env.AP_FETCH_PORT);
var prefixes = [process.env.PREFIX];
client.initialized = false;
@ -39,7 +40,7 @@ client.commands = [];
for (const file of fs.readdirSync(resolve("modules"))) {
try {
if(file.startsWith(".")) continue;
if (file.startsWith(".")) continue;
var module = (await import(resolve("modules", file))).default;
client.modules.push(module);
client.log("[load:modules]", `loaded ${module.name}`);
@ -50,7 +51,7 @@ for (const file of fs.readdirSync(resolve("modules"))) {
for (const file of fs.readdirSync(resolve("commands"))) {
try {
if(file.startsWith(".")) continue;
if (file.startsWith(".")) continue;
var command = (await import(resolve("commands", file))).default;
command.usage = process.env.PREFIX + command.command + (command.usage ? " " + command.usage : '');
client.commands.push(command);
@ -62,14 +63,14 @@ for (const file of fs.readdirSync(resolve("commands"))) {
function doModule(client, event) {
client.modules.forEach(m => {
if(!m) return;
if (!m) return;
var config = client.config.get(event.sender.roomId);
if(config.get(m.name) === false) return;
if (config.get(m.name) === false) return;
try {
m.hooks?.message(client, event);
} catch(err) {
} catch (err) {
client.log("[hook]", err);
}
});
@ -77,32 +78,32 @@ function doModule(client, event) {
function doCommand(client, event, cmd, args) {
var command;
command = client.commands.filter(c=>c.command==cmd)[0];
if(!command)
command = client.commands.filter(c => c.command == cmd)[0];
if (!command)
return false;
if((command.owner && event.sender.userId != process.env.OWNER_ID) || (command.minPwr && event.sender.powerLevel < command.minPwr && event.sender.userId != process.env.OWNER_ID)) {
if ((command.owner && event.sender.userId != process.env.OWNER_ID) || (command.minPwr && event.sender.powerLevel < command.minPwr && event.sender.userId != process.env.OWNER_ID)) {
var addl = command.minPwr ? `this command requires a power level of ${command.minPwr}\nyour power level is ${event.sender.powerLevel}` : "this command is owner-only.";
client.reply(event, addl, '<img src="mxc://possum.city/b4Vo1BTcq49B7TbFWCqq76HQWQEdNIqq" alt="nuh uh" /><br>' + addl.replaceAll("\n", "<br>"));
client.reply(event, addl, '<img src="mxc://nyx.ftp.sh/2kAXPyosdaz1M6Fv8t2V9SyxcJZEscdR" alt="nuh uh" /><br>' + addl.replaceAll("\n", "<br>"));
return true;
}
try {
command.execute(client, event, args);
} catch(err) {
} catch (err) {
client.log("[cmd]", err);
}
return true;
}
client.once("sync", async function (state, prevState, data) {
if(state != "PREPARED") return;
client.setGlobalErrorOnUnknownDevices(false);
client.name = client._store.users[client.credentials.userId].displayName;
client.log("[sdk:sync]", "connected:", client.name);
prefixes.push(client.name + ": ");
prefixes.push(client.name + " ");
client.initialized = true;
if (state != "PREPARED") return;
client.setGlobalErrorOnUnknownDevices(false);
client.name = client._store.users[client.credentials.userId].displayName;
client.log("[sdk:sync]", "connected:", client.name);
prefixes.push(client.name + ": ");
prefixes.push(client.name + " ");
client.initialized = true;
});
client.on(sdk.RoomEvent.Timeline, async function (event, room, toStartOfTimeline) {
@ -114,22 +115,22 @@ client.on(sdk.RoomEvent.Timeline, async function (event, room, toStartOfTimeline
var content = event.getContent();
if(content["m.relates_to"] !== undefined)
if (content["m.relates_to"] !== undefined)
content.body = content.body.substring(content.body.indexOf("\n\n") + 2);
event.sender = client.getRoom(event.sender.roomId).getMember(event.getSender());
var content = content.body;
var isCommand = false;
for(const prefix of prefixes) {
if(content.startsWith(prefix)) {
for (const prefix of prefixes) {
if (content.startsWith(prefix)) {
isCommand = true;
content = content.substring(prefix.length);
break;
}
}
if(!isCommand) {
if (!isCommand) {
doModule(client, event);
} else {
var args = content.split(/\s/g);

View file

@ -1,5 +1,4 @@
import httpSignature from "@peertube/http-signature";
import path from "node:path";
import fs from "node:fs";
const FRIENDLY_USERAGENT = "PossumBot/1.0 (+https://bot.possum.city/)";
@ -23,13 +22,13 @@ domainCache.set("cohost.org", "cohost"); // no nodeinfo
async function resolvePlatform(url) {
const urlObj = new URL(url);
if(domainCache.has(urlObj.hostname)) return domainCache.get(urlObj.hostname);
if (domainCache.has(urlObj.hostname)) return domainCache.get(urlObj.hostname);
const res = await fetch(urlObj.origin + "/.well-known/nodeinfo", {
headers: {"User-Agent": FRIENDLY_USERAGENT},
headers: { "User-Agent": FRIENDLY_USERAGENT },
}).then((res) => res.text());
if(!res.startsWith("{")) {
if (!res.startsWith("{")) {
console.error("[fedimbed]", `No nodeinfo for "${urlObj.hostname}"???`);
domainCache.set(urlObj.hostname, null);
return null;
@ -37,17 +36,17 @@ async function resolvePlatform(url) {
const probe = JSON.parse(res);
if(!probe?.links) {
if (!probe?.links) {
console.error("[fedimbed]", `No nodeinfo for "${urlObj.hostname}"???`);
domainCache.set(urlObj.hostname, null);
return null;
}
const nodeinfo = await fetch(probe.links[probe.links.length - 1].href, {
headers: {"User-Agent": FRIENDLY_USERAGENT},
headers: { "User-Agent": FRIENDLY_USERAGENT },
}).then((res) => res.json());
if(!nodeinfo?.software?.name) {
if (!nodeinfo?.software?.name) {
console.error("[fedimbed]", `Got nodeinfo for "${urlObj.hostname}", but missing software name.`);
domainCache.set(urlObj.hostname, null);
return null;
@ -58,7 +57,7 @@ async function resolvePlatform(url) {
}
const keyId = "https://" + process.env.AP_FETCH_DOMAIN + "/actor#main-key";
const privKey = fs.readFileSync("data/private.pem");
const privKey = fs.readFileSync("data/server-crt.pem");
async function signedFetch(url, options) {
const urlObj = new URL(url);
@ -69,20 +68,17 @@ async function signedFetch(url, options) {
const headerNames = ["(request-target)", "host", "date"];
httpSignature.sign(
{
getHeader: (name) => headers[name.toLowerCase()],
setHeader: (name, value) => (headers[name] = value),
method: options.method ?? "GET",
path: urlObj.pathname,
},
{
keyId,
key: privKey,
headers: headerNames,
authorizationHeaderName: "signature",
}
);
httpSignature.sign({
getHeader: (name) => headers[name.toLowerCase()],
setHeader: (name, value) => (headers[name] = value),
method: options.method ?? "GET",
path: urlObj.pathname,
}, {
keyId,
key: privKey,
headers: headerNames,
authorizationHeaderName: "signature",
});
options.headers = Object.assign(headers, options.headers ?? {});
@ -95,16 +91,16 @@ async function processUrl(url) {
let urlObj;
try {
urlObj = new URL(url);
} catch(err) {
} catch (err) {
console.error("[fedimbed]", err);
invalidUrl = true;
}
if(invalidUrl) return {};
if (invalidUrl) return {};
// some lemmy instances have old reddit frontend subdomains
// but these frontends are just frontends and dont actually expose the API
if(urlObj.hostname.startsWith("old.")) {
if (urlObj.hostname.startsWith("old.")) {
urlObj.hostname = urlObj.hostname.replace("old.", "");
url = urlObj.href;
}
@ -138,7 +134,7 @@ async function processUrl(url) {
} catch (err) {
console.error("[fedimbed]", `Failed to signed fetch "${url}", retrying unsigned: ${err}`);
}
if(!rawPostData) {
if (!rawPostData) {
try {
rawPostData = await fetch(url, {
headers: {
@ -152,7 +148,7 @@ async function processUrl(url) {
}
let postData;
if(rawPostData?.startsWith("{")) {
if (rawPostData?.startsWith("{")) {
try {
postData = JSON.parse(rawPostData);
} catch (err) {
@ -162,18 +158,18 @@ async function processUrl(url) {
console.warn("[fedimbed]", `Got non-JSON for "${url}": ${rawPostData}`);
}
if(postData?.error) {
if (postData?.error) {
console.error("[fedimbed]", `Received error for "${url}": ${postData.error}`);
console.error("[fedimbed]", postData);
return postData;
}
if(!postData) {
if (!postData) {
// We failed to get post.
// Assume it was due to AFM or forced HTTP signatures and use MastoAPI
// Follow redirect from /object since we need the ID from /notice
if(PATH_REGEX.pleroma.test(urlObj.pathname)) {
if (PATH_REGEX.pleroma.test(urlObj.pathname)) {
url = await signedFetch(url, {
method: "HEAD",
headers: {
@ -181,7 +177,7 @@ async function processUrl(url) {
},
redirect: "manual",
}).then((res) => res.headers.get("location"));
if(url.startsWith("/")) {
if (url.startsWith("/")) {
url = urlObj.origin + url;
}
urlObj = new URL(url);
@ -190,32 +186,32 @@ async function processUrl(url) {
let redirUrl;
const options = {};
const headers = {};
if(PATH_REGEX.pleroma2.test(urlObj.pathname)) {
if (PATH_REGEX.pleroma2.test(urlObj.pathname)) {
redirUrl = url.replace("notice", "api/v1/statuses");
} else if(PATH_REGEX.mastodon.test(urlObj.pathname)) {
} else if (PATH_REGEX.mastodon.test(urlObj.pathname)) {
const postId = urlObj.pathname.match(PATH_REGEX.mastodon)?.[2];
redirUrl = urlObj.origin + "/api/v1/statuses/" + postId;
} else if(PATH_REGEX.mastodon2.test(urlObj.pathname)) {
} else if (PATH_REGEX.mastodon2.test(urlObj.pathname)) {
redirUrl = url.replace(/^\/(.+?)\/statuses/, "/api/v1/statuses");
} else if(PATH_REGEX.misskey.test(urlObj.pathname)) {
} else if (PATH_REGEX.misskey.test(urlObj.pathname)) {
let noteId = url.split("/notes/")[1];
if(noteId.indexOf("/") > -1) {
if (noteId.indexOf("/") > -1) {
noteId = noteId.split("/")[0];
} else if(noteId.indexOf("?") > -1) {
} else if (noteId.indexOf("?") > -1) {
noteId = noteId.split("?")[0];
} else if(noteId.indexOf("#") > -1) {
} else if (noteId.indexOf("#") > -1) {
noteId = noteId.split("#")[0];
}
console.log("[fedimbed]", "Misskey post ID: " + noteId);
redirUrl = urlObj.origin + "/api/notes/show/";
options.method = "POST";
options.body = JSON.stringify({noteId});
options.body = JSON.stringify({ noteId });
headers["Content-Type"] = "application/json";
} else {
console.error("[fedimbed]", `Missing MastoAPI replacement for "${platform}"`);
}
if(redirUrl) {
if (redirUrl) {
console.log(
"[fedimbed]",
`Redirecting "${url}" to "${redirUrl}": ${JSON.stringify(options)}, ${JSON.stringify(headers)}`
@ -233,7 +229,7 @@ async function processUrl(url) {
} catch (err) {
console.error("[fedimbed]", `Failed to signed fetch "${url}" via MastoAPI, retrying unsigned: ${err}`);
}
if(!rawPostData2) {
if (!rawPostData2) {
try {
rawPostData2 = await signedFetch(
redirUrl,
@ -249,15 +245,15 @@ async function processUrl(url) {
}
let postData2;
if(rawPostData2?.startsWith("{")) {
if (rawPostData2?.startsWith("{")) {
postData2 = JSON.parse(rawPostData2);
} else {
console.warn("[fedimbed]", `Got non-JSON for "${url}" via MastoAPI: ${rawPostData2}`);
}
if(!postData2) {
if (!postData2) {
console.warn("[fedimbed]", `Bailing trying to re-embed "${url}": Failed to get post from normal and MastoAPI.`);
} else if(postData2.error) {
} else if (postData2.error) {
console.error(
"[fedimbed]",
`Bailing trying to re-embed "${url}", MastoAPI gave us error: ${JSON.stringify(postData2.error)}`
@ -281,18 +277,18 @@ async function processUrl(url) {
avatar: postData2.account?.avatar ?? postData2.user?.avatarUrl,
};
timestamp = postData2.created_at ?? postData2.createdAt;
emotes = postData2.emojis.filter((x) => !x.name.endsWith("@.")).map((x) => ({name: `:${x.name}:`, url: x.url}));
emotes = postData2.emojis.filter((x) => !x.name.endsWith("@.")).map((x) => ({ name: `:${x.name}:`, url: x.url }));
sensitive = postData2.sensitive;
const attachments = postData2.media_attachments ?? postData2.files;
if(attachments) {
for(const attachment of attachments) {
if (attachments) {
for (const attachment of attachments) {
const contentType = await fetch(attachment.url, {
method: "HEAD",
}).then((res) => res.headers.get("Content-Type"));
if(contentType) {
if(contentType.startsWith("image/") || contentType.startsWith("video/") || contentType.startsWith("audio/")) {
if (contentType) {
if (contentType.startsWith("image/") || contentType.startsWith("video/") || contentType.startsWith("audio/")) {
files.push({
url: attachment.url,
desc: attachment.description ?? attachment.comment,
@ -306,13 +302,13 @@ async function processUrl(url) {
attachment.pleroma?.mime_type ?? type.indexOf("/") > -1
? type
: type +
"/" +
(url.match(/\.([a-z0-9]{3,4})$/)?.[0] ?? type == "image"
? "png"
: type == "video"
"/" +
(url.match(/\.([a-z0-9]{3,4})$/)?.[0] ?? type == "image"
? "png"
: type == "video"
? "mp4"
: "mpeg");
if(type.startsWith("image") || type.startsWith("video") || type.startsWith("audio")) {
if (type.startsWith("image") || type.startsWith("video") || type.startsWith("audio")) {
files.push({
url: attachment.url,
desc: attachment.description ?? attachment.comment,
@ -323,11 +319,11 @@ async function processUrl(url) {
}
}
if(postData2.sensitive && attachments.length > 0) {
if (postData2.sensitive && attachments.length > 0) {
spoiler = true;
}
if(postData2.poll) {
if (postData2.poll) {
poll = {
end: new Date(postData2.poll.expires_at),
total: postData2.poll.votes_count,
@ -340,9 +336,9 @@ async function processUrl(url) {
}
}
} else {
if(postData.id) {
if (postData.id) {
const realUrlObj = new URL(postData.id);
if(realUrlObj.origin != urlObj.origin) {
if (realUrlObj.origin != urlObj.origin) {
platform = await resolvePlatform(postData.id);
platformName = platform.replace("gotosocial", "GoToSocial").replace(/^(.)/, (_, c) => c.toUpperCase());
url = postData.id;
@ -354,32 +350,32 @@ async function processUrl(url) {
timestamp = postData.published;
sensitive = postData.sensitive;
if(postData.tag) {
if (postData.tag) {
let tag = postData.tag;
// gts moment
if(!Array.isArray(tag)) tag = [tag];
emotes = tag.filter((x) => !!x.icon).map((x) => ({name: x.name, url: x.icon.url}));
if (!Array.isArray(tag)) tag = [tag];
emotes = tag.filter((x) => !!x.icon).map((x) => ({ name: x.name, url: x.icon.url }));
}
// NB: gts doesnt send singular attachments as array
const attachments = Array.isArray(postData.attachment) ? postData.attachment : [postData.attachment];
for(const attachment of attachments) {
if(!attachment) continue;
if(attachment.mediaType) {
if(attachment.mediaType.startsWith("video/") || attachment.mediaType.startsWith("image/") || attachment.mediaType.startsWith("audio/")) {
for (const attachment of attachments) {
if (!attachment) continue;
if (attachment.mediaType) {
if (attachment.mediaType.startsWith("video/") || attachment.mediaType.startsWith("image/") || attachment.mediaType.startsWith("audio/")) {
files.push({
url: attachment.url,
desc: attachment.name ?? attachment.description ?? attachment.comment,
type: attachment.mediaType,
});
}
} else if(attachment.url) {
} else if (attachment.url) {
const contentType = await fetch(attachment.url, {
method: "HEAD",
}).then((res) => res.headers.get("Content-Type"));
if(contentType) {
if(contentType.startsWith("image/") || contentType.startsWith("video/") || contentType.startsWith("audio/")) {
if (contentType) {
if (contentType.startsWith("image/") || contentType.startsWith("video/") || contentType.startsWith("audio/")) {
files.push({
url: attachment.url,
desc: attachment.name ?? attachment.description ?? attachment.comment,
@ -393,9 +389,9 @@ async function processUrl(url) {
type.indexOf("/") > -1
? type
: type +
"/" +
(url.match(/\.([a-z0-9]{3,4})$/)?.[0] ?? type == "image" ? "png" : type == "video" ? "mp4" : "mpeg");
if(type.startsWith("image") || type.startsWith("video") || type.startsWith("audio")) {
"/" +
(url.match(/\.([a-z0-9]{3,4})$/)?.[0] ?? type == "image" ? "png" : type == "video" ? "mp4" : "mpeg");
if (type.startsWith("image") || type.startsWith("video") || type.startsWith("audio")) {
files.push({
url: attachment.url,
desc: attachment.name ?? attachment.description ?? attachment.comment,
@ -408,11 +404,11 @@ async function processUrl(url) {
}
}
if(postData.sensitive && attachments.length > 0) {
if (postData.sensitive && attachments.length > 0) {
spoiler = true;
}
if(postData.image?.url) {
if (postData.image?.url) {
const imageUrl = new URL(postData.image.url);
const contentType = await fetch(postData.image.url, {
method: "HEAD",
@ -424,7 +420,7 @@ async function processUrl(url) {
});
}
if(postData.name) title = postData.name;
if (postData.name) title = postData.name;
// Author data is not sent with the post with AS2
const authorData = await signedFetch(postData.actor ?? postData.attributedTo, {
@ -436,10 +432,10 @@ async function processUrl(url) {
.then((res) => res.json())
.catch((err) => {
// only posts can be activity+json right now, reduce log spam
if(platform !== "cohost") console.error("[fedimbed]", `Failed to get author for "${url}": ${err}`);
if (platform !== "cohost") console.error("[fedimbed]", `Failed to get author for "${url}": ${err}`);
});
if(authorData) {
if (authorData) {
const authorUrlObj = new URL(authorData.url ?? authorData.id);
author = {
name: authorData.name,
@ -459,7 +455,7 @@ async function processUrl(url) {
};
}
if(postData.endTime && postData.oneOf && postData.votersCount) {
if (postData.endTime && postData.oneOf && postData.votersCount) {
poll = {
end: new Date(postData.endTime),
total: postData.votersCount,
@ -472,7 +468,7 @@ async function processUrl(url) {
}
// We could just continue without author but it'd look ugly and be confusing.
if(!author) {
if (!author) {
console.warn("[fedimbed]", `Bailing trying to re-embed "${url}": Failed to get author.`);
return {};
}
@ -489,7 +485,7 @@ async function processUrl(url) {
//cw = htmlToMarkdown(cw);
let desc = cw;
if((cw != "" || sensitive) && files.length) {
if ((cw != "" || sensitive) && files.length) {
desc += "<span data-mx-spoiler>" + content + "</span>";
} else {
desc = content;
@ -504,9 +500,9 @@ async function processUrl(url) {
title: title ?? user,
author: title
? {
name: user,
url: author.url,
}
name: user,
url: author.url,
}
: null,
footer: {
text: platformName,
@ -517,7 +513,7 @@ async function processUrl(url) {
fields: [],
};
if(poll) {
if (poll) {
baseEmbed.fields.push({
name: "Poll",
value:
@ -541,8 +537,8 @@ async function processUrl(url) {
cw != "" && (files.length > 0)
? `:warning: ${cw} <span data-mx-spoiler>${url}</span>`
: spoiler
? `<span data-mx-spoiler>${url}</span>`
: "",
? `<span data-mx-spoiler>${url}</span>`
: "",
embeds,
files,
emotes
@ -550,9 +546,9 @@ async function processUrl(url) {
}
async function fedimbed(msg) {
if(URLS_REGEX.test(msg)) {
if (URLS_REGEX.test(msg)) {
const urls = msg.match(URLS_REGEX);
for(let url of urls) {
for (let url of urls) {
let urlObj;
try {
urlObj = new URL(url);
@ -560,9 +556,9 @@ async function fedimbed(msg) {
console.error("[fedimbed]", `Invalid URL "${url}"`);
// noop
}
for(const service of Object.keys(PATH_REGEX)) {
for (const service of Object.keys(PATH_REGEX)) {
const regex = PATH_REGEX[service];
if(urlObj && regex.test(urlObj.pathname)) {
if (urlObj && regex.test(urlObj.pathname)) {
console.log("[fedimbed]", `Hit "${service}" for "${url}", processing now.`);
try {
const response = await processUrl(url);

View file

@ -1,5 +1,38 @@
import { LocalStorage } from 'node-localstorage';
import readline from "node:readline/promises";
import { stdin, stdout } from 'node:process';
import * as sdk from "matrix-js-sdk";
import readline from "node:readline";
import Olm from "@matrix-org/olm";
global.Olm = Olm;
const { stdin: input, stdout: output } = require('node:process');
const rl = readline.createInterface({ input, output });
const rl = readline.createInterface({ input: stdin, output: stdout });
const host = await rl.question("Server: ");
const user = await rl.question("Username: ");
const pass = await rl.question("Password: ");
const idnt = await rl.question("Device ID: ");
const userId = `@${user}:${host}`;
const localStorage = new LocalStorage("./data/localstorage");
const cryptoStore = new sdk.LocalStorageCryptoStore(localStorage);
const store = new sdk.MemoryStore({ localStorage });
const client = sdk.createClient({
baseUrl: "https://" + host,
deviceId: idnt,
cryptoStore,
store
});
await client.loginWithPassword(userId, pass);
await client.initCrypto();
await client.startClient();
console.log(`BASE_URL=${client.getHomeserverUrl()}
USER_ID=${client.getUserId()}
ACCESS_TOKEN=${client.getAccessToken()}
DEVICE_ID=${client.getDeviceId()}`);
client.stopClient();
process.exit(0);

View file

@ -6,6 +6,9 @@
"main": "index.js",
"author": "Ashley Graves",
"license": "GPL-v3",
"scripts": {
"login": "node login.js"
},
"dependencies": {
"@matrix-org/olm": "^3.2.15",
"@node-rs/xxhash": "^1.7.3",
@ -17,11 +20,12 @@
"jsdom": "^25.0.0",
"matrix-js-sdk": "^32.4.0",
"node-fetch": "^3.3.2",
"node-localstorage": "^3.0.5"
"node-localstorage": "^3.0.5",
"openssl-wrapper": "^0.3.4"
},
"pnpm": {
"overrides": {
"jsdom>tough-cookie": "^5.0.0-rc.4"
}
}
}
}

28
privkey.pem Normal file
View file

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCscpi8oYpieOQx
rysZVAtDBQHoKuQdtrz4zlLnZoAsWKkCgN5Df/+nxIrvp+yURnrrDPuNqTvxoo/n
mP88cuz8xAH8Gvnc6aZNrLENQwo02LscfxlAh+sXThaYN1qzf+jkJpPcy4sCXYam
7E/O7RYwRBPPw3JgMZgdjgX1IRTHmHUaAlTAcB78FFZIn0p2UGCFHSOrIRSOL8ga
e1s7alOfSqTF92N78lnbprAqxBbqiYTEY4CyTdqKZrbHjSvJcNjxdcifvmC/FIqC
PBmx49FMeUTKyY6Px5/eYcNY/MWjUCIR+65tGLftDJ1hlcZO4w3/qdKDmV/ErTVJ
jY1VQV1ZAgMBAAECggEABK08RIFXsMFOjvpif4FZUiv57UNHLyHG5jMnlGxMiudX
Tgtopl6mSZ/O6h5HuuMJGllzyQJkmceq12u9dZ+Nmx1FtddAHM70BPnrTfdveMIX
8Pc8HBU+OZeTN/WTBCMykbK/d9Fyp6cZq7/k5Nu71PtDqPLH+0vdBZArITFl0DPg
LRhG9oQmzlo9kY19upCKtn7mj/0OCv/p20RU7fjRZJcBTaZ0G6nDJbropZrp0RqK
AlTl+/u52cu2DpAt4EHMy3BJoNw2lk1b+ierEi9XolutO2IFoR9jvzrv3xW0wVom
Zm+Q8F4W5yNr1/lHqSVaBswn3HII9qgTTV+O3LgAAQKBgQDgrK7oyd4E71Uzywls
UhxQPNvGnDK7O13vVkrnyz93aUPXkkZE1EPviaRmXFKNVl1M5tjpIuyOSjl2o2M+
mpxAue526+l9zt28DGa4CyJ5y/frezI95lOaJx03QLfrD6OPXZSpqNvk+ozyPl6s
ZnB9u0avHDW4oWXgTd4+DPy1WQKBgQDEfcbVkkHTyJxiqhV9uax8WhNPxmRbnsft
0VFDSVBmqrwDpjAAlSrXegr44VPabbY8ioq9ExIaGVmihr8VpGkWmSAJIpHw+sjR
ml+r5irl8t+C7cEJJra60uuQ/SevgG9YbgWMXKvuzzYRlhcU8w5vBXccyaMEkMnV
o4HCqczoAQKBgFG4vg5Sjv1AiL9EFPNfkojk+hPt8M4FtA9AVhKl7TnkAhdBT2nt
w6A3cqMA5c4fIeS8+x0h5OWEvg4XNBwrZLZuavy6pr1qJ2ElKZ+7/RhMRqtSrl2x
j6s8mvXkBoHruFSHd3GWyBUrxWS/pvQSdsxk+DrtieTUYBgMetAbLThRAoGADiIl
7TLJ/VvOs+IcDaEPYRpxjSluCpEPPHHz8G0TlW7ueyy3AzO3kyw6IdDDYVDG7O/i
LttyT+JG6kPa9smOrYtyHHYaHUVMsJb0Dr9NkqC3pwlG+0uHlUSaoSE0e5E3cRro
10HLNDA/aWBsZJtfDGlOOgne6fMoMW/DY6cnEAECgYAQV8szcOywT7xxu8nJPJyV
nhcbqvJrPHdHZW01EPTtYRICOd5WFyw/bvULpWNKJm+aV5H+Q5JLFiJyxhJ6+juY
9LuJ1LHhnvEUmnn5ZOmDcjI0RvPZ4hFjIHslAKrizZ8fKdQLBW34c7GGMEHiN4S9
2QyE9tbfBp0u0pDBuR56wQ==
-----END PRIVATE KEY-----

View file

@ -1,4 +1,4 @@
# PossumBot
modular matrix bot :3
see: [wiki](https://git.lgbt/root/possumbot/wiki)
see: [wiki](https://git.lgbt/sisterkissers/matrix-bot/wiki)