mirror of
				https://github.com/turbomaster95/coderrrrr.git
				synced 2025-11-04 09:18:35 +00:00 
			
		
		
		
	Compare commits
	
		
			5 Commits
		
	
	
		
			0995069a74
			...
			9803161404
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 9803161404 | |||
| 3e6aabe138 | |||
| bd88cd50b7 | |||
| 6a360258b2 | |||
| ce942c6476 | 
@ -1,39 +1,58 @@
 | 
				
			|||||||
import type { VercelRequest, VercelResponse } from '@vercel/node';
 | 
					import type { VercelRequest, VercelResponse } from '@vercel/node';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function (req: VercelRequest, res: VercelResponse) {
 | 
					export default async function (req: VercelRequest, res: VercelResponse) {
 | 
				
			||||||
  const { headers } = req;
 | 
					  const { headers } = req;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if ("accept" in headers) {
 | 
					  if (headers.accept && headers.accept.includes("text/html")) {
 | 
				
			||||||
    const accept = headers["accept"];
 | 
					    res.redirect(302, "https://coderrrrr.site/");
 | 
				
			||||||
    if (accept != null && accept.split(",").indexOf("text/html") > -1) {
 | 
					    return;
 | 
				
			||||||
      return res.redirect(302, "https://coderrrrr.site/").end();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  res.statusCode = 200;
 | 
					  res.status(200).setHeader("Content-Type", "application/activity+json").json({
 | 
				
			||||||
  res.setHeader("Content-Type", `application/activity+json`);
 | 
					    "@context": ["https://www.w3.org/ns/activitystreams", { "@language": "en-US" }],
 | 
				
			||||||
  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/outbox",
 | 
					    "outbox": "https://coderrrrr.site/api/activitypub/outbox",
 | 
				
			||||||
    "following": "https://coderrrrr.site/following",
 | 
					    "following": "https://coderrrrr.site/api/activitypub/following",
 | 
				
			||||||
    "followers": "https://coderrrrr.site/followers",
 | 
					    "followers": "https://coderrrrr.site/api/activitypub/followers",
 | 
				
			||||||
    "inbox": "https://coderrrrr.site/inbox",
 | 
					    "sharedInbox": "https://coderrrrr.site/api/activitypub/sharedInbox",
 | 
				
			||||||
 | 
					    "inbox": "https://coderrrrr.site/api/activitypub/inbox",
 | 
				
			||||||
    "preferredUsername": "coder",
 | 
					    "preferredUsername": "coder",
 | 
				
			||||||
    "name": "Deva Midhun's blog",
 | 
					    "name": "Deva Midhun's blog",
 | 
				
			||||||
    "summary": "",
 | 
					    "discoverable": true,
 | 
				
			||||||
 | 
					    "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
 | 
					      "publicKeyPem": process.env.ACTIVITYPUB_PUBLIC_KEY || "MISSING_PUBLIC_KEY"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
import type { VercelRequest, VercelResponse } from '@vercel/node';
 | 
					import type { VercelRequest, VercelResponse } from '@vercel/node';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function (req: VercelRequest, res: VercelResponse) {
 | 
					export default async 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 function (req: VercelRequest, res: VercelResponse) {
 | 
					export default async 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 function (req: VercelRequest, res: VercelResponse) {
 | 
					export default async 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,102 +19,73 @@ 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) {
 | 
				
			||||||
  const { body, query, method, url, headers } = req;
 | 
					  if (req.headers.authorization !== `Bearer ${process.env.CRON_SECRET}`) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (
 | 
					 | 
				
			||||||
    headers.authorization !== `Bearer ${process.env.CRON_SECRET}`
 | 
					 | 
				
			||||||
  ) {
 | 
					 | 
				
			||||||
    return res.status(401).end("Unauthorized");
 | 
					    return res.status(401).end("Unauthorized");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const configCollection = db.collection('config');
 | 
					  const configRef = db.collection('config').doc("config");
 | 
				
			||||||
  const configRef = configCollection.doc("config");
 | 
					 | 
				
			||||||
  const config = await configRef.get();
 | 
					  const config = await configRef.get();
 | 
				
			||||||
 | 
					  const lastId = config.exists ? config.data()?.lastId || "" : "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (config.exists == false) {
 | 
					  // Fetch notes from outbox
 | 
				
			||||||
    // Config doesn't exist, make something
 | 
					  const outboxResponse = await fetch("https://coderrrrr.site/api/activitypub/outbox");
 | 
				
			||||||
    configRef.set({
 | 
					  const outbox = <OrderedCollection> await outboxResponse.json();
 | 
				
			||||||
      "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 = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  for (const followerDoc of followersQuerySnapshot.docs) {
 | 
					  const inboxes = new Set<string>(); // Track unique inboxes to avoid duplicate sending
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (const followerDoc of followersSnapshot.docs) {
 | 
				
			||||||
    const follower = followerDoc.data();
 | 
					    const follower = followerDoc.data();
 | 
				
			||||||
    try {
 | 
					    const actorUrl = typeof follower.actor === "string" ? follower.actor : follower.actor.id;
 | 
				
			||||||
      const actorUrl = (typeof follower.actor == "string") ? follower.actor : follower.actor.id;
 | 
					 | 
				
			||||||
      console.log(`Fetching actor information for ${actorUrl}`)
 | 
					 | 
				
			||||||
      const actorInformation = await fetchActorInformation(actorUrl);
 | 
					 | 
				
			||||||
      if (actorInformation == undefined) {
 | 
					 | 
				
			||||||
        // We can't send to this actor, so skip the actor. We should log it.
 | 
					 | 
				
			||||||
        continue;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
      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)) {
 | 
					    console.log(`Fetching actor information for ${actorUrl}`);
 | 
				
			||||||
        // We have to break somewhere... do it after the first.
 | 
					    const actorInformation = await fetchActorInformation(actorUrl);
 | 
				
			||||||
        const item = (<AP.EntityReference[]>outbox.orderedItems)[iteIdx];
 | 
					    if (!actorInformation || !actorInformation.inbox) {
 | 
				
			||||||
       
 | 
					      console.log(`Skipping ${actorUrl}: No valid inbox`);
 | 
				
			||||||
        console.log(`Checking ID ${item.id}, ${lastId}`);
 | 
					      continue;
 | 
				
			||||||
        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);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const inboxUrl = actorInformation.sharedInbox || actorInformation.inbox;
 | 
				
			||||||
 | 
					    inboxes.add(inboxUrl.toString()); // Add to set to ensure unique delivery
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  configRef.set({
 | 
					  for (const item of <AP.EntityReference[]>outbox.orderedItems) {
 | 
				
			||||||
    "lastId": lastSuccessfulSentId
 | 
					    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);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    break; // Only send the latest post for now
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  await configRef.set({ "lastId": lastSuccessfulSentId });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  res.status(200).end("ok");
 | 
					  res.status(200).end("ok");
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
export function verifySignature(signature:any, publicKeyJson:any) {
 | 
					function verifySignature(signature:any, publicKeyJson:any) {
 | 
				
			||||||
  let signatureValid;
 | 
					  let signatureValid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,5 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "coderrrrr.site",
 | 
					  "name": "coderrrrr.site",
 | 
				
			||||||
  "type": "module",
 | 
					 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@types/bun": "latest"
 | 
					    "@types/bun": "latest"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
				
			|||||||
@ -47,6 +47,10 @@
 | 
				
			|||||||
      "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"
 | 
				
			||||||
@ -54,7 +58,7 @@
 | 
				
			|||||||
  ],
 | 
					  ],
 | 
				
			||||||
  "headers": [
 | 
					  "headers": [
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      "source": "/(.*).ajson",
 | 
					      "source": "/(.*).json",
 | 
				
			||||||
      "headers": [
 | 
					      "headers": [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          "key": "content-type",
 | 
					          "key": "content-type",
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user