Merge branch 'develop' of https://codeberg.org/Daikei/calckey into develop

This commit is contained in:
Kio-td 2023-02-11 06:26:31 -05:00
commit 93444e1944
37 changed files with 1516 additions and 1189 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "calckey", "name": "calckey",
"version": "13.1.3-rc", "version": "13.2.0-dev",
"codename": "aqua", "codename": "aqua",
"repository": { "repository": {
"type": "git", "type": "git",

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View file

@ -197,7 +197,10 @@ export const NoteRepository = db.getRepository(Note).extend({
.map((x) => decodeReaction(x).reaction) .map((x) => decodeReaction(x).reaction)
.map((x) => x.replace(/:/g, "")); .map((x) => x.replace(/:/g, ""));
const noteEmoji = await populateEmojis(note.emojis.concat(reactionEmojiNames), host); const noteEmoji = await populateEmojis(
note.emojis.concat(reactionEmojiNames),
host,
);
const reactionEmoji = await populateEmojis(reactionEmojiNames, host); const reactionEmoji = await populateEmojis(reactionEmojiNames, host);
const packed: Packed<"Note"> = await awaitAll({ const packed: Packed<"Note"> = await awaitAll({
id: note.id, id: note.id,

View file

@ -161,8 +161,9 @@ export const packedNoteSchema = {
nullable: false, nullable: false,
}, },
emojis: { emojis: {
type: 'object', type: "object",
optional: true, nullable: true, optional: true,
nullable: true,
}, },
reactions: { reactions: {
type: "object", type: "object",

View file

@ -111,6 +111,16 @@ export async function createNote(
const note: IPost = object; const note: IPost = object;
if (note.id && !note.id.startsWith("https://")) {
throw new Error(`unexpected shcema of note.id: ${note.id}`);
}
const url = getOneApHrefNullable(note.url);
if (url && !url.startsWith("https://")) {
throw new Error(`unexpected shcema of note url: ${url}`);
}
logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`); logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`);
logger.info(`Creating the Note: ${note.id}`); logger.info(`Creating the Note: ${note.id}`);
@ -140,7 +150,9 @@ export async function createNote(
// Skip if author is suspended. // Skip if author is suspended.
if (actor.isSuspended) { if (actor.isSuspended) {
logger.debug(`User ${actor.usernameLower}@${actor.host} suspended; discarding.`) logger.debug(
`User ${actor.usernameLower}@${actor.host} suspended; discarding.`,
);
return null; return null;
} }
@ -362,7 +374,7 @@ export async function createNote(
apEmojis, apEmojis,
poll, poll,
uri: note.id, uri: note.id,
url: getOneApHrefNullable(note.url), url: url,
}, },
silent, silent,
); );

View file

@ -195,6 +195,12 @@ export async function createPerson(
const bday = person["vcard:bday"]?.match(/^\d{4}-\d{2}-\d{2}/); const bday = person["vcard:bday"]?.match(/^\d{4}-\d{2}-\d{2}/);
const url = getOneApHrefNullable(person.url);
if (url && !url.startsWith("https://")) {
throw new Error(`unexpected shcema of person url: ${url}`);
}
// Create user // Create user
let user: IRemoteUser; let user: IRemoteUser;
try { try {
@ -237,7 +243,7 @@ export async function createPerson(
description: person.summary description: person.summary
? htmlToMfm(truncate(person.summary, summaryLength), person.tag) ? htmlToMfm(truncate(person.summary, summaryLength), person.tag)
: null, : null,
url: getOneApHrefNullable(person.url), url: url,
fields, fields,
birthday: bday ? bday[0] : null, birthday: bday ? bday[0] : null,
location: person["vcard:Address"] || null, location: person["vcard:Address"] || null,
@ -387,6 +393,12 @@ export async function updatePerson(
const bday = person["vcard:bday"]?.match(/^\d{4}-\d{2}-\d{2}/); const bday = person["vcard:bday"]?.match(/^\d{4}-\d{2}-\d{2}/);
const url = getOneApHrefNullable(person.url);
if (url && !url.startsWith("https://")) {
throw new Error(`unexpected shcema of person url: ${url}`);
}
const updates = { const updates = {
lastFetchedAt: new Date(), lastFetchedAt: new Date(),
inbox: person.inbox, inbox: person.inbox,
@ -430,7 +442,7 @@ export async function updatePerson(
await UserProfiles.update( await UserProfiles.update(
{ userId: exist.id }, { userId: exist.id },
{ {
url: getOneApHrefNullable(person.url), url: url,
fields, fields,
description: person.summary description: person.summary
? htmlToMfm(truncate(person.summary, summaryLength), person.tag) ? htmlToMfm(truncate(person.summary, summaryLength), person.tag)

View file

@ -198,7 +198,7 @@ import * as ep___i_readAnnouncement from "./endpoints/i/read-announcement.js";
import * as ep___i_regenerateToken from "./endpoints/i/regenerate-token.js"; import * as ep___i_regenerateToken from "./endpoints/i/regenerate-token.js";
import * as ep___i_registry_getAll from "./endpoints/i/registry/get-all.js"; import * as ep___i_registry_getAll from "./endpoints/i/registry/get-all.js";
import * as ep___i_registry_getDetail from "./endpoints/i/registry/get-detail.js"; import * as ep___i_registry_getDetail from "./endpoints/i/registry/get-detail.js";
import * as ep___i_registry_getUnsecure from './endpoints/i/registry/get-unsecure.js'; import * as ep___i_registry_getUnsecure from "./endpoints/i/registry/get-unsecure.js";
import * as ep___i_registry_get from "./endpoints/i/registry/get.js"; import * as ep___i_registry_get from "./endpoints/i/registry/get.js";
import * as ep___i_registry_keysWithType from "./endpoints/i/registry/keys-with-type.js"; import * as ep___i_registry_keysWithType from "./endpoints/i/registry/keys-with-type.js";
import * as ep___i_registry_keys from "./endpoints/i/registry/keys.js"; import * as ep___i_registry_keys from "./endpoints/i/registry/keys.js";
@ -767,10 +767,10 @@ export interface IEndpointMeta {
} }
export interface IEndpoint { export interface IEndpoint {
name: string, name: string;
exec: any, // TODO: may be obosolete @ThatOneCalculator exec: any; // TODO: may be obosolete @ThatOneCalculator
meta: IEndpointMeta, meta: IEndpointMeta;
params: Schema, params: Schema;
} }
const endpoints: IEndpoint[] = (eps as [string, any]).map(([name, ep]) => { const endpoints: IEndpoint[] = (eps as [string, any]).map(([name, ep]) => {

View file

@ -1,6 +1,6 @@
import { ApiError } from "../../../error.js"; import { ApiError } from "../../../error.js";
import define from "../../../define.js"; import define from "../../../define.js";
import { RegistryItems } from "../../../../../models/index.js"; import { RegistryItems } from "@/models/index.js";
export const meta = { export const meta = {
requireCredential: true, requireCredential: true,

View file

@ -7,7 +7,7 @@ import Router from "@koa/router";
import multer from "@koa/multer"; import multer from "@koa/multer";
import bodyParser from "koa-bodyparser"; import bodyParser from "koa-bodyparser";
import cors from "@koa/cors"; import cors from "@koa/cors";
import { apiMastodonCompatible } from './mastodon/ApiMastodonCompatibleService.js'; import { apiMastodonCompatible } from "./mastodon/ApiMastodonCompatibleService.js";
import { Instances, AccessTokens, Users } from "@/models/index.js"; import { Instances, AccessTokens, Users } from "@/models/index.js";
import config from "@/config/index.js"; import config from "@/config/index.js";
import endpoints from "./endpoints.js"; import endpoints from "./endpoints.js";
@ -19,6 +19,7 @@ import signupPending from "./private/signup-pending.js";
import discord from "./service/discord.js"; import discord from "./service/discord.js";
import github from "./service/github.js"; import github from "./service/github.js";
import twitter from "./service/twitter.js"; import twitter from "./service/twitter.js";
import { koaBody } from "koa-body";
// Init app // Init app
const app = new Koa(); const app = new Koa();
@ -35,16 +36,10 @@ app.use(async (ctx, next) => {
await next(); await next();
}); });
app.use( // Init router
bodyParser({ const router = new Router();
// リクエストが multipart/form-data でない限りはJSONだと見なす const mastoRouter = new Router();
detectJSON: (ctx) => const errorRouter = new Router();
!(
ctx.is("multipart/form-data") ||
ctx.is("application/x-www-form-urlencoded")
),
}),
);
// Init multer instance // Init multer instance
const upload = multer({ const upload = multer({
@ -55,10 +50,23 @@ const upload = multer({
}, },
}); });
// Init router router.use(
const router = new Router(); bodyParser({
// リクエストが multipart/form-data でない限りはJSONだと見なす
detectJSON: (ctx) =>
!(
ctx.is("multipart/form-data") ||
ctx.is("application/x-www-form-urlencoded")
),
}),
);
apiMastodonCompatible(router); mastoRouter.use(koaBody({
multipart: true,
urlencoded: true
}));
apiMastodonCompatible(mastoRouter);
/** /**
* Register endpoint handlers * Register endpoint handlers
@ -144,11 +152,13 @@ router.post("/miauth/:session/check", async (ctx) => {
}); });
// Return 404 for unknown API // Return 404 for unknown API
router.all("(.*)", async (ctx) => { errorRouter.all("(.*)", async (ctx) => {
ctx.status = 404; ctx.status = 404;
}); });
// Register router // Register router
app.use(mastoRouter.routes());
app.use(router.routes()); app.use(router.routes());
app.use(errorRouter.routes());
export default app; export default app;

View file

@ -1,32 +1,39 @@
import Router from "@koa/router"; import Router from "@koa/router";
import megalodon, { MegalodonInterface } from '@cutls/megalodon'; import megalodon, { MegalodonInterface } from "@cutls/megalodon";
import { apiAuthMastodon } from './endpoints/auth.js'; import { apiAuthMastodon } from "./endpoints/auth.js";
import { apiAccountMastodon } from './endpoints/account.js'; import { apiAccountMastodon } from "./endpoints/account.js";
import { apiStatusMastodon } from './endpoints/status.js'; import { apiStatusMastodon } from "./endpoints/status.js";
import { apiFilterMastodon } from './endpoints/filter.js'; import { apiFilterMastodon } from "./endpoints/filter.js";
import { apiTimelineMastodon } from './endpoints/timeline.js'; import { apiTimelineMastodon } from "./endpoints/timeline.js";
import { apiNotificationsMastodon } from './endpoints/notifications.js'; import { apiNotificationsMastodon } from "./endpoints/notifications.js";
import { apiSearchMastodon } from './endpoints/search.js'; import { apiSearchMastodon } from "./endpoints/search.js";
import { getInstance } from './endpoints/meta.js'; import { getInstance } from "./endpoints/meta.js";
export function getClient(BASE_URL: string, authorization: string | undefined): MegalodonInterface { export function getClient(
const accessTokenArr = authorization?.split(' ') ?? [null]; BASE_URL: string,
authorization: string | undefined,
): MegalodonInterface {
const accessTokenArr = authorization?.split(" ") ?? [null];
const accessToken = accessTokenArr[accessTokenArr.length - 1]; const accessToken = accessTokenArr[accessTokenArr.length - 1];
const generator = (megalodon as any).default const generator = (megalodon as any).default;
const client = generator('misskey', BASE_URL, accessToken) as MegalodonInterface; const client = generator(
return client "misskey",
BASE_URL,
accessToken,
) as MegalodonInterface;
return client;
} }
export function apiMastodonCompatible(router: Router): void { export function apiMastodonCompatible(router: Router): void {
apiAuthMastodon(router) apiAuthMastodon(router);
apiAccountMastodon(router) apiAccountMastodon(router);
apiStatusMastodon(router) apiStatusMastodon(router);
apiFilterMastodon(router) apiFilterMastodon(router);
apiTimelineMastodon(router) apiTimelineMastodon(router);
apiNotificationsMastodon(router) apiNotificationsMastodon(router);
apiSearchMastodon(router) apiSearchMastodon(router);
router.get('/v1/custom_emojis', async (ctx) => { router.get("/v1/custom_emojis", async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`; const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization; const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -34,13 +41,13 @@ export function apiMastodonCompatible(router: Router): void {
const data = await client.getInstanceCustomEmojis(); const data = await client.getInstanceCustomEmojis();
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = 401; ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); });
router.get('/v1/instance', async (ctx) => { router.get("/v1/instance", async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`; const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization; const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt
@ -49,10 +56,9 @@ export function apiMastodonCompatible(router: Router): void {
const data = await client.getInstance(); const data = await client.getInstance();
ctx.body = getInstance(data.data); ctx.body = getInstance(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = 401; ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); });
} }

View file

@ -1,53 +1,56 @@
import megalodon, { MegalodonInterface } from '@cutls/megalodon'; import megalodon, { MegalodonInterface } from "@cutls/megalodon";
import Router from "@koa/router"; import Router from "@koa/router";
import { koaBody } from 'koa-body'; import { koaBody } from "koa-body";
import { getClient } from '../ApiMastodonCompatibleService.js'; import { getClient } from "../ApiMastodonCompatibleService.js";
import { toLimitToInt } from './timeline.js'; import { toLimitToInt } from "./timeline.js";
export function apiAccountMastodon(router: Router): void { export function apiAccountMastodon(router: Router): void {
router.get("/v1/accounts/verify_credentials", async (ctx, next) => {
router.get('/v1/accounts/verify_credentials', async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.verifyAccountCredentials(); const data = await client.verifyAccountCredentials();
const acct = data.data; const acct = data.data;
acct.url = `${BASE_URL}/@${acct.url}` acct.url = `${BASE_URL}/@${acct.url}`;
acct.note = '' acct.note = "";
acct.avatar_static = acct.avatar acct.avatar_static = acct.avatar;
acct.header = acct.header || '' acct.header = acct.header || "";
acct.header_static = acct.header || '' acct.header_static = acct.header || "";
acct.source = { acct.source = {
note: acct.note, note: acct.note,
fields: acct.fields, fields: acct.fields,
privacy: 'public', privacy: "public",
sensitive: false, sensitive: false,
language: '' language: "",
} };
ctx.body = acct ctx.body = acct;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status =(401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); });
router.patch('/v1/accounts/update_credentials', async (ctx) => { router.patch("/v1/accounts/update_credentials", async (ctx) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.updateCredentials((ctx.request as any).body as any); const data = await client.updateCredentials(
(ctx.request as any).body as any,
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status =(401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); });
router.get<{ Params: { id: string } }>('/v1/accounts/:id', async (ctx, next) => { router.get<{ Params: { id: string } }>(
"/v1/accounts/:id",
async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -55,55 +58,76 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.getAccount(ctx.params.id); const data = await client.getAccount(ctx.params.id);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status =(401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.get<{ Params: { id: string } }>('/v1/accounts/:id/statuses', async (ctx, next) => { );
router.get<{ Params: { id: string } }>(
"/v1/accounts/:id/statuses",
async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getAccountStatuses(ctx.params.id, toLimitToInt(ctx.query as any)); const data = await client.getAccountStatuses(
ctx.params.id,
toLimitToInt(ctx.query as any),
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status =(401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.get<{ Params: { id: string } }>('/v1/accounts/:id/followers', async (ctx, next) => { );
router.get<{ Params: { id: string } }>(
"/v1/accounts/:id/followers",
async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getAccountFollowers(ctx.params.id, ctx.query as any); const data = await client.getAccountFollowers(
ctx.params.id,
ctx.query as any,
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status =(401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.get<{ Params: { id: string } }>('/v1/accounts/:id/following', async (ctx, next) => { );
router.get<{ Params: { id: string } }>(
"/v1/accounts/:id/following",
async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getAccountFollowing(ctx.params.id, ctx.query as any); const data = await client.getAccountFollowing(
ctx.params.id,
ctx.query as any,
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status =(401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.get<{ Params: { id: string } }>('/v1/accounts/:id/lists', async (ctx, next) => { );
router.get<{ Params: { id: string } }>(
"/v1/accounts/:id/lists",
async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -111,13 +135,16 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.getAccountLists(ctx.params.id); const data = await client.getAccountLists(ctx.params.id);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status =(401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.post<{ Params: { id: string } }>('/v1/accounts/:id/follow', async (ctx, next) => { );
router.post<{ Params: { id: string } }>(
"/v1/accounts/:id/follow",
async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -127,13 +154,16 @@ export function apiAccountMastodon(router: Router): void {
acct.following = true; acct.following = true;
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status =(401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.post<{ Params: { id: string } }>('/v1/accounts/:id/unfollow', async (ctx, next) => { );
router.post<{ Params: { id: string } }>(
"/v1/accounts/:id/unfollow",
async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -143,13 +173,16 @@ export function apiAccountMastodon(router: Router): void {
acct.following = false; acct.following = false;
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status =(401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.post<{ Params: { id: string } }>('/v1/accounts/:id/block', async (ctx, next) => { );
router.post<{ Params: { id: string } }>(
"/v1/accounts/:id/block",
async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -157,13 +190,16 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.blockAccount(ctx.params.id); const data = await client.blockAccount(ctx.params.id);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status =(401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.post<{ Params: { id: string } }>('/v1/accounts/:id/unblock', async (ctx, next) => { );
router.post<{ Params: { id: string } }>(
"/v1/accounts/:id/unblock",
async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -171,27 +207,36 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.unblockAccount(ctx.params.id); const data = await client.unblockAccount(ctx.params.id);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status =(401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.post<{ Params: { id: string } }>('/v1/accounts/:id/mute', async (ctx) => { );
router.post<{ Params: { id: string } }>(
"/v1/accounts/:id/mute",
async (ctx) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.muteAccount(ctx.params.id, (ctx.request as any).body as any); const data = await client.muteAccount(
ctx.params.id,
(ctx.request as any).body as any,
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status =(401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.post<{ Params: { id: string } }>('/v1/accounts/:id/unmute', async (ctx, next) => { );
router.post<{ Params: { id: string } }>(
"/v1/accounts/:id/unmute",
async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -199,43 +244,44 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.unmuteAccount(ctx.params.id); const data = await client.unmuteAccount(ctx.params.id);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status =(401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.get('/v1/accounts/relationships', async (ctx, next) => { );
router.get("/v1/accounts/relationships", async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const idsRaw = (ctx.query as any)['id[]'] const idsRaw = (ctx.query as any)["id[]"];
const ids = typeof idsRaw === 'string' ? [idsRaw] : idsRaw const ids = typeof idsRaw === "string" ? [idsRaw] : idsRaw;
const data = await client.getRelationships(ids) as any; const data = (await client.getRelationships(ids)) as any;
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status =(401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); });
router.get('/v1/bookmarks', async (ctx, next) => { router.get("/v1/bookmarks", async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getBookmarks(ctx.query as any) as any; const data = (await client.getBookmarks(ctx.query as any)) as any;
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status =(401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); });
router.get('/v1/favourites', async (ctx, next) => { router.get("/v1/favourites", async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -243,13 +289,13 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.getFavourites(ctx.query as any); const data = await client.getFavourites(ctx.query as any);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status =(401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); });
router.get('/v1/mutes', async (ctx, next) => { router.get("/v1/mutes", async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -257,13 +303,13 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.getMutes(ctx.query as any); const data = await client.getMutes(ctx.query as any);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status =(401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); });
router.get('/v1/blocks', async (ctx, next) => { router.get("/v1/blocks", async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -271,27 +317,31 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.getBlocks(ctx.query as any); const data = await client.getBlocks(ctx.query as any);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status =(401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); });
router.get('/v1/follow_ctxs', async (ctx, next) => { router.get("/v1/follow_ctxs", async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getFollowRequests((ctx.query as any || { limit: 20 }).limit); const data = await client.getFollowRequests(
((ctx.query as any) || { limit: 20 }).limit,
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status =(401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); });
router.post<{ Params: { id: string } }>('/v1/follow_ctxs/:id/authorize', async (ctx, next) => { router.post<{ Params: { id: string } }>(
"/v1/follow_ctxs/:id/authorize",
async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -299,13 +349,16 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.acceptFollowRequest(ctx.params.id); const data = await client.acceptFollowRequest(ctx.params.id);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status =(401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.post<{ Params: { id: string } }>('/v1/follow_ctxs/:id/reject', async (ctx, next) => { );
router.post<{ Params: { id: string } }>(
"/v1/follow_ctxs/:id/reject",
async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -313,11 +366,11 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.rejectFollowRequest(ctx.params.id); const data = await client.rejectFollowRequest(ctx.params.id);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status =(401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
);
} }

View file

@ -1,81 +1,84 @@
import megalodon, { MegalodonInterface } from '@cutls/megalodon'; import megalodon, { MegalodonInterface } from "@cutls/megalodon";
import Router from "@koa/router"; import Router from "@koa/router";
import { koaBody } from 'koa-body'; import { koaBody } from "koa-body";
import { getClient } from '../ApiMastodonCompatibleService.js'; import { getClient } from "../ApiMastodonCompatibleService.js";
import bodyParser from "koa-bodyparser";
const readScope = [ const readScope = [
'read:account', "read:account",
'read:drive', "read:drive",
'read:blocks', "read:blocks",
'read:favorites', "read:favorites",
'read:following', "read:following",
'read:messaging', "read:messaging",
'read:mutes', "read:mutes",
'read:notifications', "read:notifications",
'read:reactions', "read:reactions",
'read:pages', "read:pages",
'read:page-likes', "read:page-likes",
'read:user-groups', "read:user-groups",
'read:channels', "read:channels",
'read:gallery', "read:gallery",
'read:gallery-likes' "read:gallery-likes",
] ];
const writeScope = [ const writeScope = [
'write:account', "write:account",
'write:drive', "write:drive",
'write:blocks', "write:blocks",
'write:favorites', "write:favorites",
'write:following', "write:following",
'write:messaging', "write:messaging",
'write:mutes', "write:mutes",
'write:notes', "write:notes",
'write:notifications', "write:notifications",
'write:reactions', "write:reactions",
'write:votes', "write:votes",
'write:pages', "write:pages",
'write:page-likes', "write:page-likes",
'write:user-groups', "write:user-groups",
'write:channels', "write:channels",
'write:gallery', "write:gallery",
'write:gallery-likes' "write:gallery-likes",
] ];
export function apiAuthMastodon(router: Router): void { export function apiAuthMastodon(router: Router): void {
router.post("/v1/apps", async (ctx) => {
router.post('/v1/apps', async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`; const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization; const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body; const body: any = ctx.request.body;
try { try {
let scope = body.scopes let scope = body.scopes;
console.log(body) console.log(body);
if (typeof scope === 'string') scope = scope.split(' ') if (typeof scope === "string") scope = scope.split(" ");
const pushScope = new Set<string>() const pushScope = new Set<string>();
for (const s of scope) { for (const s of scope) {
if (s.match(/^read/)) for (const r of readScope) 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) 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 let red = body.redirect_uris;
if (red === 'urn:ietf:wg:oauth:2.0:oob') { if (red === "urn:ietf:wg:oauth:2.0:oob") {
red = 'https://thedesk.top/hello.html' 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 = { ctx.body = {
id: appData.id, id: appData.id,
name: appData.name, name: appData.name,
website: appData.website, website: appData.website,
redirect_uri: red, redirect_uri: red,
client_id: Buffer.from(appData.url || '').toString('base64'), client_id: Buffer.from(appData.url || "").toString("base64"),
client_secret: appData.clientSecret, client_secret: appData.clientSecret,
} };
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = 401; ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); });
} }

View file

@ -1,11 +1,9 @@
import megalodon, { MegalodonInterface } from '@cutls/megalodon'; import megalodon, { MegalodonInterface } from "@cutls/megalodon";
import Router from "@koa/router"; 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 { 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 BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization; const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -14,13 +12,13 @@ export function apiFilterMastodon(router: Router): void {
const data = await client.getFilters(); const data = await client.getFilters();
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = 401; ctx.status = 401;
ctx.body = e.response.data; 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 BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization; const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -29,13 +27,13 @@ export function apiFilterMastodon(router: Router): void {
const data = await client.getFilter(ctx.params.id); const data = await client.getFilter(ctx.params.id);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = 401; ctx.status = 401;
ctx.body = e.response.data; 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 BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization; const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens); 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); const data = await client.createFilter(body.phrase, body.context, body);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = 401; ctx.status = 401;
ctx.body = e.response.data; 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 BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization; const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body; const body: any = ctx.request.body;
try { 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; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = 401; ctx.status = 401;
ctx.body = e.response.data; 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 BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization; const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -74,10 +76,9 @@ export function apiFilterMastodon(router: Router): void {
const data = await client.deleteFilter(ctx.params.id); const data = await client.deleteFilter(ctx.params.id);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = 401; ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); });
} }

View file

@ -1,16 +1,15 @@
import megalodon, { MegalodonInterface } from '@cutls/megalodon'; import megalodon, { MegalodonInterface } from "@cutls/megalodon";
import Router from "@koa/router"; import Router from "@koa/router";
import { koaBody } from 'koa-body'; import { koaBody } from "koa-body";
import { getClient } from '../ApiMastodonCompatibleService.js'; import { getClient } from "../ApiMastodonCompatibleService.js";
import { toTextWithReaction } from './timeline.js'; import { toTextWithReaction } from "./timeline.js";
function toLimitToInt(q: any) { function toLimitToInt(q: any) {
if (q.limit) if (typeof q.limit === 'string') q.limit = parseInt(q.limit, 10) if (q.limit) if (typeof q.limit === "string") q.limit = parseInt(q.limit, 10);
return q return q;
} }
export function apiNotificationsMastodon(router: Router): void { 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 BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization; const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens); 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 data = await client.getNotifications(toLimitToInt(ctx.query));
const notfs = data.data; const notfs = data.data;
const ret = notfs.map((n) => { const ret = notfs.map((n) => {
if(n.type !== 'follow' && n.type !== 'follow_request') { if (n.type !== "follow" && n.type !== "follow_request") {
if (n.type === 'reaction') n.type = 'favourite' if (n.type === "reaction") n.type = "favourite";
n.status = toTextWithReaction(n.status ? [n.status] : [], ctx.hostname)[0] n.status = toTextWithReaction(
return n n.status ? [n.status] : [],
ctx.hostname,
)[0];
return n;
} else { } else {
return n return n;
} }
}) });
ctx.body = ret; ctx.body = ret;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = 401; ctx.status = 401;
ctx.body = e.response.data; 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 BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization; const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -43,20 +45,20 @@ export function apiNotificationsMastodon(router: Router): void {
try { try {
const dataRaw = await client.getNotification(ctx.params.id); const dataRaw = await client.getNotification(ctx.params.id);
const data = dataRaw.data; const data = dataRaw.data;
if(data.type !== 'follow' && data.type !== 'follow_request') { if (data.type !== "follow" && data.type !== "follow_request") {
if (data.type === 'reaction') data.type = 'favourite' if (data.type === "reaction") data.type = "favourite";
ctx.body = toTextWithReaction([data as any], ctx.request.hostname)[0] ctx.body = toTextWithReaction([data as any], ctx.request.hostname)[0];
} else { } else {
ctx.body = data ctx.body = data;
} }
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = 401; ctx.status = 401;
ctx.body = e.response.data; 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 BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization; const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -65,13 +67,13 @@ export function apiNotificationsMastodon(router: Router): void {
const data = await client.dismissNotifications(); const data = await client.dismissNotifications();
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = 401; ctx.status = 401;
ctx.body = e.response.data; 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 BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization; const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -80,10 +82,9 @@ export function apiNotificationsMastodon(router: Router): void {
const data = await client.dismissNotification(ctx.params.id); const data = await client.dismissNotification(ctx.params.id);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = 401; ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); });
} }

View file

@ -1,25 +1,22 @@
import megalodon, { MegalodonInterface } from '@cutls/megalodon'; import megalodon, { MegalodonInterface } from "@cutls/megalodon";
import Router from "@koa/router"; 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 { 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 BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization; const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body; const body: any = ctx.request.body;
try { try {
const query: any = ctx.query const query: any = ctx.query;
const type = query.type || '' const type = query.type || "";
const data = await client.search(query.q, type, query); const data = await client.search(query.q, type, query);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = 401; ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); });
} }

View file

@ -1,54 +1,58 @@
import Router from "@koa/router"; import Router from "@koa/router";
import { koaBody } from 'koa-body'; import megalodon, { MegalodonInterface } from "@cutls/megalodon";
import megalodon, { MegalodonInterface } from '@cutls/megalodon'; import { getClient } from "../ApiMastodonCompatibleService.js";
import { getClient } from '../ApiMastodonCompatibleService.js'; import fs from "fs";
import fs from 'fs' import { pipeline } from "node:stream";
import { pipeline } from 'node:stream'; import { promisify } from "node:util";
import { promisify } from 'node:util'; import { createTemp } from "@/misc/create-temp.js";
import { createTemp } from '@/misc/create-temp.js'; import { emojiRegex, emojiRegexAtStartToEnd } from "@/misc/emoji-regex.js";
import { emojiRegex, emojiRegexAtStartToEnd } from '@/misc/emoji-regex.js'; import axios from "axios";
import axios from 'axios';
const pump = promisify(pipeline); const pump = promisify(pipeline);
export function apiStatusMastodon(router: Router): void { export function apiStatusMastodon(router: Router): void {
router.post('/v1/statuses', async (ctx, reply) => { router.post("/v1/statuses", async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const body: any = ctx.request.body const body: any = ctx.request.body;
const text = body.status const text = body.status;
const removed = text.replace(/@\S+/g, '').replaceAll(' ', '') const removed = text.replace(/@\S+/g, "").replaceAll(" ", "");
const isDefaultEmoji = emojiRegexAtStartToEnd.test(removed) const isDefaultEmoji = emojiRegexAtStartToEnd.test(removed);
const isCustomEmoji = /^:[a-zA-Z0-9@_]+:$/.test(removed) const isCustomEmoji = /^:[a-zA-Z0-9@_]+:$/.test(removed);
if (body.in_reply_to_id && isDefaultEmoji || isCustomEmoji) { if ((body.in_reply_to_id && isDefaultEmoji) || isCustomEmoji) {
const a = await client.createEmojiReaction(body.in_reply_to_id, removed) const a = await client.createEmojiReaction(
ctx.body = a.data body.in_reply_to_id,
removed,
);
ctx.body = a.data;
} }
if (body.in_reply_to_id && removed === '/unreact') { if (body.in_reply_to_id && removed === "/unreact") {
try { try {
const id = body.in_reply_to_id const id = body.in_reply_to_id;
const post = await client.getStatus(id) const post = await client.getStatus(id);
const react = post.data.emoji_reactions.filter((e) => e.me)[0].name const react = post.data.emoji_reactions.filter((e) => e.me)[0].name;
const data = await client.deleteEmojiReaction(id, react); const data = await client.deleteEmojiReaction(id, react);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
} }
if (!body.media_ids) body.media_ids = undefined if (!body.media_ids) body.media_ids = undefined;
if (body.media_ids && !body.media_ids.length) body.media_ids = undefined if (body.media_ids && !body.media_ids.length) body.media_ids = undefined;
const data = await client.postStatus(text, body); const data = await client.postStatus(text, body);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); });
router.get<{ Params: { id: string } }>('/v1/statuses/:id', async (ctx, reply) => { router.get<{ Params: { id: string } }>(
"/v1/statuses/:id",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -56,12 +60,15 @@ export function apiStatusMastodon(router: Router): void {
const data = await client.getStatus(ctx.params.id); const data = await client.getStatus(ctx.params.id);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.delete<{ Params: { id: string } }>('/v1/statuses/:id', async (ctx, reply) => { );
router.delete<{ Params: { id: string } }>(
"/v1/statuses/:id",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -69,37 +76,54 @@ export function apiStatusMastodon(router: Router): void {
const data = await client.deleteStatus(ctx.params.id); const data = await client.deleteStatus(ctx.params.id);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
);
interface IReaction { interface IReaction {
id: string id: string;
createdAt: string createdAt: string;
user: MisskeyEntity.User, user: MisskeyEntity.User;
type: string type: string;
} }
router.get<{ Params: { id: string } }>('/v1/statuses/:id/context', async (ctx, reply) => { router.get<{ Params: { id: string } }>(
"/v1/statuses/:id/context",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const id = ctx.params.id const id = ctx.params.id;
const data = await client.getStatusContext(id, ctx.query as any); const data = await client.getStatusContext(id, ctx.query as any);
const status = await client.getStatus(id); const status = await client.getStatus(id);
const reactionsAxios = await axios.get(`${BASE_URL}/api/notes/reactions?noteId=${id}`) const reactionsAxios = await axios.get(
const reactions: IReaction[] = reactionsAxios.data `${BASE_URL}/api/notes/reactions?noteId=${id}`,
const text = reactions.map((r) => `${r.type.replace('@.', '')} ${r.user.username}`).join('<br />') );
data.data.descendants.unshift(statusModel(status.data.id, status.data.account.id, status.data.emojis, text)) const reactions: IReaction[] = reactionsAxios.data;
const text = reactions
.map((r) => `${r.type.replace("@.", "")} ${r.user.username}`)
.join("<br />");
data.data.descendants.unshift(
statusModel(
status.data.id,
status.data.account.id,
status.data.emojis,
text,
),
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.get<{ Params: { id: string } }>('/v1/statuses/:id/reblogged_by', async (ctx, reply) => { );
router.get<{ Params: { id: string } }>(
"/v1/statuses/:id/reblogged_by",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -107,31 +131,43 @@ export function apiStatusMastodon(router: Router): void {
const data = await client.getStatusRebloggedBy(ctx.params.id); const data = await client.getStatusRebloggedBy(ctx.params.id);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.get<{ Params: { id: string } }>('/v1/statuses/:id/favourited_by', async (ctx, reply) => { );
ctx.body = [] router.get<{ Params: { id: string } }>(
}); "/v1/statuses/:id/favourited_by",
router.post<{ Params: { id: string } }>('/v1/statuses/:id/favourite', async (ctx, reply) => { 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 BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
const react = await getFirstReaction(BASE_URL, accessTokens); const react = await getFirstReaction(BASE_URL, accessTokens);
try { try {
const a = await client.createEmojiReaction(ctx.params.id, react) as any; const a = (await client.createEmojiReaction(
ctx.params.id,
react,
)) as any;
//const data = await client.favouriteStatus(ctx.params.id) as any; //const data = await client.favouriteStatus(ctx.params.id) as any;
ctx.body = a.data; ctx.body = a.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.post<{ Params: { id: string } }>('/v1/statuses/:id/unfavourite', async (ctx, reply) => { );
router.post<{ Params: { id: string } }>(
"/v1/statuses/:id/unfavourite",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -140,13 +176,16 @@ export function apiStatusMastodon(router: Router): void {
const data = await client.deleteEmojiReaction(ctx.params.id, react); const data = await client.deleteEmojiReaction(ctx.params.id, react);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
);
router.post<{ Params: { id: string } }>('/v1/statuses/:id/reblog', async (ctx, reply) => { router.post<{ Params: { id: string } }>(
"/v1/statuses/:id/reblog",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -154,13 +193,16 @@ export function apiStatusMastodon(router: Router): void {
const data = await client.reblogStatus(ctx.params.id); const data = await client.reblogStatus(ctx.params.id);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
);
router.post<{ Params: { id: string } }>('/v1/statuses/:id/unreblog', async (ctx, reply) => { router.post<{ Params: { id: string } }>(
"/v1/statuses/:id/unreblog",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -168,13 +210,16 @@ export function apiStatusMastodon(router: Router): void {
const data = await client.unreblogStatus(ctx.params.id); const data = await client.unreblogStatus(ctx.params.id);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
);
router.post<{ Params: { id: string } }>('/v1/statuses/:id/bookmark', async (ctx, reply) => { router.post<{ Params: { id: string } }>(
"/v1/statuses/:id/bookmark",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -182,27 +227,33 @@ export function apiStatusMastodon(router: Router): void {
const data = await client.bookmarkStatus(ctx.params.id); const data = await client.bookmarkStatus(ctx.params.id);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
);
router.post<{ Params: { id: string } }>('/v1/statuses/:id/unbookmark', async (ctx, reply) => { router.post<{ Params: { id: string } }>(
"/v1/statuses/:id/unbookmark",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.unbookmarkStatus(ctx.params.id) as any; const data = (await client.unbookmarkStatus(ctx.params.id)) as any;
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
);
router.post<{ Params: { id: string } }>('/v1/statuses/:id/pin', async (ctx, reply) => { router.post<{ Params: { id: string } }>(
"/v1/statuses/:id/pin",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -210,13 +261,16 @@ export function apiStatusMastodon(router: Router): void {
const data = await client.pinStatus(ctx.params.id); const data = await client.pinStatus(ctx.params.id);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
);
router.post<{ Params: { id: string } }>('/v1/statuses/:id/unpin', async (ctx, reply) => { router.post<{ Params: { id: string } }>(
"/v1/statuses/:id/unpin",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -224,19 +278,20 @@ export function apiStatusMastodon(router: Router): void {
const data = await client.unpinStatus(ctx.params.id); const data = await client.unpinStatus(ctx.params.id);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.post('/v1/media', async (ctx, reply) => { );
router.post("/v1/media", async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const multipartData = await ctx.file; const multipartData = await ctx.file;
if (!multipartData) { if (!multipartData) {
ctx.body = { error: 'No image' }; ctx.body = { error: "No image" };
return; return;
} }
const [path] = await createTemp(); const [path] = await createTemp();
@ -245,19 +300,19 @@ export function apiStatusMastodon(router: Router): void {
const data = await client.uploadMedia(image); const data = await client.uploadMedia(image);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); });
router.post('/v2/media', async (ctx, reply) => { router.post("/v2/media", async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const multipartData = await ctx.file; const multipartData = await ctx.file;
if (!multipartData) { if (!multipartData) {
ctx.body = { error: 'No image' }; ctx.body = { error: "No image" };
return; return;
} }
const [path] = await createTemp(); const [path] = await createTemp();
@ -266,12 +321,14 @@ export function apiStatusMastodon(router: Router): void {
const data = await client.uploadMedia(image); const data = await client.uploadMedia(image);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); });
router.get<{ Params: { id: string } }>('/v1/media/:id', async (ctx, reply) => { router.get<{ Params: { id: string } }>(
"/v1/media/:id",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -279,25 +336,34 @@ export function apiStatusMastodon(router: Router): void {
const data = await client.getMedia(ctx.params.id); const data = await client.getMedia(ctx.params.id);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.put<{ Params: { id: string } }>('/v1/media/:id', async (ctx, reply) => { );
router.put<{ Params: { id: string } }>(
"/v1/media/:id",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.updateMedia(ctx.params.id, ctx.request.body as any); const data = await client.updateMedia(
ctx.params.id,
ctx.request.body as any,
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.get<{ Params: { id: string } }>('/v1/polls/:id', async (ctx, reply) => { );
router.get<{ Params: { id: string } }>(
"/v1/polls/:id",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -305,68 +371,82 @@ export function apiStatusMastodon(router: Router): void {
const data = await client.getPoll(ctx.params.id); const data = await client.getPoll(ctx.params.id);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.post<{ Params: { id: string } }>('/v1/polls/:id/votes', async (ctx, reply) => { );
router.post<{ Params: { id: string } }>(
"/v1/polls/:id/votes",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.votePoll(ctx.params.id, (ctx.request.body as any).choices); const data = await client.votePoll(
ctx.params.id,
(ctx.request.body as any).choices,
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
);
} }
async function getFirstReaction(BASE_URL: string, accessTokens: string | undefined) { async function getFirstReaction(
const accessTokenArr = accessTokens?.split(' ') ?? [null]; BASE_URL: string,
accessTokens: string | undefined,
) {
const accessTokenArr = accessTokens?.split(" ") ?? [null];
const accessToken = accessTokenArr[accessTokenArr.length - 1]; const accessToken = accessTokenArr[accessTokenArr.length - 1];
let react = '👍' let react = "👍";
try { try {
const api = await axios.post(`${BASE_URL}/api/i/registry/get-unsecure`, { const api = await axios.post(`${BASE_URL}/api/i/registry/get-unsecure`, {
scope: ['client', 'base'], scope: ["client", "base"],
key: 'reactions', key: "reactions",
i: accessToken i: accessToken,
}) });
const reactRaw = api.data const reactRaw = api.data;
react = Array.isArray(reactRaw) ? api.data[0] : '👍' react = Array.isArray(reactRaw) ? api.data[0] : "👍";
console.log(api.data) console.log(api.data);
return react return react;
} catch (e) { } catch (e) {
return react return react;
} }
} }
export function statusModel(id: string | null, acctId: string | null, emojis: MastodonEntity.Emoji[], content: string) { export function statusModel(
const now = "1970-01-02T00:00:00.000Z" id: string | null,
acctId: string | null,
emojis: MastodonEntity.Emoji[],
content: string,
) {
const now = "1970-01-02T00:00:00.000Z";
return { return {
id: '9atm5frjhb', id: "9atm5frjhb",
uri: 'https://http.cat/404', // "" uri: "https://http.cat/404", // ""
url: 'https://http.cat/404', // "", url: "https://http.cat/404", // "",
account: { account: {
id: '9arzuvv0sw', id: "9arzuvv0sw",
username: 'ReactionBot', username: "ReactionBot",
acct: 'ReactionBot', acct: "ReactionBot",
display_name: 'ReactionOfThisPost', display_name: "ReactionOfThisPost",
locked: false, locked: false,
created_at: now, created_at: now,
followers_count: 0, followers_count: 0,
following_count: 0, following_count: 0,
statuses_count: 0, statuses_count: 0,
note: '', note: "",
url: 'https://http.cat/404', url: "https://http.cat/404",
avatar: 'https://http.cat/404', avatar: "https://http.cat/404",
avatar_static: 'https://http.cat/404', avatar_static: "https://http.cat/404",
header: 'https://http.cat/404', // "" header: "https://http.cat/404", // ""
header_static: 'https://http.cat/404', // "" header_static: "https://http.cat/404", // ""
emojis: [], emojis: [],
fields: [], fields: [],
moved: null, moved: null,
@ -386,8 +466,8 @@ export function statusModel(id: string | null, acctId: string | null, emojis: Ma
reblogged: false, reblogged: false,
muted: false, muted: false,
sensitive: false, sensitive: false,
spoiler_text: '', spoiler_text: "",
visibility: 'public' as const, visibility: "public" as const,
media_attachments: [], media_attachments: [],
mentions: [], mentions: [],
tags: [], tags: [],
@ -399,5 +479,5 @@ export function statusModel(id: string | null, acctId: string | null, emojis: Ma
emoji_reactions: [], emoji_reactions: [],
bookmarked: false, bookmarked: false,
quote: false, quote: false,
} };
} }

View file

@ -1,81 +1,99 @@
import Router from "@koa/router"; import Router from "@koa/router";
import { koaBody } from 'koa-body'; import megalodon, { Entity, MegalodonInterface } from "@cutls/megalodon";
import megalodon, { Entity, MegalodonInterface } from '@cutls/megalodon'; import { getClient } from "../ApiMastodonCompatibleService.js";
import { getClient } from '../ApiMastodonCompatibleService.js' import { statusModel } from "./status.js";
import { statusModel } from './status.js'; import Autolinker from "autolinker";
import Autolinker from 'autolinker';
import { ParsedUrlQuery } from "querystring"; import { ParsedUrlQuery } from "querystring";
export function toLimitToInt(q: ParsedUrlQuery) { export function toLimitToInt(q: ParsedUrlQuery) {
if (q.limit) if (typeof q.limit === 'string') q.limit = parseInt(q.limit, 10).toString() if (q.limit)
return q if (typeof q.limit === "string") q.limit = parseInt(q.limit, 10).toString();
return q;
} }
export function toTextWithReaction(status: Entity.Status[], host: string) { export function toTextWithReaction(status: Entity.Status[], host: string) {
return status.map((t) => { return status.map((t) => {
if (!t) return statusModel(null, null, [], 'no content') if (!t) return statusModel(null, null, [], "no content");
if (!t.emoji_reactions) return t if (!t.emoji_reactions) return t;
if (t.reblog) t.reblog = toTextWithReaction([t.reblog], host)[0] if (t.reblog) t.reblog = toTextWithReaction([t.reblog], host)[0];
const reactions = t.emoji_reactions.map((r) => `${r.name.replace('@.', '')} (${r.count}${r.me ? "* " : ''})`); const reactions = t.emoji_reactions.map(
(r) => `${r.name.replace("@.", "")} (${r.count}${r.me ? "* " : ""})`,
);
//t.emojis = getEmoji(t.content, host) //t.emojis = getEmoji(t.content, host)
t.content = `<p>${autoLinker(t.content, host)}</p><p>${reactions.join(', ')}</p>` t.content = `<p>${autoLinker(t.content, host)}</p><p>${reactions.join(
return t ", ",
}) )}</p>`;
return t;
});
} }
export function autoLinker(input: string, host: string) { export function autoLinker(input: string, host: string) {
return Autolinker.link(input, { return Autolinker.link(input, {
hashtag: 'twitter', hashtag: "twitter",
mention: 'twitter', mention: "twitter",
email: false, email: false,
stripPrefix: false, stripPrefix: false,
replaceFn: function (match) { replaceFn: function (match) {
switch (match.type) { switch (match.type) {
case 'url': case "url":
return true return true;
case 'mention': case "mention":
console.log("Mention: ", match.getMention()); console.log("Mention: ", match.getMention());
console.log("Mention Service Name: ", match.getServiceName()); console.log("Mention Service Name: ", match.getServiceName());
return `<a href="https://${host}/@${encodeURIComponent(match.getMention())}" target="_blank">@${match.getMention()}</a>`; return `<a href="https://${host}/@${encodeURIComponent(
case 'hashtag': match.getMention(),
)}" target="_blank">@${match.getMention()}</a>`;
case "hashtag":
console.log("Hashtag: ", match.getHashtag()); console.log("Hashtag: ", match.getHashtag());
return `<a href="https://${host}/tags/${encodeURIComponent(match.getHashtag())}" target="_blank">#${match.getHashtag()}</a>`; return `<a href="https://${host}/tags/${encodeURIComponent(
} match.getHashtag(),
return false )}" target="_blank">#${match.getHashtag()}</a>`;
} }
return false;
},
}); });
} }
export function apiTimelineMastodon(router: Router): void { export function apiTimelineMastodon(router: Router): void {
router.get('/v1/timelines/public', async (ctx, reply) => { router.get("/v1/timelines/public", async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const query: any = ctx.query const query: any = ctx.query;
const data = query.local ? await client.getLocalTimeline(toLimitToInt(query)) : await client.getPublicTimeline(toLimitToInt(query)); const data = query.local
? await client.getLocalTimeline(toLimitToInt(query))
: await client.getPublicTimeline(toLimitToInt(query));
ctx.body = toTextWithReaction(data.data, ctx.hostname); ctx.body = toTextWithReaction(data.data, ctx.hostname);
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); });
router.get<{ Params: { hashtag: string } }>('/v1/timelines/tag/:hashtag', async (ctx, reply) => { router.get<{ Params: { hashtag: string } }>(
"/v1/timelines/tag/:hashtag",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getTagTimeline(ctx.params.hashtag, toLimitToInt(ctx.query)); const data = await client.getTagTimeline(
ctx.params.hashtag,
toLimitToInt(ctx.query),
);
ctx.body = toTextWithReaction(data.data, ctx.hostname); ctx.body = toTextWithReaction(data.data, ctx.hostname);
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.get<{ Params: { hashtag: string } }>('/v1/timelines/home', async (ctx, reply) => { );
router.get<{ Params: { hashtag: string } }>(
"/v1/timelines/home",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -83,41 +101,50 @@ export function apiTimelineMastodon(router: Router): void {
const data = await client.getHomeTimeline(toLimitToInt(ctx.query)); const data = await client.getHomeTimeline(toLimitToInt(ctx.query));
ctx.body = toTextWithReaction(data.data, ctx.hostname); ctx.body = toTextWithReaction(data.data, ctx.hostname);
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.get<{ Params: { listId: string } }>('/v1/timelines/list/:listId', async (ctx, reply) => { );
router.get<{ Params: { listId: string } }>(
"/v1/timelines/list/:listId",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getListTimeline(ctx.params.listId, toLimitToInt(ctx.query)); const data = await client.getListTimeline(
ctx.params.listId,
toLimitToInt(ctx.query),
);
ctx.body = toTextWithReaction(data.data, ctx.hostname); ctx.body = toTextWithReaction(data.data, ctx.hostname);
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.get('/v1/conversations', async (ctx, reply) => { );
router.get("/v1/conversations", async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getConversationTimeline(toLimitToInt(ctx.query)); const data = await client.getConversationTimeline(
toLimitToInt(ctx.query),
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); });
router.get('/v1/lists', async (ctx, reply) => { router.get("/v1/lists", async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -125,13 +152,15 @@ export function apiTimelineMastodon(router: Router): void {
const data = await client.getLists(); const data = await client.getLists();
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); });
router.get<{ Params: { id: string } }>('/v1/lists/:id', async (ctx, reply) => { router.get<{ Params: { id: string } }>(
"/v1/lists/:id",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -139,13 +168,14 @@ export function apiTimelineMastodon(router: Router): void {
const data = await client.getList(ctx.params.id); const data = await client.getList(ctx.params.id);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.post('/v1/lists', async (ctx, reply) => { );
router.post("/v1/lists", async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -153,13 +183,15 @@ export function apiTimelineMastodon(router: Router): void {
const data = await client.createList((ctx.query as any).title); const data = await client.createList((ctx.query as any).title);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); });
router.put<{ Params: { id: string } }>('/v1/lists/:id', async (ctx, reply) => { router.put<{ Params: { id: string } }>(
"/v1/lists/:id",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -167,13 +199,16 @@ export function apiTimelineMastodon(router: Router): void {
const data = await client.updateList(ctx.params.id, ctx.query as any); const data = await client.updateList(ctx.params.id, ctx.query as any);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.delete<{ Params: { id: string } }>('/v1/lists/:id', async (ctx, reply) => { );
router.delete<{ Params: { id: string } }>(
"/v1/lists/:id",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -181,66 +216,90 @@ export function apiTimelineMastodon(router: Router): void {
const data = await client.deleteList(ctx.params.id); const data = await client.deleteList(ctx.params.id);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.get<{ Params: { id: string } }>('/v1/lists/:id/accounts', async (ctx, reply) => { );
router.get<{ Params: { id: string } }>(
"/v1/lists/:id/accounts",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getAccountsInList(ctx.params.id, ctx.query as any); const data = await client.getAccountsInList(
ctx.params.id,
ctx.query as any,
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.post<{ Params: { id: string } }>('/v1/lists/:id/accounts', async (ctx, reply) => { );
router.post<{ Params: { id: string } }>(
"/v1/lists/:id/accounts",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.addAccountsToList(ctx.params.id, (ctx.query as any).account_ids); const data = await client.addAccountsToList(
ctx.params.id,
(ctx.query as any).account_ids,
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
router.delete<{ Params: { id: string } }>('/v1/lists/:id/accounts', async (ctx, reply) => { );
router.delete<{ Params: { id: string } }>(
"/v1/lists/:id/accounts",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.deleteAccountsFromList(ctx.params.id, (ctx.query as any).account_ids); const data = await client.deleteAccountsFromList(
ctx.params.id,
(ctx.query as any).account_ids,
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e);
console.error(e.response.data) console.error(e.response.data);
ctx.status = (401); ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); },
);
} }
function escapeHTML(str: string) { function escapeHTML(str: string) {
if (!str) { if (!str) {
return '' return "";
} }
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;') return str
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
} }
function nl2br(str: string) { function nl2br(str: string) {
if (!str) { if (!str) {
return '' return "";
} }
str = str.replace(/\r\n/g, '<br />') str = str.replace(/\r\n/g, "<br />");
str = str.replace(/(\n|\r)/g, '<br />') str = str.replace(/(\n|\r)/g, "<br />");
return str return str;
} }

View file

@ -16,7 +16,7 @@ export const initializeStreamingServer = (server: http.Server) => {
ws.on("request", async (request) => { ws.on("request", async (request) => {
const q = request.resourceURL.query as ParsedUrlQuery; 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 cred = q.i || q.access_token || headers;
const accessToken = cred.toString(); const accessToken = cred.toString();
@ -48,9 +48,17 @@ export const initializeStreamingServer = (server: http.Server) => {
redisClient.on("message", onRedisMessage); redisClient.on("message", onRedisMessage);
const host = `https://${request.host}`; const host = `https://${request.host}`;
const prepareStream = q.stream?.toString(); 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 const intervalId = user
? setInterval(() => { ? setInterval(() => {

View file

@ -20,8 +20,7 @@ import { createTemp } from "@/misc/create-temp.js";
import { publishMainStream } from "@/services/stream.js"; import { publishMainStream } from "@/services/stream.js";
import * as Acct from "@/misc/acct.js"; import * as Acct from "@/misc/acct.js";
import { envOption } from "@/env.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 activityPub from "./activitypub.js";
import nodeinfo from "./nodeinfo.js"; import nodeinfo from "./nodeinfo.js";
import wellKnown from "./well-known.js"; import wellKnown from "./well-known.js";
@ -30,6 +29,7 @@ import fileServer from "./file/index.js";
import proxyServer from "./proxy/index.js"; import proxyServer from "./proxy/index.js";
import webServer from "./web/index.js"; import webServer from "./web/index.js";
import { initializeStreamingServer } from "./api/streaming.js"; import { initializeStreamingServer } from "./api/streaming.js";
import { koaBody } from "koa-body";
export const serverLogger = new Logger("server", "gray", false); export const serverLogger = new Logger("server", "gray", false);
@ -70,6 +70,11 @@ app.use(mount("/proxy", proxyServer));
// Init router // Init router
const router = new Router(); const router = new Router();
const mastoRouter = new Router();
mastoRouter.use(koaBody({
urlencoded: true
}));
// Routing // Routing
router.use(activityPub.routes()); 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; const client_id = ctx.request.query.client_id;
console.log(ctx.request.req); 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; 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 BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const generator = (megalodon as any).default; const generator = (megalodon as any).default;
const client = generator('misskey', BASE_URL, null) as MegalodonInterface; const client = generator("misskey", BASE_URL, null) as MegalodonInterface;
const m = body.code.match(/^[a-zA-Z0-9-]+/); let m = null;
if (!m.length) return { error: 'Invalid code' } 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 { 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 = { ctx.body = {
access_token: atData.accessToken, access_token: atData.accessToken,
token_type: 'Bearer', token_type: "Bearer",
scope: 'read write follow', scope: "read write follow",
created_at: new Date().getTime() / 1000 created_at: Math.floor(new Date().getTime() / 1000),
}; };
} catch (err: any) { } catch (err: any) {
console.error(err); console.error(err);
@ -164,6 +185,7 @@ router.post("/oauth/token", async (ctx) => {
}); });
// Register router // Register router
app.use(mastoRouter.routes());
app.use(router.routes()); app.use(router.routes());
app.use(mount(webServer)); app.use(mount(webServer));

View file

@ -44,6 +44,23 @@ export const urlPreviewHandler = async (ctx: Koa.Context) => {
logger.succ(`Got preview of ${url}: ${summary.title}`); 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.icon = wrap(summary.icon);
summary.thumbnail = wrap(summary.thumbnail); summary.thumbnail = wrap(summary.thumbnail);

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View file

@ -67,6 +67,7 @@ const embedId = `embed${Math.random().toString().replace(/\D/,'')}`;
let tweetHeight = $ref(150); let tweetHeight = $ref(150);
const requestUrl = new URL(props.url); 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') { if (requestUrl.hostname === 'twitter.com' || requestUrl.hostname === 'mobile.twitter.com') {
const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/); const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/);

View file

@ -33,6 +33,7 @@ const props = defineProps<{
const self = props.url.startsWith(local); const self = props.url.startsWith(local);
const url = new URL(props.url); const url = new URL(props.url);
if (!['http:', 'https:'].includes(url.protocol)) throw new Error('invalid url');
const el = ref(); const el = ref();
useTooltip(el, (showing) => { useTooltip(el, (showing) => {

View file

@ -377,12 +377,7 @@ export default defineComponent({
case "quote": { case "quote": {
if (!this.nowrap) { if (!this.nowrap) {
return [ return [h("blockquote", genEl(token.children))];
h(
"blockquote",
genEl(token.children),
),
];
} else { } else {
return [ return [
h( h(

View file

@ -5,10 +5,10 @@ import * as os from "@/os";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { ui } from "@/config"; import { ui } from "@/config";
import { unisonReload } from "@/scripts/unison-reload"; import { unisonReload } from "@/scripts/unison-reload";
import { defaultStore } from '@/store'; import { defaultStore } from "@/store";
import { instance } from '@/instance'; import { instance } from "@/instance";
import { host } from '@/config'; import { host } from "@/config";
import XTutorial from '@/components/MkTutorialDialog.vue'; import XTutorial from "@/components/MkTutorialDialog.vue";
export const navbarItemDef = reactive({ export const navbarItemDef = reactive({
notifications: { notifications: {
@ -152,54 +152,68 @@ export const navbarItemDef = reactive({
title: "help", title: "help",
icon: "ph-question-bold ph-lg", icon: "ph-question-bold ph-lg",
action: (ev) => { action: (ev) => {
os.popupMenu([{ os.popupMenu(
[
{
text: instance.name ?? host, text: instance.name ?? host,
type: 'label', type: "label",
}, { },
type: 'link', {
type: "link",
text: i18n.ts.instanceInfo, text: i18n.ts.instanceInfo,
icon: 'ph-info-bold ph-lg', icon: "ph-info-bold ph-lg",
to: '/about', to: "/about",
}, { },
type: 'link', {
type: "link",
text: i18n.ts.aboutMisskey, text: i18n.ts.aboutMisskey,
icon: 'ph-lightbulb-bold ph-lg', icon: "ph-lightbulb-bold ph-lg",
to: '/about-calckey', to: "/about-calckey",
}, { },
type: 'link', {
type: "link",
text: i18n.ts._apps.apps, text: i18n.ts._apps.apps,
icon: 'ph-device-mobile-bold ph-lg', icon: "ph-device-mobile-bold ph-lg",
to: '/apps', to: "/apps",
}, { },
type: 'button', {
type: "button",
action: async () => { action: async () => {
defaultStore.set('tutorial', 0); defaultStore.set("tutorial", 0);
os.popup(XTutorial, {}, {}, 'closed'); os.popup(XTutorial, {}, {}, "closed");
}, },
text: i18n.ts.replayTutorial, text: i18n.ts.replayTutorial,
icon: 'ph-circle-wavy-question-bold ph-lg', 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');
}, },
}, { null,
type: 'link', {
to: '/scratchpad', type: "parent",
text: 'AiScript Scratchpad', text: i18n.ts.developer,
icon: 'ph-scribble-loop-bold ph-lg', icon: "ph-code-bold ph-lg",
}] children: [
}], ev.currentTarget ?? ev.target, {
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,
); );
}, },
}, },

View file

@ -80,6 +80,8 @@ export default defineComponent({
this.state = 'accepted'; 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; }, {}); 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) { 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 || ''}`; location.href = `${this.session.app.callbackUrl}?token=${this.session.token}&code=${this.session.token}&state=${getUrlParams().state || ''}`;
} }
}, onLogin(res) { }, onLogin(res) {

View file

@ -13,7 +13,7 @@
<swiper-slide> <swiper-slide>
<div class="_formRoot"> <div class="_formRoot">
<div class="fnfelxur"> <div class="fnfelxur">
<img :src="instance.iconUrl || instance.faviconUrl" alt="" class="icon"/> <img :src="faviconUrl" alt="" class="icon"/>
<span class="name">{{ instance.name || `(${i18n.ts.unknown})` }}</span> <span class="name">{{ instance.name || `(${i18n.ts.unknown})` }}</span>
</div> </div>
<MkKeyValue :copy="host" oneline style="margin: 1em 0;"> <MkKeyValue :copy="host" oneline style="margin: 1em 0;">
@ -156,6 +156,7 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue';
import MkPagination from '@/components/MkPagination.vue'; import MkPagination from '@/components/MkPagination.vue';
import 'swiper/scss'; import 'swiper/scss';
import 'swiper/scss/virtual'; import 'swiper/scss/virtual';
import { getProxiedImageUrlNullable } from '@/scripts/media-proxy';
const props = defineProps<{ const props = defineProps<{
host: string; host: string;
@ -171,6 +172,7 @@ let meta = $ref<misskey.entities.DetailedInstanceMetadata | null>(null);
let instance = $ref<misskey.entities.Instance | null>(null); let instance = $ref<misskey.entities.Instance | null>(null);
let suspended = $ref(false); let suspended = $ref(false);
let isBlocked = $ref(false); let isBlocked = $ref(false);
let faviconUrl = $ref(null);
const usersPagination = { const usersPagination = {
endpoint: iAmModerator ? 'admin/show-users' : 'users' as const, endpoint: iAmModerator ? 'admin/show-users' : 'users' as const,
@ -189,6 +191,7 @@ async function fetch() {
}); });
suspended = instance.isSuspended; suspended = instance.isSuspended;
isBlocked = instance.isBlocked; isBlocked = instance.isBlocked;
faviconUrl = getProxiedImageUrlNullable(instance.faviconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.iconUrl, 'preview');
} }
async function toggleBlock(ev) { async function toggleBlock(ev) {

View file

@ -70,6 +70,8 @@ async function accept(): Promise<void> {
state = 'accepted'; state = 'accepted';
if (props.callback) { if (props.callback) {
const cbUrl = new URL(props.callback);
if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(cbUrl.protocol)) throw new Error('invalid url');
location.href = appendQuery(props.callback, query({ location.href = appendQuery(props.callback, query({
session: props.session, session: props.session,
})); }));

View file

@ -24,7 +24,11 @@ export function createAiScriptEnv(opts) {
return confirm.canceled ? values.FALSE : values.TRUE; return confirm.canceled ? values.FALSE : values.TRUE;
}), }),
"Mk:api": values.FN_NATIVE(async ([ep, param, token]) => { "Mk:api": values.FN_NATIVE(async ([ep, param, token]) => {
if (token) utils.assertString(token); if (token) {
utils.assertString(token);
// バグがあればundefinedもあり得るため念のため
if (typeof token.value !== "string") throw new Error("invalid token");
}
apiRequests++; apiRequests++;
if (apiRequests > 16) return values.NULL; if (apiRequests > 16) return values.NULL;
const res = await os.api( const res = await os.api(

View file

@ -125,19 +125,22 @@ export function getUserMenu(user, router: Router = mainRouter) {
) )
return; return;
await os.apiWithDialog(user.isBlocking ? "blocking/delete" : "blocking/create", { await os.apiWithDialog(
user.isBlocking ? "blocking/delete" : "blocking/create",
{
userId: user.id, userId: user.id,
}) },
);
user.isBlocking = !user.isBlocking; user.isBlocking = !user.isBlocking;
await os.api(user.isBlocking ? "mute/create" : "mute/delete", { await os.api(user.isBlocking ? "mute/create" : "mute/delete", {
userId: user.id, userId: user.id,
}) });
user.isMuted = user.isBlocking; user.isMuted = user.isBlocking;
if (user.isBlocking) { if (user.isBlocking) {
await os.api('following/delete', { await os.api("following/delete", {
userId: user.id, userId: user.id,
}); });
user.isFollowing = false user.isFollowing = false;
} }
} }

View file

@ -4,7 +4,7 @@
<transition name="change" mode="default"> <transition name="change" mode="default">
<MarqueeText :key="key" :duration="marqueeDuration" :reverse="marqueeReverse"> <MarqueeText :key="key" :duration="marqueeDuration" :reverse="marqueeReverse">
<span v-for="instance in instances" :key="instance.id" class="item" :class="{ colored }" :style="{ background: colored ? instance.themeColor : null }"> <span v-for="instance in instances" :key="instance.id" class="item" :class="{ colored }" :style="{ background: colored ? instance.themeColor : null }">
<img v-if="instance.iconUrl" class="icon" :src="instance.iconUrl" alt=""/> <img class="icon" :src="getInstanceIcon(instance)" alt=""/>
<MkA :to="`/instance-info/${instance.host}`" class="host _monospace"> <MkA :to="`/instance-info/${instance.host}`" class="host _monospace">
{{ instance.host }} {{ instance.host }}
</MkA> </MkA>
@ -27,6 +27,7 @@ import * as os from '@/os';
import { useInterval } from '@/scripts/use-interval'; import { useInterval } from '@/scripts/use-interval';
import { getNoteSummary } from '@/scripts/get-note-summary'; import { getNoteSummary } from '@/scripts/get-note-summary';
import { notePage } from '@/filters/note'; import { notePage } from '@/filters/note';
import { getProxiedImageUrlNullable } from '@/scripts/media-proxy';
const props = defineProps<{ const props = defineProps<{
display?: 'marquee' | 'oneByOne'; display?: 'marquee' | 'oneByOne';
@ -56,6 +57,10 @@ useInterval(tick, Math.max(5000, props.refreshIntervalSec * 1000), {
immediate: true, immediate: true,
afterMounted: true, afterMounted: true,
}); });
function getInstanceIcon(instance): string {
return getProxiedImageUrlNullable(instance.iconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.faviconUrl, 'preview') ?? '/client-assets/dummy.png';
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -6,7 +6,7 @@
<MkLoading v-if="fetching"/> <MkLoading v-if="fetching"/>
<transition-group v-else tag="div" :name="$store.state.animation ? 'chart' : ''" class="instances"> <transition-group v-else tag="div" :name="$store.state.animation ? 'chart' : ''" class="instances">
<div v-for="(instance, i) in instances" :key="instance.id" class="instance"> <div v-for="(instance, i) in instances" :key="instance.id" class="instance">
<img v-if="instance.iconUrl" :src="instance.iconUrl" alt=""/> <img :src="getInstanceIcon(instance)" alt=""/>
<div class="body"> <div class="body">
<a class="a" :href="'https://' + instance.host" target="_blank" :title="instance.host">{{ instance.host }}</a> <a class="a" :href="'https://' + instance.host" target="_blank" :title="instance.host">{{ instance.host }}</a>
<p>{{ instance.softwareName || '?' }} {{ instance.softwareVersion }}</p> <p>{{ instance.softwareName || '?' }} {{ instance.softwareVersion }}</p>
@ -27,6 +27,7 @@ import MkMiniChart from '@/components/MkMiniChart.vue';
import * as os from '@/os'; import * as os from '@/os';
import { useInterval } from '@/scripts/use-interval'; import { useInterval } from '@/scripts/use-interval';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { getProxiedImageUrlNullable } from '@/scripts/media-proxy';
const name = 'federation'; const name = 'federation';
@ -71,6 +72,10 @@ useInterval(fetch, 1000 * 60, {
afterMounted: true, afterMounted: true,
}); });
function getInstanceIcon(instance): string {
return getProxiedImageUrlNullable(instance.iconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.faviconUrl, 'preview') ?? '/client-assets/dummy.png';
}
defineExpose<WidgetComponentExpose>({ defineExpose<WidgetComponentExpose>({
name, name,
configure, configure,

View file

@ -4,7 +4,7 @@
<MkTagCloud v-if="activeInstances"> <MkTagCloud v-if="activeInstances">
<li v-for="instance in activeInstances" :key="instance.id"> <li v-for="instance in activeInstances" :key="instance.id">
<a @click.prevent="onInstanceClick(instance)"> <a @click.prevent="onInstanceClick(instance)">
<img style="width: 32px;" :src="instance.iconUrl"> <img style="width: 32px;" :src="getInstanceIcon(instance)">
</a> </a>
</li> </li>
</MkTagCloud> </MkTagCloud>
@ -21,6 +21,7 @@ import MkContainer from '@/components/MkContainer.vue';
import MkTagCloud from '@/components/MkTagCloud.vue'; import MkTagCloud from '@/components/MkTagCloud.vue';
import * as os from '@/os'; import * as os from '@/os';
import { useInterval } from '@/scripts/use-interval'; import { useInterval } from '@/scripts/use-interval';
import { getProxiedImageUrlNullable } from '@/scripts/media-proxy';
const name = 'instanceCloud'; const name = 'instanceCloud';
@ -65,6 +66,10 @@ useInterval(() => {
afterMounted: true, afterMounted: true,
}); });
function getInstanceIcon(instance): string {
return getProxiedImageUrlNullable(instance.iconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.faviconUrl, 'preview') ?? '/client-assets/dummy.png';
}
defineExpose<WidgetComponentExpose>({ defineExpose<WidgetComponentExpose>({
name, name,
configure, configure,

View file

@ -3335,7 +3335,7 @@ packages:
/axios/0.24.0: /axios/0.24.0:
resolution: {integrity: sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==} resolution: {integrity: sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==}
dependencies: dependencies:
follow-redirects: 1.15.2 follow-redirects: 1.15.2_debug@4.3.4
transitivePeerDependencies: transitivePeerDependencies:
- debug - debug
dev: false dev: false
@ -3343,7 +3343,7 @@ packages:
/axios/0.25.0_debug@4.3.4: /axios/0.25.0_debug@4.3.4:
resolution: {integrity: sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==} resolution: {integrity: sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==}
dependencies: dependencies:
follow-redirects: 1.15.2 follow-redirects: 1.15.2_debug@4.3.4
transitivePeerDependencies: transitivePeerDependencies:
- debug - debug
dev: true dev: true
@ -3351,7 +3351,7 @@ packages:
/axios/1.2.2: /axios/1.2.2:
resolution: {integrity: sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==} resolution: {integrity: sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==}
dependencies: dependencies:
follow-redirects: 1.15.2 follow-redirects: 1.15.2_debug@4.3.4
form-data: 4.0.0 form-data: 4.0.0
proxy-from-env: 1.1.0 proxy-from-env: 1.1.0
transitivePeerDependencies: transitivePeerDependencies:
@ -3361,7 +3361,7 @@ packages:
/axios/1.3.2: /axios/1.3.2:
resolution: {integrity: sha512-1M3O703bYqYuPhbHeya5bnhpYVsDDRyQSabNja04mZtboLNSuZ4YrltestrLXfHgmzua4TpUqRiVKbiQuo2epw==} resolution: {integrity: sha512-1M3O703bYqYuPhbHeya5bnhpYVsDDRyQSabNja04mZtboLNSuZ4YrltestrLXfHgmzua4TpUqRiVKbiQuo2epw==}
dependencies: dependencies:
follow-redirects: 1.15.2 follow-redirects: 1.15.2_debug@4.3.4
form-data: 4.0.0 form-data: 4.0.0
proxy-from-env: 1.1.0 proxy-from-env: 1.1.0
transitivePeerDependencies: transitivePeerDependencies:
@ -6301,7 +6301,7 @@ packages:
readable-stream: 2.3.7 readable-stream: 2.3.7
dev: true dev: true
/follow-redirects/1.15.2: /follow-redirects/1.15.2_debug@4.3.4:
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
engines: {node: '>=4.0'} engines: {node: '>=4.0'}
peerDependencies: peerDependencies:
@ -6309,6 +6309,8 @@ packages:
peerDependenciesMeta: peerDependenciesMeta:
debug: debug:
optional: true optional: true
dependencies:
debug: 4.3.4
/for-each/0.3.3: /for-each/0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}