feat(mastodon): complete mastodon post fetching

This commit is contained in:
Matyáš Caras 2023-11-23 19:14:20 +01:00
parent 0749936c25
commit 5e610d9172
Signed by untrusted user who does not match committer: hernik
GPG key ID: 2A3175F98820C5C6
9 changed files with 69 additions and 29 deletions

View file

@ -7,10 +7,10 @@ RUN npm install -g pnpm
RUN pnpm install --frozen-lockfile RUN pnpm install --frozen-lockfile
FROM node:lts-alpine AS builder FROM node:lts-alpine AS builder
ARG APP_ENV # ARG APP_ENV
WORKDIR /app WORKDIR /app
COPY . . COPY . .
COPY .env.$APP_ENV .env COPY .env .env
COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/node_modules ./node_modules
RUN npm run build RUN npm run build
@ -19,7 +19,7 @@ WORKDIR /usr/app
ARG APP_ENV ARG APP_ENV
COPY --from=builder /app/build ./build COPY --from=builder /app/build ./build
COPY package.json ./ COPY package.json ./
COPY .env.$APP_ENV .env COPY .env .env
RUN npm install -g pnpm RUN npm install -g pnpm
RUN pnpm install --prod RUN pnpm install --prod
USER node USER node

View file

@ -7,7 +7,7 @@ Ever felt like you need to have a privacy-respecting alternative for everything?
Next pro comes in the form of customization. By fetching the raw data and ~~formatting it~~ trying to format it into a reasonable JSON object, you are not limited by some embed image returned by a server. Let your creativity flow into your theme, which can be made by simply throwing some HTML and CSS together. Or if you're lazy, just use some of the pre-made ones (unless you use the ones I made, they are ugly). Next pro comes in the form of customization. By fetching the raw data and ~~formatting it~~ trying to format it into a reasonable JSON object, you are not limited by some embed image returned by a server. Let your creativity flow into your theme, which can be made by simply throwing some HTML and CSS together. Or if you're lazy, just use some of the pre-made ones (unless you use the ones I made, they are ugly).
## Configuration ## Configuration
TODO Refer to [configuration.md](./docs/configuration.md).
## Roadmap ## Roadmap
- [X] Easy theme switching - [X] Easy theme switching
@ -16,10 +16,12 @@ TODO
- [ ] Contact form that sends stuff somewhere - [ ] Contact form that sends stuff somewhere
- [ ] Shoutbox - [ ] Shoutbox
- [ ] Discord online/offline??? (idk probably needs a bot) - [ ] Discord online/offline??? (idk probably needs a bot)
- [] Fetch latest Mastodon post - [X] Fetch latest Mastodon post
- [ ] Follower counts for different sites - [ ] Follower counts for different sites
- [X] Dynamically add custom pages - [X] Dynamically add custom pages
- [ ] Mini CMS blog - [ ] Mini CMS blog
- [ ] Gallery
- [ ] Comments
## License ## License
``` ```

View file

@ -15,6 +15,7 @@
"enabled":true, "enabled":true,
"method":"friendly", "method":"friendly",
"secretKey":"", "secretKey":"",
"clientKey":"" "clientKey":"",
"url":""
} }
} }

View file

@ -1,7 +1,7 @@
{ {
"name": "fastify-typescript-starter", "name": "egoport",
"version": "1.5.0", "version": "0.1.0",
"description": "Node.js boilerplate using fastify & TypeScript", "description": "Your personal portfolio, serving stuff while respecting user privacy",
"type": "module", "type": "module",
"scripts": { "scripts": {
"lint": "eslint .", "lint": "eslint .",
@ -15,10 +15,6 @@
"test": "vitest", "test": "vitest",
"test:watch": "vitest -w" "test:watch": "vitest -w"
}, },
"repository": {
"type": "git",
"url": "git+https://github.com/yonathan06/fastify-typescript-starter.git"
},
"engines": { "engines": {
"node": ">=16.0.0" "node": ">=16.0.0"
}, },
@ -27,12 +23,12 @@
"fastify", "fastify",
"typescript" "typescript"
], ],
"author": "Yonatan Bendahan", "author": "Matyáš Caras",
"license": "MIT", "license": "AGPL-3.0-only",
"bugs": { "bugs": {
"url": "https://github.com/yonathan06/fastify-typescript-starter/issues" "url": "https://git.mnau.xyz/hernik/egoport/issues"
}, },
"homepage": "https://github.com/yonathan06/fastify-typescript-starter#readme", "homepage": "https://git.mnau.xyz/hernik/egoport#readme",
"dependencies": { "dependencies": {
"@fastify/static": "^6.12.0", "@fastify/static": "^6.12.0",
"@fastify/view": "^8.2.0", "@fastify/view": "^8.2.0",
@ -44,6 +40,7 @@
"env-schema": "^5.2.1", "env-schema": "^5.2.1",
"fastify": "^4.24.3", "fastify": "^4.24.3",
"fastify-plugin": "^3.0.1", "fastify-plugin": "^3.0.1",
"node-cache": "^5.1.2",
"ts-json-validator": "^0.7.1" "ts-json-validator": "^0.7.1"
}, },
"devDependencies": { "devDependencies": {

View file

@ -11,7 +11,7 @@ async function findMastodonUser(username: string, host: string): Promise<string>
console.error(res.data); console.error(res.data);
return ''; return '';
} }
return JSON.parse(res.data)['accounts'][0]['id']; return res.data['accounts'][0]['id'];
} }
interface MastodonPost { interface MastodonPost {
@ -21,6 +21,8 @@ interface MastodonPost {
reblogs: number; reblogs: number;
replies: number; replies: number;
inReplyTo?: string; inReplyTo?: string;
mediaUrls: string[];
user: string;
} }
async function getLatestMastodonPost(id: string, host: string): Promise<MastodonPost> { async function getLatestMastodonPost(id: string, host: string): Promise<MastodonPost> {
@ -34,11 +36,17 @@ async function getLatestMastodonPost(id: string, host: string): Promise<Mastodon
console.error(`Could not get latest posts for id '${id}' using host '${host}':`); console.error(`Could not get latest posts for id '${id}' using host '${host}':`);
throw new Error(res.data); throw new Error(res.data);
} }
const data = JSON.parse(res.data)[0]; const data = res.data[0];
let inReplyTo = undefined; let inReplyTo = undefined;
if (data['in_reply_to_account_id'] != null) { if (data['in_reply_to_account_id'] != null) {
inReplyTo = data['mentions'][0]['acct']; inReplyTo = data['mentions'][0]['acct'];
} }
const mediaUrl: string[] = [];
data['media_attachments'].forEach((media: { [key: string]: string | object | null }) => {
if (media['url'] != null) {
mediaUrl.push(media['url'] as string);
}
});
return { return {
url: data['url'], url: data['url'],
content: decodeURI(data['content']), content: decodeURI(data['content']),
@ -46,7 +54,9 @@ async function getLatestMastodonPost(id: string, host: string): Promise<Mastodon
reblogs: data['reblogs_count'], reblogs: data['reblogs_count'],
replies: data['replies_count'], replies: data['replies_count'],
inReplyTo: inReplyTo, inReplyTo: inReplyTo,
mediaUrls: mediaUrl,
user: data['account']['username']
}; };
} }
export {findMastodonUser, getLatestMastodonPost} export { findMastodonUser, getLatestMastodonPost };

View file

@ -3,6 +3,7 @@ import { FastifyPluginAsync } from 'fastify';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import path from 'path'; import path from 'path';
import { parser } from '../server'; import { parser } from '../server';
import { findMastodonUser, getLatestMastodonPost } from 'api/mastodon';
const routes: FastifyPluginAsync = async (server) => { const routes: FastifyPluginAsync = async (server) => {
server.get('/', {}, async function (req, res) { server.get('/', {}, async function (req, res) {
@ -19,7 +20,21 @@ const routes: FastifyPluginAsync = async (server) => {
console.error('Check your config.json file!'); console.error('Check your config.json file!');
process.exit(1); process.exit(1);
} }
return res.view('index.ejs',{title:userConfig.title,subtitle:userConfig.subtitle}); try {
let mastodon = undefined;
if(userConfig.mastodon != null && userConfig.mastodon.host != undefined && userConfig.mastodon.username != undefined){
let user = userConfig.mastodon.username
if(/\w/gm.test(user)){
// TODO: save ID to config
user = await findMastodonUser(userConfig.mastodon.username,userConfig.mastodon.host);
}
mastodon = await getLatestMastodonPost(user,userConfig.mastodon.host)
}
return res.view('index.ejs',{title:userConfig.title,subtitle:userConfig.subtitle,mastodon});
} catch (error) {
console.error("Error while rendering index! Got following error:")
console.error(error)
}
}); });
}; };

View file

@ -15,6 +15,7 @@ const parser = new TsjsonParser(
title: 'EGOport Configuration', title: 'EGOport Configuration',
description: 'Configuration for the EGOport program', description: 'Configuration for the EGOport program',
type: 'object', type: 'object',
additionalProperties:S(false),
properties: { properties: {
theme: S({ theme: S({
description: "The name of the theme matching a name of a folder inside 'themes'", description: "The name of the theme matching a name of a folder inside 'themes'",
@ -43,6 +44,7 @@ const parser = new TsjsonParser(
type: 'object', type: 'object',
description: 'Contains configuration for the Mastodon integration', description: 'Contains configuration for the Mastodon integration',
default: {}, default: {},
additionalProperties:S(false),
properties: { properties: {
host: S({ host: S({
type: 'string', type: 'string',
@ -69,13 +71,14 @@ const parser = new TsjsonParser(
}), }),
captcha: S({ captcha: S({
type:"object", type:"object",
additionalProperties:S(false),
properties:{ properties:{
enabled: S({ enabled: S({
type:"boolean", type:"boolean",
description:"Whether captcha should be enabled on shoutbox/contact form", description:"Whether captcha should be enabled on shoutbox/contact form",
default:true default:true
}), }),
host: S({ method: S({
type:"string", type:"string",
description:"Who will provide the CAPTCHA service", description:"Who will provide the CAPTCHA service",
enum: ["friendly","mosparo"], enum: ["friendly","mosparo"],

View file

@ -18,6 +18,10 @@
<% if (subtitle) { %> <% if (subtitle) { %>
<h2><%= subtitle %></h2> <h2><%= subtitle %></h2>
<% } %> <% } %>
<p>Latest Mastodon post:</p>
<% if (mastodon){ %>
<%- include('widgets/mastodon',{mastodon:mastodon}) %>
<% } %>
</div> </div>
<div id="right"> <div id="right">
<p>Hello World</p> <p>Hello World</p>

View file

@ -0,0 +1,8 @@
<div id="mastodon">
<p style="font-style: italic !important;">
<%- mastodon.content %>
</p>
<p class="signature">
- <a href="<%= mastodon.url %>"> <%= mastodon.user %> </a>
</p>
</div>