mirror of
https://github.com/turbomaster95/coderrrrr.git
synced 2025-08-02 10:42:24 +00:00
Compare commits
No commits in common. "9803161404397c7c69f9316e0b7748251ec18379" and "0995069a74f2b844e522e749c3bf622742e504fe" have entirely different histories.
9803161404
...
0995069a74
@ -1,58 +1,39 @@
|
|||||||
import type { VercelRequest, VercelResponse } from '@vercel/node';
|
import type { VercelRequest, VercelResponse } from '@vercel/node';
|
||||||
|
|
||||||
export default async function (req: VercelRequest, res: VercelResponse) {
|
export default function (req: VercelRequest, res: VercelResponse) {
|
||||||
const { headers } = req;
|
const { headers } = req;
|
||||||
|
|
||||||
if (headers.accept && headers.accept.includes("text/html")) {
|
if ("accept" in headers) {
|
||||||
res.redirect(302, "https://coderrrrr.site/");
|
const accept = headers["accept"];
|
||||||
return;
|
if (accept != null && accept.split(",").indexOf("text/html") > -1) {
|
||||||
|
return res.redirect(302, "https://coderrrrr.site/").end();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(200).setHeader("Content-Type", "application/activity+json").json({
|
res.statusCode = 200;
|
||||||
"@context": ["https://www.w3.org/ns/activitystreams", { "@language": "en-US" }],
|
res.setHeader("Content-Type", `application/activity+json`);
|
||||||
|
res.json({
|
||||||
|
"@context": ["https://www.w3.org/ns/activitystreams", { "@language": "en- US" }],
|
||||||
"type": "Person",
|
"type": "Person",
|
||||||
"id": "https://coderrrrr.site/coder",
|
"id": "https://coderrrrr.site/coder",
|
||||||
"outbox": "https://coderrrrr.site/api/activitypub/outbox",
|
"outbox": "https://coderrrrr.site/outbox",
|
||||||
"following": "https://coderrrrr.site/api/activitypub/following",
|
"following": "https://coderrrrr.site/following",
|
||||||
"followers": "https://coderrrrr.site/api/activitypub/followers",
|
"followers": "https://coderrrrr.site/followers",
|
||||||
"sharedInbox": "https://coderrrrr.site/api/activitypub/sharedInbox",
|
"inbox": "https://coderrrrr.site/inbox",
|
||||||
"inbox": "https://coderrrrr.site/api/activitypub/inbox",
|
|
||||||
"preferredUsername": "coder",
|
"preferredUsername": "coder",
|
||||||
"name": "Deva Midhun's blog",
|
"name": "Deva Midhun's blog",
|
||||||
"discoverable": true,
|
"summary": "",
|
||||||
"indexable": true,
|
|
||||||
"summary": "Software developer & self-hosting enthusiast.",
|
|
||||||
"icon": {
|
"icon": {
|
||||||
"type": "Image",
|
"type": "Image",
|
||||||
"mediaType": "image/png",
|
"mediaType": "image/png",
|
||||||
"url": "https://i.ibb.co/N6J5b8WS/download20250102015611.png"
|
"url": "https://i.ibb.co/N6J5b8WS/download20250102015611.png"
|
||||||
},
|
},
|
||||||
"url": [
|
|
||||||
{
|
|
||||||
"type": "Link",
|
|
||||||
"mediaType": "text/html",
|
|
||||||
"href": "https://coderrrrr.site",
|
|
||||||
"name": "Website"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "Link",
|
|
||||||
"mediaType": "text/html",
|
|
||||||
"href": "https://github.com/turbomaster95",
|
|
||||||
"name": "GitHub"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "Link",
|
|
||||||
"mediaType": "text/html",
|
|
||||||
"href": "https://usr.cloud/@coder",
|
|
||||||
"name": "Owner"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"publicKey": {
|
"publicKey": {
|
||||||
"@context": "https://w3id.org/security/v1",
|
"@context": "https://w3id.org/security/v1",
|
||||||
"@type": "Key",
|
"@type": "Key",
|
||||||
"id": "https://coderrrrr.site/coder#main-key",
|
"id": "https://coderrrrr.site/coder#main-key",
|
||||||
"owner": "https://coderrrrr.site/coder",
|
"owner": "https://coderrrrr.site/coder",
|
||||||
"publicKeyPem": process.env.ACTIVITYPUB_PUBLIC_KEY || "MISSING_PUBLIC_KEY"
|
"publicKeyPem": process.env.ACTIVITYPUB_PUBLIC_KEY
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { VercelRequest, VercelResponse } from '@vercel/node';
|
import type { VercelRequest, VercelResponse } from '@vercel/node';
|
||||||
|
|
||||||
export default async function (req: VercelRequest, res: VercelResponse) {
|
export default function (req: VercelRequest, res: VercelResponse) {
|
||||||
res.statusCode = 200;
|
res.statusCode = 200;
|
||||||
res.setHeader("Content-Type", `application/jrd+json`);
|
res.setHeader("Content-Type", `application/jrd+json`);
|
||||||
res.end('ok');
|
res.end('ok');
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { VercelRequest, VercelResponse } from '@vercel/node';
|
import type { VercelRequest, VercelResponse } from '@vercel/node';
|
||||||
|
|
||||||
export default async function (req: VercelRequest, res: VercelResponse) {
|
export default function (req: VercelRequest, res: VercelResponse) {
|
||||||
const output = {
|
const output = {
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
"id": "https://coderrrrr.site/api/activitypub/following",
|
"id": "https://coderrrrr.site/api/activitypub/following",
|
||||||
|
@ -8,7 +8,7 @@ import { readFileSync } from 'fs';
|
|||||||
|
|
||||||
It's a GET request. This doesn't post it to anyone's timeline.
|
It's a GET request. This doesn't post it to anyone's timeline.
|
||||||
*/
|
*/
|
||||||
export default async function (req: VercelRequest, res: VercelResponse) {
|
export default function (req: VercelRequest, res: VercelResponse) {
|
||||||
// All of the outbox data is generated at build time, so just return that static file.
|
// All of the outbox data is generated at build time, so just return that static file.
|
||||||
const file = join(cwd(), 'public', 'outbox.ajson');
|
const file = join(cwd(), 'public', 'outbox.ajson');
|
||||||
const stringified = readFileSync(file, 'utf8');
|
const stringified = readFileSync(file, 'utf8');
|
||||||
|
@ -19,73 +19,102 @@ if (!admin.apps.length) {
|
|||||||
|
|
||||||
const db = admin.firestore();
|
const db = admin.firestore();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Sends the latest not that hasn't yet been sent.
|
||||||
|
*/
|
||||||
export default async function (req: VercelRequest, res: VercelResponse) {
|
export default async function (req: VercelRequest, res: VercelResponse) {
|
||||||
if (req.headers.authorization !== `Bearer ${process.env.CRON_SECRET}`) {
|
const { body, query, method, url, headers } = req;
|
||||||
|
|
||||||
|
if (
|
||||||
|
headers.authorization !== `Bearer ${process.env.CRON_SECRET}`
|
||||||
|
) {
|
||||||
return res.status(401).end("Unauthorized");
|
return res.status(401).end("Unauthorized");
|
||||||
}
|
}
|
||||||
|
|
||||||
const configRef = db.collection('config').doc("config");
|
const configCollection = db.collection('config');
|
||||||
|
const configRef = configCollection.doc("config");
|
||||||
const config = await configRef.get();
|
const config = await configRef.get();
|
||||||
const lastId = config.exists ? config.data()?.lastId || "" : "";
|
|
||||||
|
|
||||||
// Fetch notes from outbox
|
if (config.exists == false) {
|
||||||
const outboxResponse = await fetch("https://coderrrrr.site/api/activitypub/outbox");
|
// Config doesn't exist, make something
|
||||||
const outbox = <OrderedCollection> await outboxResponse.json();
|
configRef.set({
|
||||||
|
"lastId": ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const configData = config.data();
|
||||||
|
let lastId = "";
|
||||||
|
if (configData != undefined) {
|
||||||
|
lastId = configData.lastId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get my outbox because it contains all my notes.
|
||||||
|
const outboxResponse = await fetch('https://coderrrrr.site/outbox');
|
||||||
|
const outbox = <OrderedCollection>(await outboxResponse.json());
|
||||||
|
|
||||||
|
const followersCollection = db.collection('followers');
|
||||||
|
const followersQuerySnapshot = await followersCollection.get();
|
||||||
|
|
||||||
const followersSnapshot = await db.collection('followers').get();
|
|
||||||
let lastSuccessfulSentId = "";
|
let lastSuccessfulSentId = "";
|
||||||
|
|
||||||
const inboxes = new Set<string>(); // Track unique inboxes to avoid duplicate sending
|
for (const followerDoc of followersQuerySnapshot.docs) {
|
||||||
|
|
||||||
for (const followerDoc of followersSnapshot.docs) {
|
|
||||||
const follower = followerDoc.data();
|
const follower = followerDoc.data();
|
||||||
const actorUrl = typeof follower.actor === "string" ? follower.actor : follower.actor.id;
|
try {
|
||||||
|
const actorUrl = (typeof follower.actor == "string") ? follower.actor : follower.actor.id;
|
||||||
console.log(`Fetching actor information for ${actorUrl}`);
|
console.log(`Fetching actor information for ${actorUrl}`)
|
||||||
const actorInformation = await fetchActorInformation(actorUrl);
|
const actorInformation = await fetchActorInformation(actorUrl);
|
||||||
if (!actorInformation || !actorInformation.inbox) {
|
if (actorInformation == undefined) {
|
||||||
console.log(`Skipping ${actorUrl}: No valid inbox`);
|
// We can't send to this actor, so skip the actor. We should log it.
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
const inboxUrl = actorInformation.sharedInbox || actorInformation.inbox;
|
|
||||||
inboxes.add(inboxUrl.toString()); // Add to set to ensure unique delivery
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const item of <AP.EntityReference[]>outbox.orderedItems) {
|
|
||||||
if (item.id === lastId) {
|
|
||||||
console.log(`${item.id} has already been posted - skipping`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.object) {
|
|
||||||
item.object.published = new Date().toISOString();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const inboxUrl of inboxes) {
|
|
||||||
try {
|
|
||||||
console.log(`Sending to ${inboxUrl}`);
|
|
||||||
|
|
||||||
const response = await sendSignedRequest(new URL(inboxUrl), <AP.Activity> item, {
|
|
||||||
headers: {
|
|
||||||
"Accept": "application/activity+json",
|
|
||||||
"Content-Type": "application/activity+json"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`Send result: ${response.status} ${response.statusText}`);
|
|
||||||
const responseText = await response.text();
|
|
||||||
console.log("Response body:", responseText);
|
|
||||||
|
|
||||||
lastSuccessfulSentId = item.id;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error sending to ${inboxUrl}:`, error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (actorInformation.inbox == undefined) {
|
||||||
|
console.log(
|
||||||
|
`Actor ${actorUrl} doesn't have an inbox, so we can't send to them. ${actorInformation}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const actorInbox = new URL(actorInformation.inbox.toString());
|
||||||
|
|
||||||
|
for (const iteIdx in (<AP.EntityReference[]>outbox.orderedItems)) {
|
||||||
|
// We have to break somewhere... do it after the first.
|
||||||
|
const item = (<AP.EntityReference[]>outbox.orderedItems)[iteIdx];
|
||||||
|
|
||||||
|
console.log(`Checking ID ${item.id}, ${lastId}`);
|
||||||
|
if (item.id == `${lastId}`) {
|
||||||
|
lastSuccessfulSentId = item.id;
|
||||||
|
// We've already posted this, don't try and send it again.
|
||||||
|
console.log(`${item.id} has already been posted - don't attempt`)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.object != undefined) {
|
||||||
|
// We might not need this.
|
||||||
|
item.object.published = (new Date()).toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item will be an entity, i.e, { Create { Note } }
|
||||||
|
try {
|
||||||
|
console.log(`Sending to ${actorInbox}`);
|
||||||
|
|
||||||
|
const response = await sendSignedRequest(actorInbox, <AP.Activity> item);
|
||||||
|
console.log(`Send result: ${actorInbox}`, response.status, response.statusText, await response.text());
|
||||||
|
|
||||||
|
// It's not been sent.
|
||||||
|
lastSuccessfulSentId = item.id; // we shouldn't really set this every time.
|
||||||
|
} catch (sendSignedError) {
|
||||||
|
console.log("Error sending signed request", sendSignedError)
|
||||||
|
}
|
||||||
|
|
||||||
|
break; // At some point we might want to post more than one post, so remove this.
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
console.log("Error", ex);
|
||||||
}
|
}
|
||||||
break; // Only send the latest post for now
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await configRef.set({ "lastId": lastSuccessfulSentId });
|
configRef.set({
|
||||||
|
"lastId": lastSuccessfulSentId
|
||||||
|
});
|
||||||
|
|
||||||
res.status(200).end("ok");
|
res.status(200).end("ok");
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
function verifySignature(signature:any, publicKeyJson:any) {
|
export function verifySignature(signature:any, publicKeyJson:any) {
|
||||||
let signatureValid;
|
let signatureValid;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "coderrrrr.site",
|
"name": "coderrrrr.site",
|
||||||
|
"type": "module",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest"
|
"@types/bun": "latest"
|
||||||
},
|
},
|
||||||
|
@ -47,10 +47,6 @@
|
|||||||
"source": "/inbox",
|
"source": "/inbox",
|
||||||
"destination": "/api/activitypub/inbox.ts"
|
"destination": "/api/activitypub/inbox.ts"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"source": "/sharedInbox",
|
|
||||||
"destination": "/api/activitypub/sharedInbox.ts"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"source": "/outbox",
|
"source": "/outbox",
|
||||||
"destination": "/api/activitypub/outbox.ts"
|
"destination": "/api/activitypub/outbox.ts"
|
||||||
@ -58,7 +54,7 @@
|
|||||||
],
|
],
|
||||||
"headers": [
|
"headers": [
|
||||||
{
|
{
|
||||||
"source": "/(.*).json",
|
"source": "/(.*).ajson",
|
||||||
"headers": [
|
"headers": [
|
||||||
{
|
{
|
||||||
"key": "content-type",
|
"key": "content-type",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user