feat(mastodon): ✨ complete mastodon post fetching
This commit is contained in:
parent
0749936c25
commit
5e610d9172
9 changed files with 69 additions and 29 deletions
|
@ -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
|
||||||
|
|
20
README.md
20
README.md
|
@ -7,19 +7,21 @@ 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
|
||||||
- [] Actual caching!!!!!
|
- [ ] Actual caching!!!!!
|
||||||
- [] ListenBrainz current/last listening
|
- [ ] ListenBrainz current/last listening
|
||||||
- [] 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
|
||||||
```
|
```
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
"enabled":true,
|
"enabled":true,
|
||||||
"method":"friendly",
|
"method":"friendly",
|
||||||
"secretKey":"",
|
"secretKey":"",
|
||||||
"clientKey":""
|
"clientKey":"",
|
||||||
|
"url":""
|
||||||
}
|
}
|
||||||
}
|
}
|
19
package.json
19
package.json
|
@ -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": {
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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"],
|
||||||
|
|
|
@ -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>
|
||||||
|
|
8
themes/default/widgets/mastodon.ejs
Normal file
8
themes/default/widgets/mastodon.ejs
Normal 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>
|
Loading…
Reference in a new issue