();
for (const s of scope) {
- if (s.match(/^read/)) for (const r of readScope) pushScope.add(r)
- if (s.match(/^write/)) for (const r of writeScope) pushScope.add(r)
+ if (s.match(/^read/)) for (const r of readScope) pushScope.add(r);
+ if (s.match(/^write/)) for (const r of writeScope) pushScope.add(r);
}
- const scopeArr = Array.from(pushScope)
+ const scopeArr = Array.from(pushScope);
- let red = body.redirect_uris
- if (red === 'urn:ietf:wg:oauth:2.0:oob') {
- red = 'https://thedesk.top/hello.html'
+ let red = body.redirect_uris;
+ if (red === "urn:ietf:wg:oauth:2.0:oob") {
+ red = "https://thedesk.top/hello.html";
}
- const appData = await client.registerApp(body.client_name, { scopes: scopeArr, redirect_uris: red, website: body.website });
+ const appData = await client.registerApp(body.client_name, {
+ scopes: scopeArr,
+ redirect_uris: red,
+ website: body.website,
+ });
ctx.body = {
id: appData.id,
name: appData.name,
website: appData.website,
redirect_uri: red,
- client_id: Buffer.from(appData.url || '').toString('base64'),
+ client_id: Buffer.from(appData.url || "").toString("base64"),
client_secret: appData.clientSecret,
- }
+ };
} catch (e: any) {
- console.error(e)
+ console.error(e);
ctx.status = 401;
ctx.body = e.response.data;
}
});
-
}
diff --git a/packages/backend/src/server/api/mastodon/endpoints/filter.ts b/packages/backend/src/server/api/mastodon/endpoints/filter.ts
index 810b8be110..6098a95435 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/filter.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/filter.ts
@@ -1,11 +1,9 @@
-import megalodon, { MegalodonInterface } from '@cutls/megalodon';
+import megalodon, { MegalodonInterface } from "@cutls/megalodon";
import Router from "@koa/router";
-import { koaBody } from 'koa-body';
-import { getClient } from '../ApiMastodonCompatibleService.js';
+import { getClient } from "../ApiMastodonCompatibleService.js";
export function apiFilterMastodon(router: Router): void {
-
- router.get('/v1/filters', async (ctx) => {
+ router.get("/v1/filters", async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
@@ -14,13 +12,13 @@ export function apiFilterMastodon(router: Router): void {
const data = await client.getFilters();
ctx.body = data.data;
} catch (e: any) {
- console.error(e)
+ console.error(e);
ctx.status = 401;
ctx.body = e.response.data;
}
});
- router.get('/v1/filters/:id', async (ctx) => {
+ router.get("/v1/filters/:id", async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
@@ -29,13 +27,13 @@ export function apiFilterMastodon(router: Router): void {
const data = await client.getFilter(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
- console.error(e)
+ console.error(e);
ctx.status = 401;
ctx.body = e.response.data;
}
});
- router.post('/v1/filters', async (ctx) => {
+ router.post("/v1/filters", async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
@@ -44,28 +42,32 @@ export function apiFilterMastodon(router: Router): void {
const data = await client.createFilter(body.phrase, body.context, body);
ctx.body = data.data;
} catch (e: any) {
- console.error(e)
+ console.error(e);
ctx.status = 401;
ctx.body = e.response.data;
}
});
- router.post('/v1/filters/:id', async (ctx) => {
+ router.post("/v1/filters/:id", async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body;
try {
- const data = await client.updateFilter(ctx.params.id, body.phrase, body.context);
+ const data = await client.updateFilter(
+ ctx.params.id,
+ body.phrase,
+ body.context,
+ );
ctx.body = data.data;
} catch (e: any) {
- console.error(e)
+ console.error(e);
ctx.status = 401;
ctx.body = e.response.data;
}
});
- router.delete('/v1/filters/:id', async (ctx) => {
+ router.delete("/v1/filters/:id", async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
@@ -74,10 +76,9 @@ export function apiFilterMastodon(router: Router): void {
const data = await client.deleteFilter(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
- console.error(e)
+ console.error(e);
ctx.status = 401;
ctx.body = e.response.data;
}
});
-
}
diff --git a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts
index 625ff386c1..9c52414ad0 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts
@@ -1,16 +1,15 @@
-import megalodon, { MegalodonInterface } from '@cutls/megalodon';
+import megalodon, { MegalodonInterface } from "@cutls/megalodon";
import Router from "@koa/router";
-import { koaBody } from 'koa-body';
-import { getClient } from '../ApiMastodonCompatibleService.js';
-import { toTextWithReaction } from './timeline.js';
+import { koaBody } from "koa-body";
+import { getClient } from "../ApiMastodonCompatibleService.js";
+import { toTextWithReaction } from "./timeline.js";
function toLimitToInt(q: any) {
- if (q.limit) if (typeof q.limit === 'string') q.limit = parseInt(q.limit, 10)
- return q
+ if (q.limit) if (typeof q.limit === "string") q.limit = parseInt(q.limit, 10);
+ return q;
}
export function apiNotificationsMastodon(router: Router): void {
-
- router.get('/v1/notifications', async (ctx) => {
+ router.get("/v1/notifications", async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
@@ -19,23 +18,26 @@ export function apiNotificationsMastodon(router: Router): void {
const data = await client.getNotifications(toLimitToInt(ctx.query));
const notfs = data.data;
const ret = notfs.map((n) => {
- if(n.type !== 'follow' && n.type !== 'follow_request') {
- if (n.type === 'reaction') n.type = 'favourite'
- n.status = toTextWithReaction(n.status ? [n.status] : [], ctx.hostname)[0]
- return n
- } else {
- return n
- }
- })
+ if (n.type !== "follow" && n.type !== "follow_request") {
+ if (n.type === "reaction") n.type = "favourite";
+ n.status = toTextWithReaction(
+ n.status ? [n.status] : [],
+ ctx.hostname,
+ )[0];
+ return n;
+ } else {
+ return n;
+ }
+ });
ctx.body = ret;
} catch (e: any) {
- console.error(e)
+ console.error(e);
ctx.status = 401;
ctx.body = e.response.data;
}
});
- router.get('/v1/notification/:id', async (ctx) => {
+ router.get("/v1/notification/:id", async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
@@ -43,20 +45,20 @@ export function apiNotificationsMastodon(router: Router): void {
try {
const dataRaw = await client.getNotification(ctx.params.id);
const data = dataRaw.data;
- if(data.type !== 'follow' && data.type !== 'follow_request') {
- if (data.type === 'reaction') data.type = 'favourite'
- ctx.body = toTextWithReaction([data as any], ctx.request.hostname)[0]
+ if (data.type !== "follow" && data.type !== "follow_request") {
+ if (data.type === "reaction") data.type = "favourite";
+ ctx.body = toTextWithReaction([data as any], ctx.request.hostname)[0];
} else {
- ctx.body = data
+ ctx.body = data;
}
} catch (e: any) {
- console.error(e)
+ console.error(e);
ctx.status = 401;
ctx.body = e.response.data;
}
});
- router.post('/v1/notifications/clear', async (ctx) => {
+ router.post("/v1/notifications/clear", async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
@@ -65,13 +67,13 @@ export function apiNotificationsMastodon(router: Router): void {
const data = await client.dismissNotifications();
ctx.body = data.data;
} catch (e: any) {
- console.error(e)
+ console.error(e);
ctx.status = 401;
ctx.body = e.response.data;
}
});
- router.post('/v1/notification/:id/dismiss', async (ctx) => {
+ router.post("/v1/notification/:id/dismiss", async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
@@ -80,10 +82,9 @@ export function apiNotificationsMastodon(router: Router): void {
const data = await client.dismissNotification(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
- console.error(e)
+ console.error(e);
ctx.status = 401;
ctx.body = e.response.data;
}
});
-
}
diff --git a/packages/backend/src/server/api/mastodon/endpoints/search.ts b/packages/backend/src/server/api/mastodon/endpoints/search.ts
index dce3ff57c8..48161733ed 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/search.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/search.ts
@@ -1,25 +1,22 @@
-import megalodon, { MegalodonInterface } from '@cutls/megalodon';
+import megalodon, { MegalodonInterface } from "@cutls/megalodon";
import Router from "@koa/router";
-import { koaBody } from 'koa-body';
-import { getClient } from '../ApiMastodonCompatibleService.js';
+import { getClient } from "../ApiMastodonCompatibleService.js";
export function apiSearchMastodon(router: Router): void {
-
- router.get('/v1/search', async (ctx) => {
+ router.get("/v1/search", async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body;
try {
- const query: any = ctx.query
- const type = query.type || ''
+ const query: any = ctx.query;
+ const type = query.type || "";
const data = await client.search(query.q, type, query);
ctx.body = data.data;
} catch (e: any) {
- console.error(e)
+ console.error(e);
ctx.status = 401;
ctx.body = e.response.data;
}
});
-
}
diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts
index 8dc4ba5f7d..3afd7e5769 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/status.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts
@@ -1,403 +1,483 @@
import Router from "@koa/router";
-import { koaBody } from 'koa-body';
-import megalodon, { MegalodonInterface } from '@cutls/megalodon';
-import { getClient } from '../ApiMastodonCompatibleService.js';
-import fs from 'fs'
-import { pipeline } from 'node:stream';
-import { promisify } from 'node:util';
-import { createTemp } from '@/misc/create-temp.js';
-import { emojiRegex, emojiRegexAtStartToEnd } from '@/misc/emoji-regex.js';
-import axios from 'axios';
+import megalodon, { MegalodonInterface } from "@cutls/megalodon";
+import { getClient } from "../ApiMastodonCompatibleService.js";
+import fs from "fs";
+import { pipeline } from "node:stream";
+import { promisify } from "node:util";
+import { createTemp } from "@/misc/create-temp.js";
+import { emojiRegex, emojiRegexAtStartToEnd } from "@/misc/emoji-regex.js";
+import axios from "axios";
const pump = promisify(pipeline);
export function apiStatusMastodon(router: Router): void {
- router.post('/v1/statuses', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const body: any = ctx.request.body
- const text = body.status
- const removed = text.replace(/@\S+/g, '').replaceAll(' ', '')
- const isDefaultEmoji = emojiRegexAtStartToEnd.test(removed)
- const isCustomEmoji = /^:[a-zA-Z0-9@_]+:$/.test(removed)
- if (body.in_reply_to_id && isDefaultEmoji || isCustomEmoji) {
- const a = await client.createEmojiReaction(body.in_reply_to_id, removed)
- ctx.body = a.data
- }
- if (body.in_reply_to_id && removed === '/unreact') {
- try {
- const id = body.in_reply_to_id
- const post = await client.getStatus(id)
- const react = post.data.emoji_reactions.filter((e) => e.me)[0].name
- const data = await client.deleteEmojiReaction(id, react);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- }
- if (!body.media_ids) body.media_ids = undefined
- if (body.media_ids && !body.media_ids.length) body.media_ids = undefined
- const data = await client.postStatus(text, body);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- router.get<{ Params: { id: string } }>('/v1/statuses/:id', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.getStatus(ctx.params.id);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- router.delete<{ Params: { id: string } }>('/v1/statuses/:id', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.deleteStatus(ctx.params.id);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- interface IReaction {
- id: string
- createdAt: string
- user: MisskeyEntity.User,
- type: string
- }
- router.get<{ Params: { id: string } }>('/v1/statuses/:id/context', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const id = ctx.params.id
- const data = await client.getStatusContext(id, ctx.query as any);
- const status = await client.getStatus(id);
- const reactionsAxios = await axios.get(`${BASE_URL}/api/notes/reactions?noteId=${id}`)
- const reactions: IReaction[] = reactionsAxios.data
- const text = reactions.map((r) => `${r.type.replace('@.', '')} ${r.user.username}`).join('
')
- data.data.descendants.unshift(statusModel(status.data.id, status.data.account.id, status.data.emojis, text))
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- router.get<{ Params: { id: string } }>('/v1/statuses/:id/reblogged_by', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.getStatusRebloggedBy(ctx.params.id);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- router.get<{ Params: { id: string } }>('/v1/statuses/:id/favourited_by', async (ctx, reply) => {
- ctx.body = []
- });
- router.post<{ Params: { id: string } }>('/v1/statuses/:id/favourite', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- const react = await getFirstReaction(BASE_URL, accessTokens);
- try {
- const a = await client.createEmojiReaction(ctx.params.id, react) as any;
- //const data = await client.favouriteStatus(ctx.params.id) as any;
- ctx.body = a.data;
- } catch (e: any) {
- console.error(e)
- console.error(e.response.data)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- router.post<{ Params: { id: string } }>('/v1/statuses/:id/unfavourite', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- const react = await getFirstReaction(BASE_URL, accessTokens);
- try {
- const data = await client.deleteEmojiReaction(ctx.params.id, react);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
+ router.post("/v1/statuses", async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const body: any = ctx.request.body;
+ const text = body.status;
+ const removed = text.replace(/@\S+/g, "").replaceAll(" ", "");
+ const isDefaultEmoji = emojiRegexAtStartToEnd.test(removed);
+ const isCustomEmoji = /^:[a-zA-Z0-9@_]+:$/.test(removed);
+ if ((body.in_reply_to_id && isDefaultEmoji) || isCustomEmoji) {
+ const a = await client.createEmojiReaction(
+ body.in_reply_to_id,
+ removed,
+ );
+ ctx.body = a.data;
+ }
+ if (body.in_reply_to_id && removed === "/unreact") {
+ try {
+ const id = body.in_reply_to_id;
+ const post = await client.getStatus(id);
+ const react = post.data.emoji_reactions.filter((e) => e.me)[0].name;
+ const data = await client.deleteEmojiReaction(id, react);
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ }
+ if (!body.media_ids) body.media_ids = undefined;
+ if (body.media_ids && !body.media_ids.length) body.media_ids = undefined;
+ const data = await client.postStatus(text, body);
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ });
+ router.get<{ Params: { id: string } }>(
+ "/v1/statuses/:id",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.getStatus(ctx.params.id);
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
+ router.delete<{ Params: { id: string } }>(
+ "/v1/statuses/:id",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.deleteStatus(ctx.params.id);
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
+ interface IReaction {
+ id: string;
+ createdAt: string;
+ user: MisskeyEntity.User;
+ type: string;
+ }
+ router.get<{ Params: { id: string } }>(
+ "/v1/statuses/:id/context",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const id = ctx.params.id;
+ const data = await client.getStatusContext(id, ctx.query as any);
+ const status = await client.getStatus(id);
+ const reactionsAxios = await axios.get(
+ `${BASE_URL}/api/notes/reactions?noteId=${id}`,
+ );
+ const reactions: IReaction[] = reactionsAxios.data;
+ const text = reactions
+ .map((r) => `${r.type.replace("@.", "")} ${r.user.username}`)
+ .join("
");
+ data.data.descendants.unshift(
+ statusModel(
+ status.data.id,
+ status.data.account.id,
+ status.data.emojis,
+ text,
+ ),
+ );
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
+ router.get<{ Params: { id: string } }>(
+ "/v1/statuses/:id/reblogged_by",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.getStatusRebloggedBy(ctx.params.id);
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
+ router.get<{ Params: { id: string } }>(
+ "/v1/statuses/:id/favourited_by",
+ async (ctx, reply) => {
+ ctx.body = [];
+ },
+ );
+ router.post<{ Params: { id: string } }>(
+ "/v1/statuses/:id/favourite",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ const react = await getFirstReaction(BASE_URL, accessTokens);
+ try {
+ const a = (await client.createEmojiReaction(
+ ctx.params.id,
+ react,
+ )) as any;
+ //const data = await client.favouriteStatus(ctx.params.id) as any;
+ ctx.body = a.data;
+ } catch (e: any) {
+ console.error(e);
+ console.error(e.response.data);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
+ router.post<{ Params: { id: string } }>(
+ "/v1/statuses/:id/unfavourite",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ const react = await getFirstReaction(BASE_URL, accessTokens);
+ try {
+ const data = await client.deleteEmojiReaction(ctx.params.id, react);
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
- router.post<{ Params: { id: string } }>('/v1/statuses/:id/reblog', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.reblogStatus(ctx.params.id);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
+ router.post<{ Params: { id: string } }>(
+ "/v1/statuses/:id/reblog",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.reblogStatus(ctx.params.id);
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
- router.post<{ Params: { id: string } }>('/v1/statuses/:id/unreblog', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.unreblogStatus(ctx.params.id);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
+ router.post<{ Params: { id: string } }>(
+ "/v1/statuses/:id/unreblog",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.unreblogStatus(ctx.params.id);
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
- router.post<{ Params: { id: string } }>('/v1/statuses/:id/bookmark', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.bookmarkStatus(ctx.params.id);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
+ router.post<{ Params: { id: string } }>(
+ "/v1/statuses/:id/bookmark",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.bookmarkStatus(ctx.params.id);
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
- router.post<{ Params: { id: string } }>('/v1/statuses/:id/unbookmark', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.unbookmarkStatus(ctx.params.id) as any;
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
+ router.post<{ Params: { id: string } }>(
+ "/v1/statuses/:id/unbookmark",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = (await client.unbookmarkStatus(ctx.params.id)) as any;
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
- router.post<{ Params: { id: string } }>('/v1/statuses/:id/pin', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.pinStatus(ctx.params.id);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
-
- router.post<{ Params: { id: string } }>('/v1/statuses/:id/unpin', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.unpinStatus(ctx.params.id);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- router.post('/v1/media', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const multipartData = await ctx.file;
- if (!multipartData) {
- ctx.body = { error: 'No image' };
- return;
- }
- const [path] = await createTemp();
- await pump(multipartData.buffer, fs.createWriteStream(path));
- const image = fs.readFileSync(path);
- const data = await client.uploadMedia(image);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- router.post('/v2/media', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const multipartData = await ctx.file;
- if (!multipartData) {
- ctx.body = { error: 'No image' };
- return;
- }
- const [path] = await createTemp();
- await pump(multipartData.buffer, fs.createWriteStream(path));
- const image = fs.readFileSync(path);
- const data = await client.uploadMedia(image);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- router.get<{ Params: { id: string } }>('/v1/media/:id', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.getMedia(ctx.params.id);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- router.put<{ Params: { id: string } }>('/v1/media/:id', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.updateMedia(ctx.params.id, ctx.request.body as any);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- router.get<{ Params: { id: string } }>('/v1/polls/:id', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.getPoll(ctx.params.id);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- router.post<{ Params: { id: string } }>('/v1/polls/:id/votes', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.votePoll(ctx.params.id, (ctx.request.body as any).choices);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
+ router.post<{ Params: { id: string } }>(
+ "/v1/statuses/:id/pin",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.pinStatus(ctx.params.id);
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
+ router.post<{ Params: { id: string } }>(
+ "/v1/statuses/:id/unpin",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.unpinStatus(ctx.params.id);
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
+ router.post("/v1/media", async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const multipartData = await ctx.file;
+ if (!multipartData) {
+ ctx.body = { error: "No image" };
+ return;
+ }
+ const [path] = await createTemp();
+ await pump(multipartData.buffer, fs.createWriteStream(path));
+ const image = fs.readFileSync(path);
+ const data = await client.uploadMedia(image);
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ });
+ router.post("/v2/media", async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const multipartData = await ctx.file;
+ if (!multipartData) {
+ ctx.body = { error: "No image" };
+ return;
+ }
+ const [path] = await createTemp();
+ await pump(multipartData.buffer, fs.createWriteStream(path));
+ const image = fs.readFileSync(path);
+ const data = await client.uploadMedia(image);
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ });
+ router.get<{ Params: { id: string } }>(
+ "/v1/media/:id",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.getMedia(ctx.params.id);
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
+ router.put<{ Params: { id: string } }>(
+ "/v1/media/:id",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.updateMedia(
+ ctx.params.id,
+ ctx.request.body as any,
+ );
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
+ router.get<{ Params: { id: string } }>(
+ "/v1/polls/:id",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.getPoll(ctx.params.id);
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
+ router.post<{ Params: { id: string } }>(
+ "/v1/polls/:id/votes",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.votePoll(
+ ctx.params.id,
+ (ctx.request.body as any).choices,
+ );
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
}
-async function getFirstReaction(BASE_URL: string, accessTokens: string | undefined) {
- const accessTokenArr = accessTokens?.split(' ') ?? [null];
- const accessToken = accessTokenArr[accessTokenArr.length - 1];
- let react = '👍'
- try {
- const api = await axios.post(`${BASE_URL}/api/i/registry/get-unsecure`, {
- scope: ['client', 'base'],
- key: 'reactions',
- i: accessToken
- })
- const reactRaw = api.data
- react = Array.isArray(reactRaw) ? api.data[0] : '👍'
- console.log(api.data)
- return react
- } catch (e) {
- return react
- }
+async function getFirstReaction(
+ BASE_URL: string,
+ accessTokens: string | undefined,
+) {
+ const accessTokenArr = accessTokens?.split(" ") ?? [null];
+ const accessToken = accessTokenArr[accessTokenArr.length - 1];
+ let react = "👍";
+ try {
+ const api = await axios.post(`${BASE_URL}/api/i/registry/get-unsecure`, {
+ scope: ["client", "base"],
+ key: "reactions",
+ i: accessToken,
+ });
+ const reactRaw = api.data;
+ react = Array.isArray(reactRaw) ? api.data[0] : "👍";
+ console.log(api.data);
+ return react;
+ } catch (e) {
+ return react;
+ }
}
-export function statusModel(id: string | null, acctId: string | null, emojis: MastodonEntity.Emoji[], content: string) {
- const now = "1970-01-02T00:00:00.000Z"
- return {
- id: '9atm5frjhb',
- uri: 'https://http.cat/404', // ""
- url: 'https://http.cat/404', // "",
- account: {
- id: '9arzuvv0sw',
- username: 'ReactionBot',
- acct: 'ReactionBot',
- display_name: 'ReactionOfThisPost',
- locked: false,
- created_at: now,
- followers_count: 0,
- following_count: 0,
- statuses_count: 0,
- note: '',
- url: 'https://http.cat/404',
- avatar: 'https://http.cat/404',
- avatar_static: 'https://http.cat/404',
- header: 'https://http.cat/404', // ""
- header_static: 'https://http.cat/404', // ""
- emojis: [],
- fields: [],
- moved: null,
- bot: false,
- },
- in_reply_to_id: id,
- in_reply_to_account_id: acctId,
- reblog: null,
- content: `${content}
`,
- plain_content: null,
- created_at: now,
- emojis: emojis,
- replies_count: 0,
- reblogs_count: 0,
- favourites_count: 0,
- favourited: false,
- reblogged: false,
- muted: false,
- sensitive: false,
- spoiler_text: '',
- visibility: 'public' as const,
- media_attachments: [],
- mentions: [],
- tags: [],
- card: null,
- poll: null,
- application: null,
- language: null,
- pinned: false,
- emoji_reactions: [],
- bookmarked: false,
- quote: false,
- }
-}
+export function statusModel(
+ id: string | null,
+ acctId: string | null,
+ emojis: MastodonEntity.Emoji[],
+ content: string,
+) {
+ const now = "1970-01-02T00:00:00.000Z";
+ return {
+ id: "9atm5frjhb",
+ uri: "https://http.cat/404", // ""
+ url: "https://http.cat/404", // "",
+ account: {
+ id: "9arzuvv0sw",
+ username: "ReactionBot",
+ acct: "ReactionBot",
+ display_name: "ReactionOfThisPost",
+ locked: false,
+ created_at: now,
+ followers_count: 0,
+ following_count: 0,
+ statuses_count: 0,
+ note: "",
+ url: "https://http.cat/404",
+ avatar: "https://http.cat/404",
+ avatar_static: "https://http.cat/404",
+ header: "https://http.cat/404", // ""
+ header_static: "https://http.cat/404", // ""
+ emojis: [],
+ fields: [],
+ moved: null,
+ bot: false,
+ },
+ in_reply_to_id: id,
+ in_reply_to_account_id: acctId,
+ reblog: null,
+ content: `${content}
`,
+ plain_content: null,
+ created_at: now,
+ emojis: emojis,
+ replies_count: 0,
+ reblogs_count: 0,
+ favourites_count: 0,
+ favourited: false,
+ reblogged: false,
+ muted: false,
+ sensitive: false,
+ spoiler_text: "",
+ visibility: "public" as const,
+ media_attachments: [],
+ mentions: [],
+ tags: [],
+ card: null,
+ poll: null,
+ application: null,
+ language: null,
+ pinned: false,
+ emoji_reactions: [],
+ bookmarked: false,
+ quote: false,
+ };
+}
diff --git a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts
index 3fdb6ce881..9caf431143 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts
@@ -1,246 +1,305 @@
import Router from "@koa/router";
-import { koaBody } from 'koa-body';
-import megalodon, { Entity, MegalodonInterface } from '@cutls/megalodon';
-import { getClient } from '../ApiMastodonCompatibleService.js'
-import { statusModel } from './status.js';
-import Autolinker from 'autolinker';
+import megalodon, { Entity, MegalodonInterface } from "@cutls/megalodon";
+import { getClient } from "../ApiMastodonCompatibleService.js";
+import { statusModel } from "./status.js";
+import Autolinker from "autolinker";
import { ParsedUrlQuery } from "querystring";
export function toLimitToInt(q: ParsedUrlQuery) {
- if (q.limit) if (typeof q.limit === 'string') q.limit = parseInt(q.limit, 10).toString()
- return q
+ if (q.limit)
+ if (typeof q.limit === "string") q.limit = parseInt(q.limit, 10).toString();
+ return q;
}
export function toTextWithReaction(status: Entity.Status[], host: string) {
return status.map((t) => {
- if (!t) return statusModel(null, null, [], 'no content')
- if (!t.emoji_reactions) return t
- if (t.reblog) t.reblog = toTextWithReaction([t.reblog], host)[0]
- const reactions = t.emoji_reactions.map((r) => `${r.name.replace('@.', '')} (${r.count}${r.me ? "* " : ''})`);
- //t.emojis = getEmoji(t.content, host)
- t.content = `${autoLinker(t.content, host)}
${reactions.join(', ')}
`
- return t
- })
+ if (!t) return statusModel(null, null, [], "no content");
+ if (!t.emoji_reactions) return t;
+ if (t.reblog) t.reblog = toTextWithReaction([t.reblog], host)[0];
+ const reactions = t.emoji_reactions.map(
+ (r) => `${r.name.replace("@.", "")} (${r.count}${r.me ? "* " : ""})`,
+ );
+ //t.emojis = getEmoji(t.content, host)
+ t.content = `${autoLinker(t.content, host)}
${reactions.join(
+ ", ",
+ )}
`;
+ return t;
+ });
}
export function autoLinker(input: string, host: string) {
return Autolinker.link(input, {
- hashtag: 'twitter',
- mention: 'twitter',
- email: false,
- stripPrefix: false,
- replaceFn : function (match) {
- switch(match.type) {
- case 'url':
- return true
- case 'mention':
- console.log("Mention: ", match.getMention());
- console.log("Mention Service Name: ", match.getServiceName());
- return `@${match.getMention()}`;
- case 'hashtag':
- console.log("Hashtag: ", match.getHashtag());
- return `#${match.getHashtag()}`;
- }
- return false
- }
- } );
+ hashtag: "twitter",
+ mention: "twitter",
+ email: false,
+ stripPrefix: false,
+ replaceFn: function (match) {
+ switch (match.type) {
+ case "url":
+ return true;
+ case "mention":
+ console.log("Mention: ", match.getMention());
+ console.log("Mention Service Name: ", match.getServiceName());
+ return `@${match.getMention()}`;
+ case "hashtag":
+ console.log("Hashtag: ", match.getHashtag());
+ return `#${match.getHashtag()}`;
+ }
+ return false;
+ },
+ });
}
export function apiTimelineMastodon(router: Router): void {
- router.get('/v1/timelines/public', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const query: any = ctx.query
- const data = query.local ? await client.getLocalTimeline(toLimitToInt(query)) : await client.getPublicTimeline(toLimitToInt(query));
- ctx.body = toTextWithReaction(data.data, ctx.hostname);
- } catch (e: any) {
- console.error(e)
- console.error(e.response.data)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- router.get<{ Params: { hashtag: string } }>('/v1/timelines/tag/:hashtag', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.getTagTimeline(ctx.params.hashtag, toLimitToInt(ctx.query));
- ctx.body = toTextWithReaction(data.data, ctx.hostname);
- } catch (e: any) {
- console.error(e)
- console.error(e.response.data)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- router.get<{ Params: { hashtag: string } }>('/v1/timelines/home', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.getHomeTimeline(toLimitToInt(ctx.query));
- ctx.body = toTextWithReaction(data.data, ctx.hostname);
- } catch (e: any) {
- console.error(e)
- console.error(e.response.data)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- router.get<{ Params: { listId: string } }>('/v1/timelines/list/:listId', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.getListTimeline(ctx.params.listId, toLimitToInt(ctx.query));
- ctx.body = toTextWithReaction(data.data, ctx.hostname);
- } catch (e: any) {
- console.error(e)
- console.error(e.response.data)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- router.get('/v1/conversations', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.getConversationTimeline(toLimitToInt(ctx.query));
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- console.error(e.response.data)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- router.get('/v1/lists', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.getLists();
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- console.error(e.response.data)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- router.get<{ Params: { id: string } }>('/v1/lists/:id', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.getList(ctx.params.id);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- console.error(e.response.data)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- router.post('/v1/lists', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.createList((ctx.query as any).title);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- console.error(e.response.data)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- router.put<{ Params: { id: string } }>('/v1/lists/:id', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.updateList(ctx.params.id, ctx.query as any);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- console.error(e.response.data)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- router.delete<{ Params: { id: string } }>('/v1/lists/:id', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.deleteList(ctx.params.id);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- console.error(e.response.data)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- router.get<{ Params: { id: string } }>('/v1/lists/:id/accounts', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.getAccountsInList(ctx.params.id, ctx.query as any);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- console.error(e.response.data)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- router.post<{ Params: { id: string } }>('/v1/lists/:id/accounts', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.addAccountsToList(ctx.params.id, (ctx.query as any).account_ids);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- console.error(e.response.data)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
- router.delete<{ Params: { id: string } }>('/v1/lists/:id/accounts', async (ctx, reply) => {
- const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
- const accessTokens = ctx.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
- try {
- const data = await client.deleteAccountsFromList(ctx.params.id, (ctx.query as any).account_ids);
- ctx.body = data.data;
- } catch (e: any) {
- console.error(e)
- console.error(e.response.data)
- ctx.status = (401);
- ctx.body = e.response.data;
- }
- });
+ router.get("/v1/timelines/public", async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const query: any = ctx.query;
+ const data = query.local
+ ? await client.getLocalTimeline(toLimitToInt(query))
+ : await client.getPublicTimeline(toLimitToInt(query));
+ ctx.body = toTextWithReaction(data.data, ctx.hostname);
+ } catch (e: any) {
+ console.error(e);
+ console.error(e.response.data);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ });
+ router.get<{ Params: { hashtag: string } }>(
+ "/v1/timelines/tag/:hashtag",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.getTagTimeline(
+ ctx.params.hashtag,
+ toLimitToInt(ctx.query),
+ );
+ ctx.body = toTextWithReaction(data.data, ctx.hostname);
+ } catch (e: any) {
+ console.error(e);
+ console.error(e.response.data);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
+ router.get<{ Params: { hashtag: string } }>(
+ "/v1/timelines/home",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.getHomeTimeline(toLimitToInt(ctx.query));
+ ctx.body = toTextWithReaction(data.data, ctx.hostname);
+ } catch (e: any) {
+ console.error(e);
+ console.error(e.response.data);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
+ router.get<{ Params: { listId: string } }>(
+ "/v1/timelines/list/:listId",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.getListTimeline(
+ ctx.params.listId,
+ toLimitToInt(ctx.query),
+ );
+ ctx.body = toTextWithReaction(data.data, ctx.hostname);
+ } catch (e: any) {
+ console.error(e);
+ console.error(e.response.data);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
+ router.get("/v1/conversations", async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.getConversationTimeline(
+ toLimitToInt(ctx.query),
+ );
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ console.error(e.response.data);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ });
+ router.get("/v1/lists", async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.getLists();
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ console.error(e.response.data);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ });
+ router.get<{ Params: { id: string } }>(
+ "/v1/lists/:id",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.getList(ctx.params.id);
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ console.error(e.response.data);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
+ router.post("/v1/lists", async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.createList((ctx.query as any).title);
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ console.error(e.response.data);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ });
+ router.put<{ Params: { id: string } }>(
+ "/v1/lists/:id",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.updateList(ctx.params.id, ctx.query as any);
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ console.error(e.response.data);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
+ router.delete<{ Params: { id: string } }>(
+ "/v1/lists/:id",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.deleteList(ctx.params.id);
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ console.error(e.response.data);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
+ router.get<{ Params: { id: string } }>(
+ "/v1/lists/:id/accounts",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.getAccountsInList(
+ ctx.params.id,
+ ctx.query as any,
+ );
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ console.error(e.response.data);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
+ router.post<{ Params: { id: string } }>(
+ "/v1/lists/:id/accounts",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.addAccountsToList(
+ ctx.params.id,
+ (ctx.query as any).account_ids,
+ );
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ console.error(e.response.data);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
+ router.delete<{ Params: { id: string } }>(
+ "/v1/lists/:id/accounts",
+ async (ctx, reply) => {
+ const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+ const accessTokens = ctx.headers.authorization;
+ const client = getClient(BASE_URL, accessTokens);
+ try {
+ const data = await client.deleteAccountsFromList(
+ ctx.params.id,
+ (ctx.query as any).account_ids,
+ );
+ ctx.body = data.data;
+ } catch (e: any) {
+ console.error(e);
+ console.error(e.response.data);
+ ctx.status = 401;
+ ctx.body = e.response.data;
+ }
+ },
+ );
}
function escapeHTML(str: string) {
- if (!str) {
- return ''
- }
- return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''')
+ if (!str) {
+ return "";
+ }
+ return str
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """)
+ .replace(/'/g, "'");
}
function nl2br(str: string) {
- if (!str) {
- return ''
- }
- str = str.replace(/\r\n/g, '
')
- str = str.replace(/(\n|\r)/g, '
')
- return str
+ if (!str) {
+ return "";
+ }
+ str = str.replace(/\r\n/g, "
");
+ str = str.replace(/(\n|\r)/g, "
");
+ return str;
}
diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts
index 5be8ab109e..167aa614c8 100644
--- a/packages/backend/src/server/api/stream/index.ts
+++ b/packages/backend/src/server/api/stream/index.ts
@@ -152,7 +152,7 @@ export default class Connection {
} catch (e) {
return;
}
-
+
const simpleObj = objs[0];
if (simpleObj.stream) {
// is Mastodon Compatible
diff --git a/packages/backend/src/server/api/streaming.ts b/packages/backend/src/server/api/streaming.ts
index 4ccad96e81..14e07b7487 100644
--- a/packages/backend/src/server/api/streaming.ts
+++ b/packages/backend/src/server/api/streaming.ts
@@ -16,7 +16,7 @@ export const initializeStreamingServer = (server: http.Server) => {
ws.on("request", async (request) => {
const q = request.resourceURL.query as ParsedUrlQuery;
- const headers = request.httpRequest.headers['sec-websocket-protocol'] || '';
+ const headers = request.httpRequest.headers["sec-websocket-protocol"] || "";
const cred = q.i || q.access_token || headers;
const accessToken = cred.toString();
@@ -48,9 +48,17 @@ export const initializeStreamingServer = (server: http.Server) => {
redisClient.on("message", onRedisMessage);
const host = `https://${request.host}`;
const prepareStream = q.stream?.toString();
- console.log('start', q);
+ console.log("start", q);
- const main = new MainStreamConnection(connection, ev, user, app, host, accessToken, prepareStream);
+ const main = new MainStreamConnection(
+ connection,
+ ev,
+ user,
+ app,
+ host,
+ accessToken,
+ prepareStream,
+ );
const intervalId = user
? setInterval(() => {
diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts
index 6ade50d18d..cd495971ef 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -20,8 +20,7 @@ import { createTemp } from "@/misc/create-temp.js";
import { publishMainStream } from "@/services/stream.js";
import * as Acct from "@/misc/acct.js";
import { envOption } from "@/env.js";
-import { koaBody } from 'koa-body';
-import megalodon, { MegalodonInterface } from '@cutls/megalodon';
+import megalodon, { MegalodonInterface } from "@cutls/megalodon";
import activityPub from "./activitypub.js";
import nodeinfo from "./nodeinfo.js";
import wellKnown from "./well-known.js";
@@ -30,6 +29,7 @@ import fileServer from "./file/index.js";
import proxyServer from "./proxy/index.js";
import webServer from "./web/index.js";
import { initializeStreamingServer } from "./api/streaming.js";
+import { koaBody } from "koa-body";
export const serverLogger = new Logger("server", "gray", false);
@@ -70,6 +70,11 @@ app.use(mount("/proxy", proxyServer));
// Init router
const router = new Router();
+const mastoRouter = new Router();
+
+mastoRouter.use(koaBody({
+ urlencoded: true
+}));
// Routing
router.use(activityPub.routes());
@@ -135,26 +140,42 @@ router.get("/verify-email/:code", async (ctx) => {
}
});
-router.get("/oauth/authorize", async (ctx) => {
+mastoRouter.get("/oauth/authorize", async (ctx) => {
const client_id = ctx.request.query.client_id;
console.log(ctx.request.req);
- ctx.redirect(Buffer.from(client_id?.toString() || '', 'base64').toString());
+ ctx.redirect(Buffer.from(client_id?.toString() || "", "base64").toString());
});
-router.post("/oauth/token", async (ctx) => {
+mastoRouter.post("/oauth/token", async (ctx) => {
const body: any = ctx.request.body;
+ let client_id: any = ctx.request.query.client_id;
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const generator = (megalodon as any).default;
- const client = generator('misskey', BASE_URL, null) as MegalodonInterface;
- const m = body.code.match(/^[a-zA-Z0-9-]+/);
- if (!m.length) return { error: 'Invalid code' }
+ const client = generator("misskey", BASE_URL, null) as MegalodonInterface;
+ let m = null;
+ if (body.code) {
+ m = body.code.match(/^[a-zA-Z0-9-]+/);
+ if (!m.length) {
+ ctx.body = { error: "Invalid code" };
+ return;
+ }
+ }
+ if (client_id instanceof Array) {
+ client_id = client_id.toString();;
+ } else if (!client_id) {
+ client_id = null;
+ }
try {
- const atData = await client.fetchAccessToken(null, body.client_secret, m[0]);
+ const atData = await client.fetchAccessToken(
+ client_id,
+ body.client_secret,
+ m ? m[0] : '',
+ );
ctx.body = {
access_token: atData.accessToken,
- token_type: 'Bearer',
- scope: 'read write follow',
- created_at: new Date().getTime() / 1000
+ token_type: "Bearer",
+ scope: "read write follow",
+ created_at: Math.floor(new Date().getTime() / 1000),
};
} catch (err: any) {
console.error(err);
@@ -164,6 +185,7 @@ router.post("/oauth/token", async (ctx) => {
});
// Register router
+app.use(mastoRouter.routes());
app.use(router.routes());
app.use(mount(webServer));
diff --git a/packages/backend/src/server/web/url-preview.ts b/packages/backend/src/server/web/url-preview.ts
index d7da4e72ce..c9f3b6cac9 100644
--- a/packages/backend/src/server/web/url-preview.ts
+++ b/packages/backend/src/server/web/url-preview.ts
@@ -44,6 +44,23 @@ export const urlPreviewHandler = async (ctx: Koa.Context) => {
logger.succ(`Got preview of ${url}: ${summary.title}`);
+ if (
+ summary.url &&
+ !(summary.url.startsWith("http://") || summary.url.startsWith("https://"))
+ ) {
+ throw new Error("unsupported schema included");
+ }
+
+ if (
+ summary.player?.url &&
+ !(
+ summary.player.url.startsWith("http://") ||
+ summary.player.url.startsWith("https://")
+ )
+ ) {
+ throw new Error("unsupported schema included");
+ }
+
summary.icon = wrap(summary.icon);
summary.thumbnail = wrap(summary.thumbnail);
diff --git a/packages/client/assets/dummy.png b/packages/client/assets/dummy.png
new file mode 100644
index 0000000000..ee22bdb3c8
Binary files /dev/null and b/packages/client/assets/dummy.png differ
diff --git a/packages/client/assets/dummy_original.png b/packages/client/assets/dummy_original.png
new file mode 100644
index 0000000000..55c1c595dc
Binary files /dev/null and b/packages/client/assets/dummy_original.png differ
diff --git a/packages/client/src/components/MkUrlPreview.vue b/packages/client/src/components/MkUrlPreview.vue
index ef65cb7960..865d4bcbe6 100644
--- a/packages/client/src/components/MkUrlPreview.vue
+++ b/packages/client/src/components/MkUrlPreview.vue
@@ -67,6 +67,7 @@ const embedId = `embed${Math.random().toString().replace(/\D/,'')}`;
let tweetHeight = $ref(150);
const requestUrl = new URL(props.url);
+if (!['http:', 'https:'].includes(requestUrl.protocol)) throw new Error('invalid url');
if (requestUrl.hostname === 'twitter.com' || requestUrl.hostname === 'mobile.twitter.com') {
const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/);
diff --git a/packages/client/src/components/global/MkUrl.vue b/packages/client/src/components/global/MkUrl.vue
index 2d328211d5..d22c0b2999 100644
--- a/packages/client/src/components/global/MkUrl.vue
+++ b/packages/client/src/components/global/MkUrl.vue
@@ -33,6 +33,7 @@ const props = defineProps<{
const self = props.url.startsWith(local);
const url = new URL(props.url);
+if (!['http:', 'https:'].includes(url.protocol)) throw new Error('invalid url');
const el = ref();
useTooltip(el, (showing) => {
diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts
index 3f040541f8..ddfcc3de17 100644
--- a/packages/client/src/components/mfm.ts
+++ b/packages/client/src/components/mfm.ts
@@ -377,12 +377,7 @@ export default defineComponent({
case "quote": {
if (!this.nowrap) {
- return [
- h(
- "blockquote",
- genEl(token.children),
- ),
- ];
+ return [h("blockquote", genEl(token.children))];
} else {
return [
h(
diff --git a/packages/client/src/navbar.ts b/packages/client/src/navbar.ts
index 5b116cee1b..c8c4556500 100644
--- a/packages/client/src/navbar.ts
+++ b/packages/client/src/navbar.ts
@@ -5,10 +5,10 @@ import * as os from "@/os";
import { i18n } from "@/i18n";
import { ui } from "@/config";
import { unisonReload } from "@/scripts/unison-reload";
-import { defaultStore } from '@/store';
-import { instance } from '@/instance';
-import { host } from '@/config';
-import XTutorial from '@/components/MkTutorialDialog.vue';
+import { defaultStore } from "@/store";
+import { instance } from "@/instance";
+import { host } from "@/config";
+import XTutorial from "@/components/MkTutorialDialog.vue";
export const navbarItemDef = reactive({
notifications: {
@@ -152,54 +152,68 @@ export const navbarItemDef = reactive({
title: "help",
icon: "ph-question-bold ph-lg",
action: (ev) => {
- os.popupMenu([{
- text: instance.name ?? host,
- type: 'label',
- }, {
- type: 'link',
- text: i18n.ts.instanceInfo,
- icon: 'ph-info-bold ph-lg',
- to: '/about',
- }, {
- type: 'link',
- text: i18n.ts.aboutMisskey,
- icon: 'ph-lightbulb-bold ph-lg',
- to: '/about-calckey',
- }, {
- type: 'link',
- text: i18n.ts._apps.apps,
- icon: 'ph-device-mobile-bold ph-lg',
- to: '/apps',
- }, {
- type: 'button',
- action: async () => {
- defaultStore.set('tutorial', 0);
- os.popup(XTutorial, {}, {}, 'closed');
+ os.popupMenu(
+ [
+ {
+ text: instance.name ?? host,
+ type: "label",
},
- text: i18n.ts.replayTutorial,
- icon: 'ph-circle-wavy-question-bold ph-lg',
- }, null, {
- type: 'parent',
- text: i18n.ts.developer,
- icon: 'ph-code-bold ph-lg',
- children: [{
- type: 'link',
- to: '/api-console',
- text: 'API Console',
- icon: 'ph-terminal-window-bold ph-lg',
- }, {
- text: i18n.ts.document,
- icon: 'ph-file-doc-bold ph-lg',
- action: () => {
- window.open('/api-doc', '_blank');
+ {
+ type: "link",
+ text: i18n.ts.instanceInfo,
+ icon: "ph-info-bold ph-lg",
+ to: "/about",
+ },
+ {
+ type: "link",
+ text: i18n.ts.aboutMisskey,
+ icon: "ph-lightbulb-bold ph-lg",
+ to: "/about-calckey",
+ },
+ {
+ type: "link",
+ text: i18n.ts._apps.apps,
+ icon: "ph-device-mobile-bold ph-lg",
+ to: "/apps",
+ },
+ {
+ type: "button",
+ action: async () => {
+ defaultStore.set("tutorial", 0);
+ os.popup(XTutorial, {}, {}, "closed");
},
- }, {
- type: 'link',
- to: '/scratchpad',
- text: 'AiScript Scratchpad',
- icon: 'ph-scribble-loop-bold ph-lg',
- }]
- }], ev.currentTarget ?? ev.target,
+ text: i18n.ts.replayTutorial,
+ icon: "ph-circle-wavy-question-bold ph-lg",
+ },
+ null,
+ {
+ type: "parent",
+ text: i18n.ts.developer,
+ icon: "ph-code-bold ph-lg",
+ children: [
+ {
+ type: "link",
+ to: "/api-console",
+ text: "API Console",
+ icon: "ph-terminal-window-bold ph-lg",
+ },
+ {
+ text: i18n.ts.document,
+ icon: "ph-file-doc-bold ph-lg",
+ action: () => {
+ window.open("/api-doc", "_blank");
+ },
+ },
+ {
+ type: "link",
+ to: "/scratchpad",
+ text: "AiScript Scratchpad",
+ icon: "ph-scribble-loop-bold ph-lg",
+ },
+ ],
+ },
+ ],
+ ev.currentTarget ?? ev.target,
);
},
},
diff --git a/packages/client/src/pages/auth.vue b/packages/client/src/pages/auth.vue
index bb3c54bd32..9fc04d4f49 100644
--- a/packages/client/src/pages/auth.vue
+++ b/packages/client/src/pages/auth.vue
@@ -80,6 +80,8 @@ export default defineComponent({
this.state = 'accepted';
const getUrlParams = () => window.location.search.substring(1).split('&').reduce((result, query) => { const [k, v] = query.split('='); result[k] = decodeURI(v); return result; }, {});
if (this.session.app.callbackUrl) {
+ const url = new URL(this.session.app.callbackUrl);
+ if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(url.protocol)) throw new Error('invalid url');
location.href = `${this.session.app.callbackUrl}?token=${this.session.token}&code=${this.session.token}&state=${getUrlParams().state || ''}`;
}
}, onLogin(res) {
diff --git a/packages/client/src/pages/instance-info.vue b/packages/client/src/pages/instance-info.vue
index 27f08bdb5d..59547a3cbd 100644
--- a/packages/client/src/pages/instance-info.vue
+++ b/packages/client/src/pages/instance-info.vue
@@ -13,7 +13,7 @@