formatting
This commit is contained in:
parent
12769bd1ab
commit
acfc88389a
|
@ -1 +1 @@
|
|||
declare module 'koa-remove-trailing-slashes';
|
||||
declare module "koa-remove-trailing-slashes";
|
||||
|
|
|
@ -7,8 +7,7 @@ const logger = dbLogger.createSubLogger("sonic", "gray", false);
|
|||
|
||||
logger.info("Connecting to Sonic");
|
||||
|
||||
const handlers = (type: string): SonicChannel.Handlers => (
|
||||
{
|
||||
const handlers = (type: string): SonicChannel.Handlers => ({
|
||||
connected: () => {
|
||||
logger.succ(`Connected to Sonic ${type}`);
|
||||
},
|
||||
|
@ -24,15 +23,10 @@ const handlers = (type: string): SonicChannel.Handlers => (
|
|||
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,8 +36,12 @@ 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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -440,14 +440,10 @@ export function createCleanRemoteFilesJob() {
|
|||
}
|
||||
|
||||
export function createIndexAllNotesJob(data = {}) {
|
||||
return backgroundQueue.add(
|
||||
"indexAllNotes",
|
||||
data,
|
||||
{
|
||||
return backgroundQueue.add("indexAllNotes", data, {
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function webhookDeliver(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -88,7 +88,7 @@ export async function importPosts(
|
|||
continue;
|
||||
}
|
||||
if (job.data.signatureCheck) {
|
||||
if(!post.signature) {
|
||||
if (!post.signature) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
followersCount !== undefined
|
||||
? followersCount
|
||||
: person.followers && typeof person.followers !== "string" && isCollectionOrOrderedCollection(person.followers)
|
||||
: person.followers &&
|
||||
typeof person.followers !== "string" &&
|
||||
isCollectionOrOrderedCollection(person.followers)
|
||||
? person.followers.totalItems
|
||||
: undefined,
|
||||
followingCount: followingCount !== undefined
|
||||
followingCount:
|
||||
followingCount !== undefined
|
||||
? followingCount
|
||||
: person.following && typeof person.following !== "string" && isCollectionOrOrderedCollection(person.following)
|
||||
: 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:
|
||||
followersCount !== undefined
|
||||
? followersCount
|
||||
: person.followers && typeof person.followers !== "string" && isCollectionOrOrderedCollection(person.followers)
|
||||
: person.followers &&
|
||||
typeof person.followers !== "string" &&
|
||||
isCollectionOrOrderedCollection(person.followers)
|
||||
? person.followers.totalItems
|
||||
: undefined,
|
||||
followingCount: followingCount !== undefined
|
||||
followingCount:
|
||||
followingCount !== undefined
|
||||
? followingCount
|
||||
: person.following && typeof person.following !== "string" && isCollectionOrOrderedCollection(person.following)
|
||||
: person.following &&
|
||||
typeof person.following !== "string" &&
|
||||
isCollectionOrOrderedCollection(person.following)
|
||||
? person.following.totalItems
|
||||
: undefined,
|
||||
featured: person.featured,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -11,7 +11,8 @@ export const meta = {
|
|||
|
||||
res: {
|
||||
type: "object",
|
||||
optional: false, nullable: false,
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "Emoji",
|
||||
},
|
||||
} as const;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,7 +24,7 @@ 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 };
|
||||
|
@ -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;
|
||||
|
|
|
@ -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,9 +91,7 @@ export function apiAccountMastodon(router: Router): void {
|
|||
ctx.body = e.response.data;
|
||||
}
|
||||
});
|
||||
router.get<{ Params: { id: string } }>(
|
||||
"/v1/accounts/:id",
|
||||
async (ctx) => {
|
||||
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);
|
||||
|
@ -106,8 +107,7 @@ export function apiAccountMastodon(router: Router): void {
|
|||
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;
|
||||
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -2,8 +2,8 @@ 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) {
|
||||
|
@ -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,9 +61,7 @@ export function apiStatusMastodon(router: Router): void {
|
|||
ctx.body = e.response.data;
|
||||
}
|
||||
});
|
||||
router.get<{ Params: { id: string } }>(
|
||||
"/v1/statuses/:id",
|
||||
async (ctx) => {
|
||||
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);
|
||||
|
@ -71,11 +73,8 @@ export function apiStatusMastodon(router: Router): void {
|
|||
ctx.status = 401;
|
||||
ctx.body = e.response.data;
|
||||
}
|
||||
},
|
||||
);
|
||||
router.delete<{ Params: { id: string } }>(
|
||||
"/v1/statuses/:id",
|
||||
async (ctx) => {
|
||||
});
|
||||
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);
|
||||
|
@ -87,8 +86,7 @@ export function apiStatusMastodon(router: Router): void {
|
|||
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,9 +297,7 @@ export function apiStatusMastodon(router: Router): void {
|
|||
}
|
||||
},
|
||||
);
|
||||
router.get<{ Params: { id: string } }>(
|
||||
"/v1/media/:id",
|
||||
async (ctx) => {
|
||||
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);
|
||||
|
@ -310,11 +309,8 @@ export function apiStatusMastodon(router: Router): void {
|
|||
ctx.status = 401;
|
||||
ctx.body = e.response.data;
|
||||
}
|
||||
},
|
||||
);
|
||||
router.put<{ Params: { id: string } }>(
|
||||
"/v1/media/:id",
|
||||
async (ctx) => {
|
||||
});
|
||||
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);
|
||||
|
@ -329,11 +325,8 @@ export function apiStatusMastodon(router: Router): void {
|
|||
ctx.status = 401;
|
||||
ctx.body = e.response.data;
|
||||
}
|
||||
},
|
||||
);
|
||||
router.get<{ Params: { id: string } }>(
|
||||
"/v1/polls/:id",
|
||||
async (ctx) => {
|
||||
});
|
||||
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);
|
||||
|
@ -345,8 +338,7 @@ export function apiStatusMastodon(router: Router): void {
|
|||
ctx.status = 401;
|
||||
ctx.body = e.response.data;
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
router.post<{ Params: { id: string } }>(
|
||||
"/v1/polls/:id/votes",
|
||||
async (ctx) => {
|
||||
|
|
|
@ -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
|
||||
if (!r.url) continue;
|
||||
emoji.push({
|
||||
'shortcode': r.name,
|
||||
'url': r.url,
|
||||
'static_url': r.url,
|
||||
'visible_in_picker': true,
|
||||
category: ""
|
||||
},)
|
||||
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,9 +127,7 @@ export function apiTimelineMastodon(router: Router): void {
|
|||
}
|
||||
},
|
||||
);
|
||||
router.get(
|
||||
"/v1/timelines/home",
|
||||
async (ctx, reply) => {
|
||||
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);
|
||||
|
@ -141,8 +140,7 @@ export function apiTimelineMastodon(router: Router): void {
|
|||
ctx.status = 401;
|
||||
ctx.body = e.response.data;
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
router.get<{ Params: { listId: string } }>(
|
||||
"/v1/timelines/list/:listId",
|
||||
async (ctx, reply) => {
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -7,13 +7,11 @@ 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 = [")", "}", "]"];
|
||||
|
||||
|
@ -143,20 +143,28 @@ function expandKaTeXMacroOnce(src: string, macros: { [name: string]: KaTeXMacro
|
|||
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 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
|
||||
|
||||
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,12 +248,10 @@ 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];
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue