mirror of
https://github.com/turbomaster95/coderrrrr.git
synced 2025-08-02 10:42:24 +00:00
Compare commits
No commits in common. "faaef129e811fdc458b44510598a6a301dd2e34c" and "31b776e956f0f4f54fbe0eff609ae7635008b407" have entirely different histories.
faaef129e8
...
31b776e956
@ -4,7 +4,7 @@ export default async function (req: VercelRequest, res: VercelResponse) {
|
|||||||
const { headers } = req;
|
const { headers } = req;
|
||||||
|
|
||||||
if (headers.accept && headers.accept.includes("text/html")) {
|
if (headers.accept && headers.accept.includes("text/html")) {
|
||||||
res.redirect(302, "https://coder.is-a.dev/");
|
res.redirect(302, "https://coderrrrr.site/");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ export default function (req: VercelRequest, res: VercelResponse) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Expected format: acct:username@coderrrrr.site
|
// Expected format: acct:username@coderrrrr.site
|
||||||
const match = resource.match(/^acct:([^@]+)@coder\.is-a.dev$/i);
|
const match = resource.match(/^acct:([^@]+)@coderrrrr\.site$/i);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
res.status(400).json({ error: "Resource format not supported" });
|
res.status(400).json({ error: "Resource format not supported" });
|
||||||
return;
|
return;
|
||||||
@ -22,7 +22,7 @@ export default function (req: VercelRequest, res: VercelResponse) {
|
|||||||
// You can add more supported usernames as needed.
|
// You can add more supported usernames as needed.
|
||||||
let profileUrl: string;
|
let profileUrl: string;
|
||||||
if (username === "blog") {
|
if (username === "blog") {
|
||||||
profileUrl = "https://coder.is-a.dev/@blog";
|
profileUrl = "https://coderrrrr.site/@blog";
|
||||||
} else {
|
} else {
|
||||||
// If the username is not recognized, return a 404.
|
// If the username is not recognized, return a 404.
|
||||||
res.status(404).json({ error: "User not found" });
|
res.status(404).json({ error: "User not found" });
|
||||||
|
74
netlify.toml
74
netlify.toml
@ -1,74 +0,0 @@
|
|||||||
[build]
|
|
||||||
functions = "netlify/functions"
|
|
||||||
|
|
||||||
# Redirects from old AMP and HTML URLs
|
|
||||||
[[redirects]]
|
|
||||||
from = "/amp/:slug/"
|
|
||||||
to = "/:slug/"
|
|
||||||
status = 301
|
|
||||||
force = true
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
from = "/:year/:month/:slug.html"
|
|
||||||
to = "/:slug/"
|
|
||||||
status = 301
|
|
||||||
force = true
|
|
||||||
|
|
||||||
# Rewrites for ActivityPub API endpoints
|
|
||||||
[[redirects]]
|
|
||||||
from = "/.well-known/*"
|
|
||||||
to = "/.netlify/functions/well-known/:splat"
|
|
||||||
status = 200
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
from = "/authorize_interaction"
|
|
||||||
to = "/.netlify/functions/authorize_interaction"
|
|
||||||
status = 200
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
from = "/@blog"
|
|
||||||
to = "/.netlify/functions/actor"
|
|
||||||
status = 200
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
from = "/coder"
|
|
||||||
to = "/.netlify/functions/old"
|
|
||||||
status = 200
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
from = "/followers"
|
|
||||||
to = "/.netlify/functions/followers"
|
|
||||||
status = 200
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
from = "/following"
|
|
||||||
to = "/.netlify/functions/following"
|
|
||||||
status = 200
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
from = "/inbox"
|
|
||||||
to = "/.netlify/functions/inbox"
|
|
||||||
status = 200
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
from = "/sharedInbox"
|
|
||||||
to = "/.netlify/functions/sharedInbox"
|
|
||||||
status = 200
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
from = "/outbox"
|
|
||||||
to = "/.netlify/functions/outbox"
|
|
||||||
status = 200
|
|
||||||
|
|
||||||
# Rewrite for trailing slashes to index.html
|
|
||||||
[[redirects]]
|
|
||||||
from = "/:path/"
|
|
||||||
to = "/:path/index.html"
|
|
||||||
status = 200
|
|
||||||
|
|
||||||
# Custom content-type for ActivityPub JSON
|
|
||||||
[[headers]]
|
|
||||||
for = "/*.json"
|
|
||||||
[headers.values]
|
|
||||||
Content-Type = "application/activity+json"
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
|||||||
exports.handler = async (event, context) => {
|
|
||||||
const headers = event.headers;
|
|
||||||
|
|
||||||
if (headers.accept && headers.accept.includes("text/html")) {
|
|
||||||
return {
|
|
||||||
statusCode: 302,
|
|
||||||
headers: {
|
|
||||||
Location: "https://coder.is-a.dev/"
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/activity+json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
"@context": ["https://www.w3.org/ns/activitystreams", { "@language": "en-US" }],
|
|
||||||
"type": "Person",
|
|
||||||
"id": "https://coder.is-a.dev/@blog",
|
|
||||||
"outbox": "https://coder.is-a.dev/api/activitypub/outbox",
|
|
||||||
"following": "https://coder.is-a.dev/api/activitypub/following",
|
|
||||||
"followers": "https://coder.is-a.dev/api/activitypub/followers",
|
|
||||||
"sharedInbox": "https://coder.is-a.dev/api/activitypub/sharedInbox",
|
|
||||||
"inbox": "https://coder.is-a.dev/api/activitypub/inbox",
|
|
||||||
"url": "https://coder.is-a.dev/@blog",
|
|
||||||
"published": "2024-05-02T15:25:40Z",
|
|
||||||
"preferredUsername": "blog",
|
|
||||||
"name": "Deva Midhun's blog",
|
|
||||||
"manuallyApprovesFollowers": false,
|
|
||||||
"discoverable": true,
|
|
||||||
"indexable": true,
|
|
||||||
"memorial": false,
|
|
||||||
"summary": "Software developer & self-hosting enthusiast. This is a bridge between my blog and the fediverse!",
|
|
||||||
"tag": [],
|
|
||||||
"icon": {
|
|
||||||
"type": "Image",
|
|
||||||
"mediaType": "image/png",
|
|
||||||
"url": "https://i.ibb.co/N6J5b8WS/download20250102015611.png"
|
|
||||||
},
|
|
||||||
"publicKey": {
|
|
||||||
"id": "https://coder.is-a.dev/@blog#main-key",
|
|
||||||
"owner": "https://coder.is-a.dev/@blog",
|
|
||||||
"publicKeyPem": process.env.ACTIVITYPUB_PUBLIC_KEY || "MISSING_PUBLIC_KEY"
|
|
||||||
},
|
|
||||||
"attachment": [
|
|
||||||
{
|
|
||||||
"type": "PropertyValue",
|
|
||||||
"value": "<a href=\"https://coder.is-a.dev\" target=\"_blank\" rel=\"nofollow noopener me\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">coder.is-a.dev</span><span class=\"invisible\"></span></a>",
|
|
||||||
"name": "Website"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "PropertyValue",
|
|
||||||
"value": "<a href=\"https://github.com/turbomaster95\" target=\"_blank\" rel=\"nofollow noopener me\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">github.com/turbomaster95</span><span class=\"invisible\"></span></a>",
|
|
||||||
"name": "GitHub"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "PropertyValue",
|
|
||||||
"value": "<a href=\"https://usr.cloud/@coder\" target=\"_blank\" rel=\"nofollow noopener me\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">usr.cloud/@coder</span><span class=\"invisible\"></span></a>",
|
|
||||||
"name": "Owner"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "PropertyValue",
|
|
||||||
"value": "<a href=\"https://keyoxide.org/6389542B98CB868DAC73A373ED1190B780583CF6\" target=\"_blank\" rel=\"nofollow noopener me\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"ellipsis\">keyoxide.org/6389542B98CB868DA</span><span class=\"invisible\">C73A373ED1190B780583CF6</span></a>",
|
|
||||||
"name": "Keyoxide"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,9 +0,0 @@
|
|||||||
exports.handler = async (event, context) => {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/jrd+json"
|
|
||||||
},
|
|
||||||
body: "ok"
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,43 +0,0 @@
|
|||||||
import { Handler } from '@netlify/functions';
|
|
||||||
import { firestore } from '../../../lib/firebase';
|
|
||||||
import { fetchActorInformation } from '../../../lib/activitypub/utils/fetchActorInformation';
|
|
||||||
|
|
||||||
const handler: Handler = async (event, context) => {
|
|
||||||
const [, , username] = event.path.split('/'); // /users/:username/followers
|
|
||||||
|
|
||||||
try {
|
|
||||||
const followersSnapshot = await firestore
|
|
||||||
.collection('followers')
|
|
||||||
.where('following', '==', username)
|
|
||||||
.get();
|
|
||||||
|
|
||||||
const followers = followersSnapshot.docs.map(doc => ({
|
|
||||||
type: 'Link',
|
|
||||||
href: doc.data().follower,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const actor = await fetchActorInformation(username);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/activity+json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
|
||||||
id: `${actor.id}/followers`,
|
|
||||||
type: 'OrderedCollection',
|
|
||||||
totalItems: followers.length,
|
|
||||||
orderedItems: followers,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
body: JSON.stringify({ error: error.message }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export { handler };
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
|||||||
import { Handler } from '@netlify/functions';
|
|
||||||
|
|
||||||
const handler: Handler = async (event, context) => {
|
|
||||||
const output = {
|
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
|
||||||
"id": "https://coder.is-a.dev/api/activitypub/following",
|
|
||||||
"type": "OrderedCollection",
|
|
||||||
"totalItems": 2,
|
|
||||||
"orderedItems": [
|
|
||||||
"https://mastodon.social/users/coderrrrr",
|
|
||||||
"https://usr.cloud/users/coder"
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/activity+json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(output),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export { handler };
|
|
@ -1,46 +0,0 @@
|
|||||||
import { Handler } from '@netlify/functions';
|
|
||||||
import { firestore } from '../../../lib/firebase';
|
|
||||||
import { verifySignature } from '../../../lib/activitypub/utils/verifySignature.js';
|
|
||||||
import { handleFollow } from '../../../lib/handleFollow';
|
|
||||||
|
|
||||||
const handler: Handler = async (event, context) => {
|
|
||||||
if (event.httpMethod !== 'POST') {
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
body: 'Method Not Allowed',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const inboxData = JSON.parse(event.body || '{}');
|
|
||||||
const username = event.path.split('/')[2]; // /users/:username/inbox
|
|
||||||
|
|
||||||
const isVerified = await verifySignature(event.headers, event.body);
|
|
||||||
if (!isVerified) {
|
|
||||||
return {
|
|
||||||
statusCode: 401,
|
|
||||||
body: 'Unauthorized',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inboxData.type === 'Follow') {
|
|
||||||
await handleFollow(inboxData, username);
|
|
||||||
return {
|
|
||||||
statusCode: 202,
|
|
||||||
body: 'Follow request accepted',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
body: 'Activity received',
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
body: JSON.stringify({ error: error.message }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export { handler };
|
|
@ -1,20 +0,0 @@
|
|||||||
import { Handler } from '@netlify/functions';
|
|
||||||
import { join } from 'path';
|
|
||||||
import { cwd } from 'process';
|
|
||||||
import { readFileSync } from 'fs';
|
|
||||||
|
|
||||||
const handler: Handler = async (event, context) => {
|
|
||||||
const file = join(cwd(), 'public', 'outbox.ajson');
|
|
||||||
const stringified = readFileSync(file, 'utf8');
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/activity+json",
|
|
||||||
},
|
|
||||||
body: stringified,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export { handler };
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
|||||||
import { Handler } from '@netlify/functions';
|
|
||||||
import { AP } from 'activitypub-core-types';
|
|
||||||
import admin from 'firebase-admin';
|
|
||||||
import { OrderedCollection } from 'activitypub-core-types/lib/activitypub/index.js';
|
|
||||||
import { sendSignedRequest } from '../../lib/activitypub/utils/sendSignedRequest.js';
|
|
||||||
import { fetchActorInformation } from '../../lib/activitypub/utils/fetchActorInformation.js';
|
|
||||||
|
|
||||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
|
||||||
|
|
||||||
if (!admin.apps.length) {
|
|
||||||
admin.initializeApp({
|
|
||||||
credential: admin.credential.cert({
|
|
||||||
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
|
|
||||||
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
|
|
||||||
privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const db = admin.firestore();
|
|
||||||
|
|
||||||
const handler: Handler = async (event, context) => {
|
|
||||||
if (event.headers.authorization !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
||||||
return {
|
|
||||||
statusCode: 401,
|
|
||||||
body: "Unauthorized",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const configRef = db.collection('config').doc('config');
|
|
||||||
const config = await configRef.get();
|
|
||||||
const lastId = config.exists ? config.data()?.lastId || '' : '';
|
|
||||||
|
|
||||||
// Fetch notes from outbox
|
|
||||||
const outboxResponse = await fetch("https://coder.is-a.dev/api/activitypub/outbox");
|
|
||||||
const outbox = <OrderedCollection>await outboxResponse.json();
|
|
||||||
|
|
||||||
const followersSnapshot = await db.collection('followers').get();
|
|
||||||
let lastSuccessfulSentId = "";
|
|
||||||
|
|
||||||
const inboxes = new Set<string>(); // Track unique inboxes to avoid duplicate sending
|
|
||||||
|
|
||||||
for (const followerDoc of followersSnapshot.docs) {
|
|
||||||
const follower = followerDoc.data();
|
|
||||||
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 || !actorInformation.inbox) {
|
|
||||||
console.log(`Skipping ${actorUrl}: No valid inbox`);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break; // Only send the latest post for now
|
|
||||||
}
|
|
||||||
|
|
||||||
await configRef.set({ lastId: lastSuccessfulSentId });
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
body: "ok",
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export { handler };
|
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user