formatting

This commit is contained in:
ThatOneCalculator 2023-03-30 19:10:03 -07:00
parent 5be627b869
commit 19c4a59513
27 changed files with 463 additions and 366 deletions

View file

@ -1 +1 @@
declare module 'koa-remove-trailing-slashes';
declare module "koa-remove-trailing-slashes";

View file

@ -7,32 +7,26 @@ const logger = dbLogger.createSubLogger("sonic", "gray", false);
logger.info("Connecting to Sonic");
const handlers = (type: string): SonicChannel.Handlers => (
{
connected: () => {
logger.succ(`Connected to Sonic ${type}`);
},
disconnected: (error) => {
logger.warn(`Disconnected from Sonic ${type}, error: ${error}`);
},
error: (error) => {
logger.warn(`Sonic ${type} error: ${error}`);
},
retrying: () => {
logger.info(`Sonic ${type} retrying`);
},
timeout: () => {
logger.warn(`Sonic ${type} timeout`);
},
}
)
const handlers = (type: string): SonicChannel.Handlers => ({
connected: () => {
logger.succ(`Connected to Sonic ${type}`);
},
disconnected: (error) => {
logger.warn(`Disconnected from Sonic ${type}, error: ${error}`);
},
error: (error) => {
logger.warn(`Sonic ${type} error: ${error}`);
},
retrying: () => {
logger.info(`Sonic ${type} retrying`);
},
timeout: () => {
logger.warn(`Sonic ${type} timeout`);
},
});
const hasConfig =
config.sonic
&& ( config.sonic.host
|| config.sonic.port
|| config.sonic.auth
)
config.sonic && (config.sonic.host || config.sonic.port || config.sonic.auth);
const host = hasConfig ? config.sonic.host ?? "localhost" : "";
const port = hasConfig ? config.sonic.port ?? 1491 : 0;
@ -42,10 +36,14 @@ const bucket = hasConfig ? config.sonic.bucket ?? "default" : "";
export default hasConfig
? {
search: new SonicChannel.Search({host, port, auth}).connect(handlers("search")),
ingest: new SonicChannel.Ingest({host, port, auth}).connect(handlers("ingest")),
search: new SonicChannel.Search({ host, port, auth }).connect(
handlers("search"),
),
ingest: new SonicChannel.Ingest({ host, port, auth }).connect(
handlers("ingest"),
),
collection,
bucket,
}
}
: null;

View file

@ -6,7 +6,12 @@ export type Post = {
};
export function parse(acct: any): Post {
return { text: acct.text, cw: acct.cw, localOnly: acct.localOnly, createdAt: new Date(acct.createdAt) };
return {
text: acct.text,
cw: acct.cw,
localOnly: acct.localOnly,
createdAt: new Date(acct.createdAt),
};
}
export function toJson(acct: Post): string {

View file

@ -440,14 +440,10 @@ export function createCleanRemoteFilesJob() {
}
export function createIndexAllNotesJob(data = {}) {
return backgroundQueue.add(
"indexAllNotes",
data,
{
removeOnComplete: true,
removeOnFail: true,
},
);
return backgroundQueue.add("indexAllNotes", data, {
removeOnComplete: true,
removeOnFail: true,
});
}
export function webhookDeliver(

View file

@ -3,26 +3,30 @@ import type Bull from "bull";
import { queueLogger } from "../../logger.js";
import { Notes } from "@/models/index.js";
import { MoreThan } from "typeorm";
import { index } from "@/services/note/create.js"
import { index } from "@/services/note/create.js";
import { Note } from "@/models/entities/note.js";
const logger = queueLogger.createSubLogger("index-all-notes");
export default async function indexAllNotes(
job: Bull.Job<Record<string, unknown>>,
done: ()=>void,
done: () => void,
): Promise<void> {
logger.info("Indexing all notes...");
let cursor: string|null = job.data.cursor as string ?? null;
let indexedCount: number = job.data.indexedCount as number ?? 0;
let total: number = job.data.total as number ?? 0;
let cursor: string | null = (job.data.cursor as string) ?? null;
let indexedCount: number = (job.data.indexedCount as number) ?? 0;
let total: number = (job.data.total as number) ?? 0;
let running = true;
const take = 50000;
const batch = 100;
while (running) {
logger.info(`Querying for ${take} notes ${indexedCount}/${total ? total : '?'} at ${cursor}`);
logger.info(
`Querying for ${take} notes ${indexedCount}/${
total ? total : "?"
} at ${cursor}`,
);
let notes: Note[] = [];
try {
@ -49,22 +53,21 @@ export default async function indexAllNotes(
try {
const count = await Notes.count();
total = count;
job.update({ indexedCount, cursor, total })
} catch (e) {
}
job.update({ indexedCount, cursor, total });
} catch (e) {}
for (let i = 0; i < notes.length; i += batch) {
const chunk = notes.slice(i, i + batch);
await Promise.all(chunk.map(note => index(note)));
await Promise.all(chunk.map((note) => index(note)));
indexedCount += chunk.length;
const pct = (indexedCount / total)*100;
job.update({ indexedCount, cursor, total })
job.progress(+(pct.toFixed(1)));
logger.info(`Indexed notes ${indexedCount}/${total ? total : '?'}`);
const pct = (indexedCount / total) * 100;
job.update({ indexedCount, cursor, total });
job.progress(+pct.toFixed(1));
logger.info(`Indexed notes ${indexedCount}/${total ? total : "?"}`);
}
cursor = notes[notes.length - 1].id;
job.update({ indexedCount, cursor, total })
job.update({ indexedCount, cursor, total });
if (notes.length < take) {
running = false;

View file

@ -3,10 +3,7 @@ import indexAllNotes from "./index-all-notes.js";
const jobs = {
indexAllNotes,
} as Record<
string,
Bull.ProcessCallbackFunction<Record<string, unknown>>
>;
} as Record<string, Bull.ProcessCallbackFunction<Record<string, unknown>>>;
export default function (q: Bull.Queue) {
for (const [k, v] of Object.entries(jobs)) {

View file

@ -88,7 +88,7 @@ export async function importPosts(
continue;
}
if (job.data.signatureCheck) {
if(!post.signature) {
if (!post.signature) {
continue;
}
}
@ -99,7 +99,7 @@ export async function importPosts(
continue;
}
logger.info(`Posting[${linenum}] ...`);
const note = await create(user, {
createdAt: new Date(post.object.published),
files: undefined,

View file

@ -20,7 +20,7 @@ export default async (job: Bull.Job<WebhookDeliverJobData>) => {
"X-Calckey-Host": config.host,
"X-Calckey-Hook-Id": job.data.webhookId,
"X-Calckey-Hook-Secret": job.data.secret,
'Content-Type': 'application/json'
"Content-Type": "application/json",
},
body: JSON.stringify({
hookId: job.data.webhookId,

View file

@ -205,7 +205,9 @@ export async function createPerson(
if (typeof person.followers === "string") {
try {
let data = await fetch(person.followers, { headers: { "Accept": "application/json" } });
let data = await fetch(person.followers, {
headers: { Accept: "application/json" },
});
let json_data = JSON.parse(await data.text());
followersCount = json_data.totalItems;
@ -218,7 +220,9 @@ export async function createPerson(
if (typeof person.following === "string") {
try {
let data = await fetch(person.following, { headers: { "Accept": "application/json" } });
let data = await fetch(person.following, {
headers: { Accept: "application/json" },
});
let json_data = JSON.parse(await data.text());
followingCount = json_data.totalItems;
@ -227,7 +231,6 @@ export async function createPerson(
}
}
// Create user
let user: IRemoteUser;
try {
@ -255,14 +258,20 @@ export async function createPerson(
followersUri: person.followers
? getApId(person.followers)
: undefined,
followersCount: followersCount !== undefined
? followersCount
: person.followers && typeof person.followers !== "string" && isCollectionOrOrderedCollection(person.followers)
followersCount:
followersCount !== undefined
? followersCount
: person.followers &&
typeof person.followers !== "string" &&
isCollectionOrOrderedCollection(person.followers)
? person.followers.totalItems
: undefined,
followingCount: followingCount !== undefined
? followingCount
: person.following && typeof person.following !== "string" && isCollectionOrOrderedCollection(person.following)
followingCount:
followingCount !== undefined
? followingCount
: person.following &&
typeof person.following !== "string" &&
isCollectionOrOrderedCollection(person.following)
? person.following.totalItems
: undefined,
featured: person.featured ? getApId(person.featured) : undefined,
@ -440,7 +449,9 @@ export async function updatePerson(
if (typeof person.followers === "string") {
try {
let data = await fetch(person.followers, { headers: { "Accept": "application/json" } } );
let data = await fetch(person.followers, {
headers: { Accept: "application/json" },
});
let json_data = JSON.parse(await data.text());
followersCount = json_data.totalItems;
@ -449,12 +460,13 @@ export async function updatePerson(
}
}
let followingCount: number | undefined;
if (typeof person.following === "string") {
try {
let data = await fetch(person.following, { headers: { "Accept": "application/json" } } );
let data = await fetch(person.following, {
headers: { Accept: "application/json" },
});
let json_data = JSON.parse(await data.text());
followingCount = json_data.totalItems;
@ -470,14 +482,20 @@ export async function updatePerson(
person.sharedInbox ||
(person.endpoints ? person.endpoints.sharedInbox : undefined),
followersUri: person.followers ? getApId(person.followers) : undefined,
followersCount: followersCount !== undefined
? followersCount
: person.followers && typeof person.followers !== "string" && isCollectionOrOrderedCollection(person.followers)
followersCount:
followersCount !== undefined
? followersCount
: person.followers &&
typeof person.followers !== "string" &&
isCollectionOrOrderedCollection(person.followers)
? person.followers.totalItems
: undefined,
followingCount: followingCount !== undefined
? followingCount
: person.following && typeof person.following !== "string" && isCollectionOrOrderedCollection(person.following)
followingCount:
followingCount !== undefined
? followingCount
: person.following &&
typeof person.following !== "string" &&
isCollectionOrOrderedCollection(person.following)
? person.following.totalItems
: undefined,
featured: person.featured,

View file

@ -54,7 +54,11 @@ export const paramDef = {
folderId: { type: "string", format: "misskey:id", nullable: true },
name: { type: "string" },
isSensitive: { type: "boolean" },
comment: { type: "string", nullable: true, maxLength: DB_MAX_IMAGE_COMMENT_LENGTH },
comment: {
type: "string",
nullable: true,
maxLength: DB_MAX_IMAGE_COMMENT_LENGTH,
},
},
required: ["fileId"],
} as const;

View file

@ -11,7 +11,8 @@ export const meta = {
res: {
type: "object",
optional: false, nullable: false,
optional: false,
nullable: false,
ref: "Emoji",
},
} as const;

View file

@ -151,7 +151,7 @@ export default define(meta, paramDef, async (ps, user) => {
}
// テキストが無いかつ添付ファイルも無かったらエラー
if ((ps.text == null || ps.text.trim() === '') && file == null) {
if ((ps.text == null || ps.text.trim() === "") && file == null) {
throw new ApiError(meta.errors.contentRequired);
}

View file

@ -139,7 +139,7 @@ export default define(meta, paramDef, async (ps, me) => {
})
.map((key) => key.id);
ids.push(...res);
ids.push(...res);
}
// Sort all the results by note id DESC (newest first)
@ -160,7 +160,7 @@ export default define(meta, paramDef, async (ps, me) => {
});
// The notes are checked for visibility and muted/blocked users when packed
found.push(...await Notes.packMany(notes, me));
found.push(...(await Notes.packMany(notes, me)));
start += chunkSize;
}

View file

@ -7,7 +7,10 @@ import Router from "@koa/router";
import multer from "@koa/multer";
import bodyParser from "koa-bodyparser";
import cors from "@koa/cors";
import { apiMastodonCompatible, getClient } from "./mastodon/ApiMastodonCompatibleService.js";
import {
apiMastodonCompatible,
getClient,
} from "./mastodon/ApiMastodonCompatibleService.js";
import { Instances, AccessTokens, Users } from "@/models/index.js";
import config from "@/config/index.js";
import fs from "fs";
@ -21,10 +24,10 @@ import discord from "./service/discord.js";
import github from "./service/github.js";
import twitter from "./service/twitter.js";
import { koaBody } from "koa-body";
import { convertId, IdConvertType as IdType } from "native-utils"
import { convertId, IdConvertType as IdType } from "native-utils";
// re-export native rust id conversion (function and enum)
export { IdType, convertId };
export { IdType, convertId };
// Init app
const app = new Koa();
@ -74,7 +77,6 @@ mastoRouter.use(
}),
);
mastoFileRouter.post("/v1/media", upload.single("file"), async (ctx) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;

View file

@ -77,7 +77,10 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.search((ctx.request.query as any).acct, 'accounts');
const data = await client.search(
(ctx.request.query as any).acct,
"accounts",
);
let resp = data.data.accounts[0];
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
@ -88,26 +91,23 @@ export function apiAccountMastodon(router: Router): void {
ctx.body = e.response.data;
}
});
router.get<{ Params: { id: string } }>(
"/v1/accounts/:id",
async (ctx) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const calcId = convertId(ctx.params.id, IdType.CalckeyId);
const data = await client.getAccount(calcId);
let resp = data.data;
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) {
console.error(e);
console.error(e.response.data);
ctx.status = 401;
ctx.body = e.response.data;
}
},
);
router.get<{ Params: { id: string } }>("/v1/accounts/:id", async (ctx) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const calcId = convertId(ctx.params.id, IdType.CalckeyId);
const data = await client.getAccount(calcId);
let resp = data.data;
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) {
console.error(e);
console.error(e.response.data);
ctx.status = 401;
ctx.body = e.response.data;
}
});
router.get<{ Params: { id: string } }>(
"/v1/accounts/:id/statuses",
async (ctx) => {
@ -122,11 +122,19 @@ export function apiAccountMastodon(router: Router): void {
let resp = data.data;
for (let statIdx = 0; statIdx < resp.length; statIdx++) {
resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId);
resp[statIdx].in_reply_to_account_id = resp[statIdx].in_reply_to_account_id ? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId) : null;
resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id ? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId) : null;
let mentions = resp[statIdx].mentions
resp[statIdx].in_reply_to_account_id = resp[statIdx]
.in_reply_to_account_id
? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId)
: null;
resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id
? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId)
: null;
let mentions = resp[statIdx].mentions;
for (let mtnIdx = 0; mtnIdx < mentions.length; mtnIdx++) {
resp[statIdx].mentions[mtnIdx].id = convertId(mentions[mtnIdx].id, IdType.MastodonId);
resp[statIdx].mentions[mtnIdx].id = convertId(
mentions[mtnIdx].id,
IdType.MastodonId,
);
}
}
ctx.body = resp;
@ -210,7 +218,9 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.followAccount(convertId(ctx.params.id, IdType.CalckeyId));
const data = await client.followAccount(
convertId(ctx.params.id, IdType.CalckeyId),
);
let acct = data.data;
acct.following = true;
acct.id = convertId(acct.id, IdType.MastodonId);
@ -230,7 +240,9 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.unfollowAccount(convertId(ctx.params.id, IdType.CalckeyId));
const data = await client.unfollowAccount(
convertId(ctx.params.id, IdType.CalckeyId),
);
let acct = data.data;
acct.id = convertId(acct.id, IdType.MastodonId);
acct.following = false;
@ -250,7 +262,9 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.blockAccount(convertId(ctx.params.id, IdType.CalckeyId));
const data = await client.blockAccount(
convertId(ctx.params.id, IdType.CalckeyId),
);
let resp = data.data;
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
@ -269,7 +283,9 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.unblockAccount(convertId(ctx.params.id, IdType.MastodonId));
const data = await client.unblockAccount(
convertId(ctx.params.id, IdType.MastodonId),
);
let resp = data.data;
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
@ -310,7 +326,9 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.unmuteAccount(convertId(ctx.params.id, IdType.CalckeyId));
const data = await client.unmuteAccount(
convertId(ctx.params.id, IdType.CalckeyId),
);
let resp = data.data;
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
@ -344,7 +362,7 @@ export function apiAccountMastodon(router: Router): void {
for (let i = 0; i < ids.length; i++) {
reqIds.push(convertId(ids[i], IdType.CalckeyId));
}
const data = await client.getRelationships(reqIds);
let resp = data.data;
for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) {
@ -365,15 +383,25 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = (await client.getBookmarks(limitToInt(ctx.query as any))) as any;
const data = (await client.getBookmarks(
limitToInt(ctx.query as any),
)) as any;
let resp = data.data;
for (let statIdx = 0; statIdx < resp.length; statIdx++) {
resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId);
resp[statIdx].in_reply_to_account_id = resp[statIdx].in_reply_to_account_id ? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId) : null;
resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id ? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId) : null;
let mentions = resp[statIdx].mentions
resp[statIdx].in_reply_to_account_id = resp[statIdx]
.in_reply_to_account_id
? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId)
: null;
resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id
? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId)
: null;
let mentions = resp[statIdx].mentions;
for (let mtnIdx = 0; mtnIdx < mentions.length; mtnIdx++) {
resp[statIdx].mentions[mtnIdx].id = convertId(mentions[mtnIdx].id, IdType.MastodonId);
resp[statIdx].mentions[mtnIdx].id = convertId(
mentions[mtnIdx].id,
IdType.MastodonId,
);
}
}
ctx.body = resp;
@ -393,11 +421,19 @@ export function apiAccountMastodon(router: Router): void {
let resp = data.data;
for (let statIdx = 0; statIdx < resp.length; statIdx++) {
resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId);
resp[statIdx].in_reply_to_account_id = resp[statIdx].in_reply_to_account_id ? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId) : null;
resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id ? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId) : null;
let mentions = resp[statIdx].mentions
resp[statIdx].in_reply_to_account_id = resp[statIdx]
.in_reply_to_account_id
? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId)
: null;
resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id
? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId)
: null;
let mentions = resp[statIdx].mentions;
for (let mtnIdx = 0; mtnIdx < mentions.length; mtnIdx++) {
resp[statIdx].mentions[mtnIdx].id = convertId(mentions[mtnIdx].id, IdType.MastodonId);
resp[statIdx].mentions[mtnIdx].id = convertId(
mentions[mtnIdx].id,
IdType.MastodonId,
);
}
}
ctx.body = resp;
@ -471,7 +507,9 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.acceptFollowRequest(convertId(ctx.params.id, IdType.CalckeyId));
const data = await client.acceptFollowRequest(
convertId(ctx.params.id, IdType.CalckeyId),
);
let resp = data.data;
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
@ -490,7 +528,9 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.rejectFollowRequest(convertId(ctx.params.id, IdType.CalckeyId));
const data = await client.rejectFollowRequest(
convertId(ctx.params.id, IdType.CalckeyId),
);
let resp = data.data;
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;

View file

@ -44,7 +44,7 @@ const writeScope = [
export function apiAuthMastodon(router: Router): void {
router.post("/v1/apps", async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const client = getClient(BASE_URL, '');
const client = getClient(BASE_URL, "");
const body: any = ctx.request.body || ctx.request.query;
try {
let scope = body.scopes;
@ -68,9 +68,9 @@ export function apiAuthMastodon(router: Router): void {
website: body.website,
redirect_uri: red,
client_id: Buffer.from(appData.url || "").toString("base64"),
client_secret: appData.clientSecret
client_secret: appData.clientSecret,
};
console.log(returns)
console.log(returns);
ctx.body = returns;
} catch (e: any) {
console.error(e);

View file

@ -11,17 +11,20 @@ export async function getInstance(response: Entity.Instance) {
return {
uri: response.uri,
title: response.title || "Calckey",
short_description: response.description.substring(0, 50) || "See real server website",
description: response.description || "This is a vanilla Calckey Instance. It doesnt seem to have a description. BTW you are using the Mastodon api to access this server :)",
short_description:
response.description.substring(0, 50) || "See real server website",
description:
response.description ||
"This is a vanilla Calckey Instance. It doesnt seem to have a description. BTW you are using the Mastodon api to access this server :)",
email: response.email || "",
version: "3.0.0 compatible (3.5+ Calckey)", //I hope this version string is correct, we will need to test it.
urls: response.urls,
stats: {
user_count: (await totalUsers),
status_count: (await totalStatuses),
domain_count: response.stats.domain_count
user_count: await totalUsers,
status_count: await totalStatuses,
domain_count: response.stats.domain_count,
},
thumbnail: response.thumbnail || 'https://http.cat/404',
thumbnail: response.thumbnail || "https://http.cat/404",
languages: meta.langs,
registrations: !meta.disableRegistration || response.registrations,
approval_required: !response.registrations,

View file

@ -44,7 +44,7 @@ export function apiSearchMastodon(router: Router): void {
}
} catch (e: any) {
console.error(e);
ctx.status = (401);
ctx.status = 401;
ctx.body = e.response.data;
}
});
@ -52,11 +52,15 @@ export function apiSearchMastodon(router: Router): void {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.headers.authorization;
try {
const data = await getHighlight(BASE_URL, ctx.request.hostname, accessTokens);
const data = await getHighlight(
BASE_URL,
ctx.request.hostname,
accessTokens,
);
ctx.body = data;
} catch (e: any) {
console.error(e);
ctx.status = (401);
ctx.status = 401;
ctx.body = e.response.data;
}
});
@ -75,7 +79,7 @@ export function apiSearchMastodon(router: Router): void {
ctx.body = data;
} catch (e: any) {
console.error(e);
ctx.status = (401);
ctx.status = 401;
ctx.body = e.response.data;
}
});

View file

@ -2,13 +2,13 @@ import Router from "@koa/router";
import { getClient } from "../ApiMastodonCompatibleService.js";
import { emojiRegexAtStartToEnd } from "@/misc/emoji-regex.js";
import axios from "axios";
import querystring from 'node:querystring'
import qs from 'qs'
import querystring from "node:querystring";
import qs from "qs";
import { limitToInt } from "./timeline.js";
function normalizeQuery(data: any) {
const str = querystring.stringify(data);
return qs.parse(str);
const str = querystring.stringify(data);
return qs.parse(str);
}
export function apiStatusMastodon(router: Router): void {
@ -18,11 +18,14 @@ export function apiStatusMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens);
try {
let body: any = ctx.request.body;
if ((!body.poll && body['poll[options][]']) || (!body.media_ids && body['media_ids[]'])) {
body = normalizeQuery(body)
if (
(!body.poll && body["poll[options][]"]) ||
(!body.media_ids && body["media_ids[]"])
) {
body = normalizeQuery(body);
}
const text = body.status;
const removed = text.replace(/@\S+/g, "").replace(/\s|/g, '')
const removed = text.replace(/@\S+/g, "").replace(/\s|/g, "");
const isDefaultEmoji = emojiRegexAtStartToEnd.test(removed);
const isCustomEmoji = /^:[a-zA-Z0-9@_]+:$/.test(removed);
if ((body.in_reply_to_id && isDefaultEmoji) || isCustomEmoji) {
@ -47,8 +50,9 @@ export function apiStatusMastodon(router: Router): void {
}
if (!body.media_ids) body.media_ids = undefined;
if (body.media_ids && !body.media_ids.length) body.media_ids = undefined;
const { sensitive } = body
body.sensitive = typeof sensitive === 'string' ? sensitive === 'true' : sensitive
const { sensitive } = body;
body.sensitive =
typeof sensitive === "string" ? sensitive === "true" : sensitive;
const data = await client.postStatus(text, body);
ctx.body = data.data;
} catch (e: any) {
@ -57,38 +61,32 @@ export function apiStatusMastodon(router: Router): void {
ctx.body = e.response.data;
}
});
router.get<{ Params: { id: string } }>(
"/v1/statuses/:id",
async (ctx) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getStatus(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e);
ctx.status = 401;
ctx.body = e.response.data;
}
},
);
router.delete<{ Params: { id: string } }>(
"/v1/statuses/:id",
async (ctx) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.deleteStatus(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e.response.data, request.params.id);
ctx.status = 401;
ctx.body = e.response.data;
}
},
);
router.get<{ Params: { id: string } }>("/v1/statuses/:id", async (ctx) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getStatus(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e);
ctx.status = 401;
ctx.body = e.response.data;
}
});
router.delete<{ Params: { id: string } }>("/v1/statuses/:id", async (ctx) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.deleteStatus(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e.response.data, request.params.id);
ctx.status = 401;
ctx.body = e.response.data;
}
});
interface IReaction {
id: string;
createdAt: string;
@ -103,12 +101,15 @@ export function apiStatusMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens);
try {
const id = ctx.params.id;
const data = await client.getStatusContext(id, limitToInt(ctx.query as any));
const data = await client.getStatusContext(
id,
limitToInt(ctx.query as any),
);
const status = await client.getStatus(id);
let reqInstance = axios.create({
headers: {
Authorization : ctx.headers.authorization
}
Authorization: ctx.headers.authorization,
},
});
const reactionsAxios = await reqInstance.get(
`${BASE_URL}/api/notes/reactions?noteId=${id}`,
@ -296,57 +297,48 @@ export function apiStatusMastodon(router: Router): void {
}
},
);
router.get<{ Params: { id: string } }>(
"/v1/media/:id",
async (ctx) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getMedia(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e);
ctx.status = 401;
ctx.body = e.response.data;
}
},
);
router.put<{ Params: { id: string } }>(
"/v1/media/:id",
async (ctx) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.updateMedia(
ctx.params.id,
ctx.request.body as any,
);
ctx.body = data.data;
} catch (e: any) {
console.error(e);
ctx.status = 401;
ctx.body = e.response.data;
}
},
);
router.get<{ Params: { id: string } }>(
"/v1/polls/:id",
async (ctx) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getPoll(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e);
ctx.status = 401;
ctx.body = e.response.data;
}
},
);
router.get<{ Params: { id: string } }>("/v1/media/:id", async (ctx) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getMedia(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e);
ctx.status = 401;
ctx.body = e.response.data;
}
});
router.put<{ Params: { id: string } }>("/v1/media/:id", async (ctx) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.updateMedia(
ctx.params.id,
ctx.request.body as any,
);
ctx.body = data.data;
} catch (e: any) {
console.error(e);
ctx.status = 401;
ctx.body = e.response.data;
}
});
router.get<{ Params: { id: string } }>("/v1/polls/:id", async (ctx) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getPoll(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e);
ctx.status = 401;
ctx.body = e.response.data;
}
});
router.post<{ Params: { id: string } }>(
"/v1/polls/:id/votes",
async (ctx) => {

View file

@ -16,7 +16,8 @@ export function limitToInt(q: ParsedUrlQuery) {
export function argsToBools(q: ParsedUrlQuery) {
// Values taken from https://docs.joinmastodon.org/client/intro/#boolean
const toBoolean = (value: string) => !['0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].includes(value);
const toBoolean = (value: string) =>
!["0", "f", "F", "false", "FALSE", "off", "OFF"].includes(value);
let object: any = q;
if (q.only_media)
@ -35,26 +36,26 @@ export function toTextWithReaction(status: Entity.Status[], host: string) {
if (!t.emoji_reactions) return t;
if (t.reblog) t.reblog = toTextWithReaction([t.reblog], host)[0];
const reactions = t.emoji_reactions.map((r) => {
const emojiNotation = r.url ? `:${r.name.replace('@.', '')}:` : r.name
return `${emojiNotation} (${r.count}${r.me ? `* ` : ''})`
const emojiNotation = r.url ? `:${r.name.replace("@.", "")}:` : r.name;
return `${emojiNotation} (${r.count}${r.me ? `* ` : ""})`;
});
const reaction = t.emoji_reactions as Entity.Reaction[];
const emoji = t.emojis || []
const emoji = t.emojis || [];
for (const r of reaction) {
if (!r.url) continue
emoji.push({
'shortcode': r.name,
'url': r.url,
'static_url': r.url,
'visible_in_picker': true,
category: ""
},)
if (!r.url) continue;
emoji.push({
shortcode: r.name,
url: r.url,
static_url: r.url,
visible_in_picker: true,
category: "",
});
}
const isMe = reaction.findIndex((r) => r.me) > -1;
const total = reaction.reduce((sum, reaction) => sum + reaction.count, 0);
t.favourited = isMe;
t.favourites_count = total;
t.emojis = emoji
t.emojis = emoji;
t.content = `<p>${autoLinker(t.content, host)}</p><p>${reactions.join(
", ",
)}</p>`;
@ -126,23 +127,20 @@ export function apiTimelineMastodon(router: Router): void {
}
},
);
router.get(
"/v1/timelines/home",
async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getHomeTimeline(limitToInt(ctx.query));
ctx.body = toTextWithReaction(data.data, ctx.hostname);
} catch (e: any) {
console.error(e);
console.error(e.response.data);
ctx.status = 401;
ctx.body = e.response.data;
}
},
);
router.get("/v1/timelines/home", async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getHomeTimeline(limitToInt(ctx.query));
ctx.body = toTextWithReaction(data.data, ctx.hostname);
} catch (e: any) {
console.error(e);
console.error(e.response.data);
ctx.status = 401;
ctx.body = e.response.data;
}
});
router.get<{ Params: { listId: string } }>(
"/v1/timelines/list/:listId",
async (ctx, reply) => {

View file

@ -12,7 +12,11 @@ import {
} from "@/models/index.js";
import type { ILocalUser } from "@/models/entities/user.js";
import { genId } from "@/misc/gen-id.js";
import { comparePassword, hashPassword, isOldAlgorithm } from '@/misc/password.js';
import {
comparePassword,
hashPassword,
isOldAlgorithm,
} from "@/misc/password.js";
import { verifyLogin, hash } from "../2fa.js";
import { randomBytes } from "node:crypto";
import { IsNull } from "typeorm";

View file

@ -414,7 +414,7 @@ export default class Connection {
const client = getClient(this.host, this.accessToken);
client.getStatus(payload.id).then((data) => {
const newPost = toTextWithReaction([data.data], this.host);
const targetPost = newPost[0]
const targetPost = newPost[0];
for (const stream of this.currentSubscribe) {
this.wsConnection.send(
JSON.stringify({

View file

@ -31,7 +31,7 @@ import webServer from "./web/index.js";
import { initializeStreamingServer } from "./api/streaming.js";
import { koaBody } from "koa-body";
import removeTrailingSlash from "koa-remove-trailing-slashes";
import {v4 as uuid} from "uuid";
import { v4 as uuid } from "uuid";
export const serverLogger = new Logger("server", "gray", false);
@ -162,19 +162,19 @@ mastoRouter.get("/oauth/authorize", async (ctx) => {
const { client_id, state, redirect_uri } = ctx.request.query;
console.log(ctx.request.req);
let param = "mastodon=true";
if (state)
param += `&state=${state}`;
if (redirect_uri)
param += `&redirect_uri=${redirect_uri}`;
const client = client_id? client_id : "";
ctx.redirect(`${Buffer.from(client.toString(), 'base64').toString()}?${param}`);
if (state) param += `&state=${state}`;
if (redirect_uri) param += `&redirect_uri=${redirect_uri}`;
const client = client_id ? client_id : "";
ctx.redirect(
`${Buffer.from(client.toString(), "base64").toString()}?${param}`,
);
});
mastoRouter.post("/oauth/token", async (ctx) => {
const body: any = ctx.request.body || ctx.request.query;
console.log('token-request', body);
console.log('token-query', ctx.request.query);
if (body.grant_type === 'client_credentials') {
console.log("token-request", body);
console.log("token-query", ctx.request.query);
if (body.grant_type === "client_credentials") {
const ret = {
access_token: uuid(),
token_type: "Bearer",
@ -197,8 +197,8 @@ mastoRouter.post("/oauth/token", async (ctx) => {
// return;
//}
//token = `${m[1]}-${m[2]}-${m[3]}-${m[4]}-${m[5]}`
console.log(body.code, token)
token = body.code
console.log(body.code, token);
token = body.code;
}
if (client_id instanceof Array) {
client_id = client_id.toString();
@ -214,10 +214,10 @@ mastoRouter.post("/oauth/token", async (ctx) => {
const ret = {
access_token: atData.accessToken,
token_type: "Bearer",
scope: body.scope || 'read write follow push',
scope: body.scope || "read write follow push",
created_at: Math.floor(new Date().getTime() / 1000),
};
console.log('token-response', ret)
console.log("token-response", ret);
ctx.body = ret;
} catch (err: any) {
console.error(err);

View file

@ -136,7 +136,9 @@ export const routes = [
{
path: "/custom-katex-macro",
name: "custom-katex-macro",
component: page(() => import("./pages/settings/custom-katex-macro.vue")),
component: page(
() => import("./pages/settings/custom-katex-macro.vue"),
),
},
{
path: "/account-info",
@ -243,7 +245,9 @@ export const routes = [
{
path: "/custom-katex-macro",
name: "general",
component: page(() => import("./pages/settings/custom-katex-macro.vue")),
component: page(
() => import("./pages/settings/custom-katex-macro.vue"),
),
},
{
path: "/accounts",

View file

@ -262,7 +262,9 @@ export function getUserMenu(user, router: Router = mainRouter) {
menu = menu.concat([
null,
{
icon: user.isMuted ? "ph-eye ph-bold ph-lg" : "ph-eye-slash ph-bold ph-lg",
icon: user.isMuted
? "ph-eye ph-bold ph-lg"
: "ph-eye-slash ph-bold ph-lg",
text: user.isMuted ? i18n.ts.unmute : i18n.ts.mute,
hidden: user.isBlocking === true,
action: toggleMute,

View file

@ -1,19 +1,17 @@
type KaTeXMacro = {
args: number;
rule: (string | number)[];
args: number;
rule: (string | number)[];
};
function parseSingleKaTeXMacro(src: string): [string, KaTeXMacro] {
const invalid: [string, KaTeXMacro] = ["", { args: 0, rule: [] }];
const skipSpaces = (pos: number): number => {
while (src[pos] === " ")
++pos;
while (src[pos] === " ") ++pos;
return pos;
};
if (!src.startsWith("\\newcommand") || src.slice(-1) !== "}")
return invalid;
if (!src.startsWith("\\newcommand") || src.slice(-1) !== "}") return invalid;
// current index we are checking (= "\\newcommand".length)
let currentPos: number = 11;
@ -21,28 +19,21 @@ function parseSingleKaTeXMacro(src: string): [string, KaTeXMacro] {
// parse {\name}, (\name), or [\name]
let bracket: string;
if (src[currentPos] === "{")
bracket = "{}";
else if (src[currentPos] === "(")
bracket = "()";
else if (src[currentPos] === "[")
bracket = "[]";
else
return invalid;
if (src[currentPos] === "{") bracket = "{}";
else if (src[currentPos] === "(") bracket = "()";
else if (src[currentPos] === "[") bracket = "[]";
else return invalid;
++currentPos;
currentPos = skipSpaces(currentPos);
if (src[currentPos] !== "\\")
return invalid;
if (src[currentPos] !== "\\") return invalid;
const closeNameBracketPos: number = src.indexOf(bracket[1], currentPos);
if (closeNameBracketPos === -1)
return invalid;
if (closeNameBracketPos === -1) return invalid;
const name: string = src.slice(currentPos + 1, closeNameBracketPos).trim();
if (!/^[a-zA-Z]+$/.test(name))
return invalid;
if (!/^[a-zA-Z]+$/.test(name)) return invalid;
currentPos = skipSpaces(closeNameBracketPos + 1);
@ -54,8 +45,7 @@ function parseSingleKaTeXMacro(src: string): [string, KaTeXMacro] {
macro.args = Number(src.slice(currentPos + 1, closeArgsBracketPos).trim());
currentPos = closeArgsBracketPos + 1;
if (Number.isNaN(macro.args) || macro.args < 0)
return invalid;
if (Number.isNaN(macro.args) || macro.args < 0) return invalid;
} else if (src[currentPos] === "{") {
macro.args = 0;
} else {
@ -65,8 +55,7 @@ function parseSingleKaTeXMacro(src: string): [string, KaTeXMacro] {
currentPos = skipSpaces(currentPos);
// parse {rule}
if (src[currentPos] !== "{")
return invalid;
if (src[currentPos] !== "{") return invalid;
++currentPos;
currentPos = skipSpaces(currentPos);
@ -94,8 +83,11 @@ function parseSingleKaTeXMacro(src: string): [string, KaTeXMacro] {
break;
}
const argIndexEndPos = src.slice(numbersignPos + 1).search(/[^\d]/) + numbersignPos;
const argIndex: number = Number(src.slice(numbersignPos + 1, argIndexEndPos + 1));
const argIndexEndPos =
src.slice(numbersignPos + 1).search(/[^\d]/) + numbersignPos;
const argIndex: number = Number(
src.slice(numbersignPos + 1, argIndexEndPos + 1),
);
if (Number.isNaN(argIndex) || argIndex < 1 || macro.args < argIndex)
return invalid;
@ -107,10 +99,8 @@ function parseSingleKaTeXMacro(src: string): [string, KaTeXMacro] {
currentPos = argIndexEndPos + 1;
}
if (macro.args === 0)
return [name, macro];
else
return [name + bracket[0], macro];
if (macro.args === 0) return [name, macro];
else return [name + bracket[0], macro];
}
export function parseKaTeXMacros(src: string): string {
@ -118,8 +108,7 @@ export function parseKaTeXMacros(src: string): string {
for (const s of src.split("\n")) {
const [name, macro]: [string, KaTeXMacro] = parseSingleKaTeXMacro(s.trim());
if (name !== "")
result[name] = macro;
if (name !== "") result[name] = macro;
}
return JSON.stringify(result);
@ -127,11 +116,22 @@ export function parseKaTeXMacros(src: string): string {
// returns [expanded text, whether something is expanded, how many times we can expand more]
// the boolean value is used for multi-pass expansions (macros can expand to other macros)
function expandKaTeXMacroOnce(src: string, macros: { [name: string]: KaTeXMacro }, maxNumberOfExpansions: number)
: [string, boolean, number] {
function expandKaTeXMacroOnce(
src: string,
macros: { [name: string]: KaTeXMacro },
maxNumberOfExpansions: number,
): [string, boolean, number] {
const bracketKinds = 3;
const openBracketId: { [bracket: string]: number } = {"(": 0, "{": 1, "[": 2};
const closeBracketId: { [bracket: string]: number } = {")": 0, "}": 1, "]": 2};
const openBracketId: { [bracket: string]: number } = {
"(": 0,
"{": 1,
"[": 2,
};
const closeBracketId: { [bracket: string]: number } = {
")": 0,
"}": 1,
"]": 2,
};
const openBracketFromId = ["(", "{", "["];
const closeBracketFromId = [")", "}", "]"];
@ -142,21 +142,29 @@ function expandKaTeXMacroOnce(src: string, macros: { [name: string]: KaTeXMacro
let result: BracketMapping = {};
const n = src.length;
let depths = new Array<number>(bracketKinds).fill(0); // current bracket depth for "()", "{}", and "[]"
let buffer = Array.from(Array<number[]>(bracketKinds), () => Array<number>(n));
let depths = new Array<number>(bracketKinds).fill(0); // current bracket depth for "()", "{}", and "[]"
let buffer = Array.from(Array<number[]>(bracketKinds), () =>
Array<number>(n),
);
let isEscaped = false;
for (let i = 0; i < n; ++i) {
if (!isEscaped && src[i] === "\\" && i + 1 < n && ["{", "}", "\\"].includes(src[i+1])) {
if (
!isEscaped &&
src[i] === "\\" &&
i + 1 < n &&
["{", "}", "\\"].includes(src[i + 1])
) {
isEscaped = true;
continue;
}
if (isEscaped
|| (src[i] !== "\\"
&& !openBracketFromId.includes(src[i])
&& !closeBracketFromId.includes(src[i])))
{
if (
isEscaped ||
(src[i] !== "\\" &&
!openBracketFromId.includes(src[i]) &&
!closeBracketFromId.includes(src[i]))
) {
isEscaped = false;
continue;
}
@ -178,27 +186,29 @@ function expandKaTeXMacroOnce(src: string, macros: { [name: string]: KaTeXMacro
return result;
})();
function expandSingleKaTeXMacro(expandedArgs: string[], macroName: string): string {
function expandSingleKaTeXMacro(
expandedArgs: string[],
macroName: string,
): string {
let result = "";
for (const block of macros[macroName].rule) {
if (typeof block === "string")
result += block;
else
result += expandedArgs[block - 1];
if (typeof block === "string") result += block;
else result += expandedArgs[block - 1];
}
return result;
}
// only expand src.slice(beginPos, endPos)
function expandKaTeXMacroImpl(beginPos: number, endPos: number): [string, boolean] {
if (endPos <= beginPos)
return ["", false];
function expandKaTeXMacroImpl(
beginPos: number,
endPos: number,
): [string, boolean] {
if (endPos <= beginPos) return ["", false];
const raw: string = src.slice(beginPos, endPos);
const fallback: string = raw; // returned for invalid inputs or too many expansions
const fallback: string = raw; // returned for invalid inputs or too many expansions
if (maxNumberOfExpansions <= 0)
return [fallback, false];
if (maxNumberOfExpansions <= 0) return [fallback, false];
--maxNumberOfExpansions;
// search for a custom macro
@ -218,14 +228,13 @@ function expandKaTeXMacroOnce(src: string, macros: { [name: string]: KaTeXMacro
checkedPos = src.indexOf("\\", checkedPos + 1);
// there is no macro to expand
if (checkedPos === -1)
return [raw, false];
if (checkedPos === -1) return [raw, false];
// is it a custom macro?
let nonAlphaPos = src.slice(checkedPos + 1).search(/[^A-Za-z]/) + checkedPos + 1;
let nonAlphaPos =
src.slice(checkedPos + 1).search(/[^A-Za-z]/) + checkedPos + 1;
if (nonAlphaPos === checkedPos)
nonAlphaPos = endPos;
if (nonAlphaPos === checkedPos) nonAlphaPos = endPos;
let macroNameCandidate = src.slice(checkedPos + 1, nonAlphaPos);
if (macros.hasOwnProperty(macroNameCandidate)) {
@ -239,19 +248,17 @@ function expandKaTeXMacroOnce(src: string, macros: { [name: string]: KaTeXMacro
let nextOpenBracketPos = endPos;
for (let i = 0; i < bracketKinds; ++i) {
const pos = src.indexOf(openBracketFromId[i], checkedPos + 1);
if (pos !== -1 && pos < nextOpenBracketPos)
nextOpenBracketPos = pos;
if (pos !== -1 && pos < nextOpenBracketPos) nextOpenBracketPos = pos;
}
if (nextOpenBracketPos === endPos)
continue; // there is no open bracket
if (nextOpenBracketPos === endPos) continue; // there is no open bracket
macroNameCandidate += src[nextOpenBracketPos];
if (macros.hasOwnProperty(macroNameCandidate)) {
macroBackslashPos = checkedPos;
macroArgBeginPos = nextOpenBracketPos;
macroArgEndPos = nextOpenBracketPos; // to search the first arg from here
macroArgEndPos = nextOpenBracketPos; // to search the first arg from here
macroName = macroNameCandidate;
break;
}
@ -265,31 +272,46 @@ function expandKaTeXMacroOnce(src: string, macros: { [name: string]: KaTeXMacro
for (let i = 0; i < numArgs; ++i) {
// find the first open bracket after what we've searched
const nextOpenBracketPos = src.indexOf(openBracket, macroArgEndPos);
if (nextOpenBracketPos === -1)
return [fallback, false]; // not enough arguments are provided
if (!bracketMapping[nextOpenBracketPos])
return [fallback, false]; // found open bracket doesn't correspond to any close bracket
if (nextOpenBracketPos === -1) return [fallback, false]; // not enough arguments are provided
if (!bracketMapping[nextOpenBracketPos]) return [fallback, false]; // found open bracket doesn't correspond to any close bracket
macroArgEndPos = bracketMapping[nextOpenBracketPos];
expandedArgs[i] = expandKaTeXMacroImpl(nextOpenBracketPos + 1, macroArgEndPos)[0];
expandedArgs[i] = expandKaTeXMacroImpl(
nextOpenBracketPos + 1,
macroArgEndPos,
)[0];
}
return [src.slice(beginPos, macroBackslashPos)
+ expandSingleKaTeXMacro(expandedArgs, macroName)
+ expandKaTeXMacroImpl(macroArgEndPos + 1, endPos)[0], true];
return [
src.slice(beginPos, macroBackslashPos) +
expandSingleKaTeXMacro(expandedArgs, macroName) +
expandKaTeXMacroImpl(macroArgEndPos + 1, endPos)[0],
true,
];
}
const [expandedText, expandedFlag]: [string, boolean] = expandKaTeXMacroImpl(0, src.length);
const [expandedText, expandedFlag]: [string, boolean] = expandKaTeXMacroImpl(
0,
src.length,
);
return [expandedText, expandedFlag, maxNumberOfExpansions];
}
export function expandKaTeXMacro(src: string, macrosAsJSONString: string, maxNumberOfExpansions: number): string {
export function expandKaTeXMacro(
src: string,
macrosAsJSONString: string,
maxNumberOfExpansions: number,
): string {
const macros = JSON.parse(macrosAsJSONString);
let expandMore = true;
while (expandMore && (0 < maxNumberOfExpansions))
[src, expandMore, maxNumberOfExpansions] = expandKaTeXMacroOnce(src, macros, maxNumberOfExpansions);
while (expandMore && 0 < maxNumberOfExpansions)
[src, expandMore, maxNumberOfExpansions] = expandKaTeXMacroOnce(
src,
macros,
maxNumberOfExpansions,
);
return src;
}

View file

@ -4,15 +4,19 @@ import { expandKaTeXMacro } from "@/scripts/katex-macro";
export function preprocess(text: string): string {
if (defaultStore.state.enableCustomKaTeXMacro) {
const parsedKaTeXMacro = localStorage.getItem("customKaTeXMacroParsed") ?? "{}";
const maxNumberOfExpansions = 200; // to prevent infinite expansion loops
const parsedKaTeXMacro =
localStorage.getItem("customKaTeXMacroParsed") ?? "{}";
const maxNumberOfExpansions = 200; // to prevent infinite expansion loops
let nodes = mfm.parse(text);
for (let node of nodes) {
if (node["type"] === "mathInline" || node["type"] === "mathBlock") {
node["props"]["formula"]
= expandKaTeXMacro(node["props"]["formula"], parsedKaTeXMacro, maxNumberOfExpansions);
node["props"]["formula"] = expandKaTeXMacro(
node["props"]["formula"],
parsedKaTeXMacro,
maxNumberOfExpansions,
);
}
}