mirror of
https://iceshrimp.dev/limepotato/jormungandr-bite.git
synced 2024-11-26 11:57:34 -07:00
formatting
This commit is contained in:
parent
5be627b869
commit
19c4a59513
27 changed files with 463 additions and 366 deletions
|
@ -1 +1 @@
|
||||||
declare module 'koa-remove-trailing-slashes';
|
declare module "koa-remove-trailing-slashes";
|
||||||
|
|
|
@ -7,32 +7,26 @@ const logger = dbLogger.createSubLogger("sonic", "gray", false);
|
||||||
|
|
||||||
logger.info("Connecting to Sonic");
|
logger.info("Connecting to Sonic");
|
||||||
|
|
||||||
const handlers = (type: string): SonicChannel.Handlers => (
|
const handlers = (type: string): SonicChannel.Handlers => ({
|
||||||
{
|
connected: () => {
|
||||||
connected: () => {
|
logger.succ(`Connected to Sonic ${type}`);
|
||||||
logger.succ(`Connected to Sonic ${type}`);
|
},
|
||||||
},
|
disconnected: (error) => {
|
||||||
disconnected: (error) => {
|
logger.warn(`Disconnected from Sonic ${type}, error: ${error}`);
|
||||||
logger.warn(`Disconnected from Sonic ${type}, error: ${error}`);
|
},
|
||||||
},
|
error: (error) => {
|
||||||
error: (error) => {
|
logger.warn(`Sonic ${type} error: ${error}`);
|
||||||
logger.warn(`Sonic ${type} error: ${error}`);
|
},
|
||||||
},
|
retrying: () => {
|
||||||
retrying: () => {
|
logger.info(`Sonic ${type} retrying`);
|
||||||
logger.info(`Sonic ${type} retrying`);
|
},
|
||||||
},
|
timeout: () => {
|
||||||
timeout: () => {
|
logger.warn(`Sonic ${type} timeout`);
|
||||||
logger.warn(`Sonic ${type} timeout`);
|
},
|
||||||
},
|
});
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const hasConfig =
|
const hasConfig =
|
||||||
config.sonic
|
config.sonic && (config.sonic.host || config.sonic.port || config.sonic.auth);
|
||||||
&& ( config.sonic.host
|
|
||||||
|| config.sonic.port
|
|
||||||
|| config.sonic.auth
|
|
||||||
)
|
|
||||||
|
|
||||||
const host = hasConfig ? config.sonic.host ?? "localhost" : "";
|
const host = hasConfig ? config.sonic.host ?? "localhost" : "";
|
||||||
const port = hasConfig ? config.sonic.port ?? 1491 : 0;
|
const port = hasConfig ? config.sonic.port ?? 1491 : 0;
|
||||||
|
@ -42,10 +36,14 @@ const bucket = hasConfig ? config.sonic.bucket ?? "default" : "";
|
||||||
|
|
||||||
export default hasConfig
|
export default hasConfig
|
||||||
? {
|
? {
|
||||||
search: new SonicChannel.Search({host, port, auth}).connect(handlers("search")),
|
search: new SonicChannel.Search({ host, port, auth }).connect(
|
||||||
ingest: new SonicChannel.Ingest({host, port, auth}).connect(handlers("ingest")),
|
handlers("search"),
|
||||||
|
),
|
||||||
|
ingest: new SonicChannel.Ingest({ host, port, auth }).connect(
|
||||||
|
handlers("ingest"),
|
||||||
|
),
|
||||||
|
|
||||||
collection,
|
collection,
|
||||||
bucket,
|
bucket,
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
|
|
|
@ -6,7 +6,12 @@ export type Post = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function parse(acct: any): 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 {
|
export function toJson(acct: Post): string {
|
||||||
|
|
|
@ -440,14 +440,10 @@ export function createCleanRemoteFilesJob() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createIndexAllNotesJob(data = {}) {
|
export function createIndexAllNotesJob(data = {}) {
|
||||||
return backgroundQueue.add(
|
return backgroundQueue.add("indexAllNotes", data, {
|
||||||
"indexAllNotes",
|
removeOnComplete: true,
|
||||||
data,
|
removeOnFail: true,
|
||||||
{
|
});
|
||||||
removeOnComplete: true,
|
|
||||||
removeOnFail: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function webhookDeliver(
|
export function webhookDeliver(
|
||||||
|
|
|
@ -3,26 +3,30 @@ import type Bull from "bull";
|
||||||
import { queueLogger } from "../../logger.js";
|
import { queueLogger } from "../../logger.js";
|
||||||
import { Notes } from "@/models/index.js";
|
import { Notes } from "@/models/index.js";
|
||||||
import { MoreThan } from "typeorm";
|
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";
|
import { Note } from "@/models/entities/note.js";
|
||||||
|
|
||||||
const logger = queueLogger.createSubLogger("index-all-notes");
|
const logger = queueLogger.createSubLogger("index-all-notes");
|
||||||
|
|
||||||
export default async function indexAllNotes(
|
export default async function indexAllNotes(
|
||||||
job: Bull.Job<Record<string, unknown>>,
|
job: Bull.Job<Record<string, unknown>>,
|
||||||
done: ()=>void,
|
done: () => void,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
logger.info("Indexing all notes...");
|
logger.info("Indexing all notes...");
|
||||||
|
|
||||||
let cursor: string|null = job.data.cursor as string ?? null;
|
let cursor: string | null = (job.data.cursor as string) ?? null;
|
||||||
let indexedCount: number = job.data.indexedCount as number ?? 0;
|
let indexedCount: number = (job.data.indexedCount as number) ?? 0;
|
||||||
let total: number = job.data.total as number ?? 0;
|
let total: number = (job.data.total as number) ?? 0;
|
||||||
|
|
||||||
let running = true;
|
let running = true;
|
||||||
const take = 50000;
|
const take = 50000;
|
||||||
const batch = 100;
|
const batch = 100;
|
||||||
while (running) {
|
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[] = [];
|
let notes: Note[] = [];
|
||||||
try {
|
try {
|
||||||
|
@ -49,22 +53,21 @@ export default async function indexAllNotes(
|
||||||
try {
|
try {
|
||||||
const count = await Notes.count();
|
const count = await Notes.count();
|
||||||
total = count;
|
total = count;
|
||||||
job.update({ indexedCount, cursor, total })
|
job.update({ indexedCount, cursor, total });
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < notes.length; i += batch) {
|
for (let i = 0; i < notes.length; i += batch) {
|
||||||
const chunk = notes.slice(i, 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;
|
indexedCount += chunk.length;
|
||||||
const pct = (indexedCount / total)*100;
|
const pct = (indexedCount / total) * 100;
|
||||||
job.update({ indexedCount, cursor, total })
|
job.update({ indexedCount, cursor, total });
|
||||||
job.progress(+(pct.toFixed(1)));
|
job.progress(+pct.toFixed(1));
|
||||||
logger.info(`Indexed notes ${indexedCount}/${total ? total : '?'}`);
|
logger.info(`Indexed notes ${indexedCount}/${total ? total : "?"}`);
|
||||||
}
|
}
|
||||||
cursor = notes[notes.length - 1].id;
|
cursor = notes[notes.length - 1].id;
|
||||||
job.update({ indexedCount, cursor, total })
|
job.update({ indexedCount, cursor, total });
|
||||||
|
|
||||||
if (notes.length < take) {
|
if (notes.length < take) {
|
||||||
running = false;
|
running = false;
|
||||||
|
|
|
@ -3,10 +3,7 @@ import indexAllNotes from "./index-all-notes.js";
|
||||||
|
|
||||||
const jobs = {
|
const jobs = {
|
||||||
indexAllNotes,
|
indexAllNotes,
|
||||||
} as Record<
|
} as Record<string, Bull.ProcessCallbackFunction<Record<string, unknown>>>;
|
||||||
string,
|
|
||||||
Bull.ProcessCallbackFunction<Record<string, unknown>>
|
|
||||||
>;
|
|
||||||
|
|
||||||
export default function (q: Bull.Queue) {
|
export default function (q: Bull.Queue) {
|
||||||
for (const [k, v] of Object.entries(jobs)) {
|
for (const [k, v] of Object.entries(jobs)) {
|
||||||
|
|
|
@ -88,7 +88,7 @@ export async function importPosts(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (job.data.signatureCheck) {
|
if (job.data.signatureCheck) {
|
||||||
if(!post.signature) {
|
if (!post.signature) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ export default async (job: Bull.Job<WebhookDeliverJobData>) => {
|
||||||
"X-Calckey-Host": config.host,
|
"X-Calckey-Host": config.host,
|
||||||
"X-Calckey-Hook-Id": job.data.webhookId,
|
"X-Calckey-Hook-Id": job.data.webhookId,
|
||||||
"X-Calckey-Hook-Secret": job.data.secret,
|
"X-Calckey-Hook-Secret": job.data.secret,
|
||||||
'Content-Type': 'application/json'
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
hookId: job.data.webhookId,
|
hookId: job.data.webhookId,
|
||||||
|
|
|
@ -205,7 +205,9 @@ export async function createPerson(
|
||||||
|
|
||||||
if (typeof person.followers === "string") {
|
if (typeof person.followers === "string") {
|
||||||
try {
|
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());
|
let json_data = JSON.parse(await data.text());
|
||||||
|
|
||||||
followersCount = json_data.totalItems;
|
followersCount = json_data.totalItems;
|
||||||
|
@ -218,7 +220,9 @@ export async function createPerson(
|
||||||
|
|
||||||
if (typeof person.following === "string") {
|
if (typeof person.following === "string") {
|
||||||
try {
|
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());
|
let json_data = JSON.parse(await data.text());
|
||||||
|
|
||||||
followingCount = json_data.totalItems;
|
followingCount = json_data.totalItems;
|
||||||
|
@ -227,7 +231,6 @@ export async function createPerson(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Create user
|
// Create user
|
||||||
let user: IRemoteUser;
|
let user: IRemoteUser;
|
||||||
try {
|
try {
|
||||||
|
@ -255,14 +258,20 @@ export async function createPerson(
|
||||||
followersUri: person.followers
|
followersUri: person.followers
|
||||||
? getApId(person.followers)
|
? getApId(person.followers)
|
||||||
: undefined,
|
: undefined,
|
||||||
followersCount: followersCount !== undefined
|
followersCount:
|
||||||
? followersCount
|
followersCount !== undefined
|
||||||
: person.followers && typeof person.followers !== "string" && isCollectionOrOrderedCollection(person.followers)
|
? followersCount
|
||||||
|
: person.followers &&
|
||||||
|
typeof person.followers !== "string" &&
|
||||||
|
isCollectionOrOrderedCollection(person.followers)
|
||||||
? person.followers.totalItems
|
? person.followers.totalItems
|
||||||
: undefined,
|
: undefined,
|
||||||
followingCount: followingCount !== undefined
|
followingCount:
|
||||||
? followingCount
|
followingCount !== undefined
|
||||||
: person.following && typeof person.following !== "string" && isCollectionOrOrderedCollection(person.following)
|
? followingCount
|
||||||
|
: person.following &&
|
||||||
|
typeof person.following !== "string" &&
|
||||||
|
isCollectionOrOrderedCollection(person.following)
|
||||||
? person.following.totalItems
|
? person.following.totalItems
|
||||||
: undefined,
|
: undefined,
|
||||||
featured: person.featured ? getApId(person.featured) : undefined,
|
featured: person.featured ? getApId(person.featured) : undefined,
|
||||||
|
@ -440,7 +449,9 @@ export async function updatePerson(
|
||||||
|
|
||||||
if (typeof person.followers === "string") {
|
if (typeof person.followers === "string") {
|
||||||
try {
|
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());
|
let json_data = JSON.parse(await data.text());
|
||||||
|
|
||||||
followersCount = json_data.totalItems;
|
followersCount = json_data.totalItems;
|
||||||
|
@ -449,12 +460,13 @@ export async function updatePerson(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let followingCount: number | undefined;
|
let followingCount: number | undefined;
|
||||||
|
|
||||||
if (typeof person.following === "string") {
|
if (typeof person.following === "string") {
|
||||||
try {
|
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());
|
let json_data = JSON.parse(await data.text());
|
||||||
|
|
||||||
followingCount = json_data.totalItems;
|
followingCount = json_data.totalItems;
|
||||||
|
@ -470,14 +482,20 @@ export async function updatePerson(
|
||||||
person.sharedInbox ||
|
person.sharedInbox ||
|
||||||
(person.endpoints ? person.endpoints.sharedInbox : undefined),
|
(person.endpoints ? person.endpoints.sharedInbox : undefined),
|
||||||
followersUri: person.followers ? getApId(person.followers) : undefined,
|
followersUri: person.followers ? getApId(person.followers) : undefined,
|
||||||
followersCount: followersCount !== undefined
|
followersCount:
|
||||||
? followersCount
|
followersCount !== undefined
|
||||||
: person.followers && typeof person.followers !== "string" && isCollectionOrOrderedCollection(person.followers)
|
? followersCount
|
||||||
|
: person.followers &&
|
||||||
|
typeof person.followers !== "string" &&
|
||||||
|
isCollectionOrOrderedCollection(person.followers)
|
||||||
? person.followers.totalItems
|
? person.followers.totalItems
|
||||||
: undefined,
|
: undefined,
|
||||||
followingCount: followingCount !== undefined
|
followingCount:
|
||||||
? followingCount
|
followingCount !== undefined
|
||||||
: person.following && typeof person.following !== "string" && isCollectionOrOrderedCollection(person.following)
|
? followingCount
|
||||||
|
: person.following &&
|
||||||
|
typeof person.following !== "string" &&
|
||||||
|
isCollectionOrOrderedCollection(person.following)
|
||||||
? person.following.totalItems
|
? person.following.totalItems
|
||||||
: undefined,
|
: undefined,
|
||||||
featured: person.featured,
|
featured: person.featured,
|
||||||
|
|
|
@ -54,7 +54,11 @@ export const paramDef = {
|
||||||
folderId: { type: "string", format: "misskey:id", nullable: true },
|
folderId: { type: "string", format: "misskey:id", nullable: true },
|
||||||
name: { type: "string" },
|
name: { type: "string" },
|
||||||
isSensitive: { type: "boolean" },
|
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"],
|
required: ["fileId"],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
@ -11,7 +11,8 @@ export const meta = {
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: "object",
|
type: "object",
|
||||||
optional: false, nullable: false,
|
optional: false,
|
||||||
|
nullable: false,
|
||||||
ref: "Emoji",
|
ref: "Emoji",
|
||||||
},
|
},
|
||||||
} as const;
|
} 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);
|
throw new ApiError(meta.errors.contentRequired);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -139,7 +139,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
})
|
})
|
||||||
.map((key) => key.id);
|
.map((key) => key.id);
|
||||||
|
|
||||||
ids.push(...res);
|
ids.push(...res);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort all the results by note id DESC (newest first)
|
// 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
|
// 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;
|
start += chunkSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,10 @@ import Router from "@koa/router";
|
||||||
import multer from "@koa/multer";
|
import multer from "@koa/multer";
|
||||||
import bodyParser from "koa-bodyparser";
|
import bodyParser from "koa-bodyparser";
|
||||||
import cors from "@koa/cors";
|
import cors from "@koa/cors";
|
||||||
import { apiMastodonCompatible, getClient } from "./mastodon/ApiMastodonCompatibleService.js";
|
import {
|
||||||
|
apiMastodonCompatible,
|
||||||
|
getClient,
|
||||||
|
} from "./mastodon/ApiMastodonCompatibleService.js";
|
||||||
import { Instances, AccessTokens, Users } from "@/models/index.js";
|
import { Instances, AccessTokens, Users } from "@/models/index.js";
|
||||||
import config from "@/config/index.js";
|
import config from "@/config/index.js";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
@ -21,10 +24,10 @@ import discord from "./service/discord.js";
|
||||||
import github from "./service/github.js";
|
import github from "./service/github.js";
|
||||||
import twitter from "./service/twitter.js";
|
import twitter from "./service/twitter.js";
|
||||||
import { koaBody } from "koa-body";
|
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)
|
// re-export native rust id conversion (function and enum)
|
||||||
export { IdType, convertId };
|
export { IdType, convertId };
|
||||||
|
|
||||||
// Init app
|
// Init app
|
||||||
const app = new Koa();
|
const app = new Koa();
|
||||||
|
@ -74,7 +77,6 @@ mastoRouter.use(
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
mastoFileRouter.post("/v1/media", upload.single("file"), async (ctx) => {
|
mastoFileRouter.post("/v1/media", upload.single("file"), async (ctx) => {
|
||||||
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
||||||
const accessTokens = ctx.headers.authorization;
|
const accessTokens = ctx.headers.authorization;
|
||||||
|
|
|
@ -77,7 +77,10 @@ export function apiAccountMastodon(router: Router): void {
|
||||||
const accessTokens = ctx.headers.authorization;
|
const accessTokens = ctx.headers.authorization;
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const data = await client.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];
|
let resp = data.data.accounts[0];
|
||||||
resp.id = convertId(resp.id, IdType.MastodonId);
|
resp.id = convertId(resp.id, IdType.MastodonId);
|
||||||
ctx.body = resp;
|
ctx.body = resp;
|
||||||
|
@ -88,26 +91,23 @@ export function apiAccountMastodon(router: Router): void {
|
||||||
ctx.body = e.response.data;
|
ctx.body = e.response.data;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
router.get<{ Params: { id: string } }>(
|
router.get<{ Params: { id: string } }>("/v1/accounts/:id", async (ctx) => {
|
||||||
"/v1/accounts/:id",
|
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
||||||
async (ctx) => {
|
const accessTokens = ctx.headers.authorization;
|
||||||
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
const accessTokens = ctx.headers.authorization;
|
try {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const calcId = convertId(ctx.params.id, IdType.CalckeyId);
|
||||||
try {
|
const data = await client.getAccount(calcId);
|
||||||
const calcId = convertId(ctx.params.id, IdType.CalckeyId);
|
let resp = data.data;
|
||||||
const data = await client.getAccount(calcId);
|
resp.id = convertId(resp.id, IdType.MastodonId);
|
||||||
let resp = data.data;
|
ctx.body = resp;
|
||||||
resp.id = convertId(resp.id, IdType.MastodonId);
|
} catch (e: any) {
|
||||||
ctx.body = resp;
|
console.error(e);
|
||||||
} catch (e: any) {
|
console.error(e.response.data);
|
||||||
console.error(e);
|
ctx.status = 401;
|
||||||
console.error(e.response.data);
|
ctx.body = e.response.data;
|
||||||
ctx.status = 401;
|
}
|
||||||
ctx.body = e.response.data;
|
});
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
router.get<{ Params: { id: string } }>(
|
router.get<{ Params: { id: string } }>(
|
||||||
"/v1/accounts/:id/statuses",
|
"/v1/accounts/:id/statuses",
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
|
@ -122,11 +122,19 @@ export function apiAccountMastodon(router: Router): void {
|
||||||
let resp = data.data;
|
let resp = data.data;
|
||||||
for (let statIdx = 0; statIdx < resp.length; statIdx++) {
|
for (let statIdx = 0; statIdx < resp.length; statIdx++) {
|
||||||
resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId);
|
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_account_id = resp[statIdx]
|
||||||
resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id ? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId) : null;
|
.in_reply_to_account_id
|
||||||
let mentions = resp[statIdx].mentions
|
? 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++) {
|
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;
|
ctx.body = resp;
|
||||||
|
@ -210,7 +218,9 @@ export function apiAccountMastodon(router: Router): void {
|
||||||
const accessTokens = ctx.headers.authorization;
|
const accessTokens = ctx.headers.authorization;
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const data = await client.followAccount(convertId(ctx.params.id, IdType.CalckeyId));
|
const data = await client.followAccount(
|
||||||
|
convertId(ctx.params.id, IdType.CalckeyId),
|
||||||
|
);
|
||||||
let acct = data.data;
|
let acct = data.data;
|
||||||
acct.following = true;
|
acct.following = true;
|
||||||
acct.id = convertId(acct.id, IdType.MastodonId);
|
acct.id = convertId(acct.id, IdType.MastodonId);
|
||||||
|
@ -230,7 +240,9 @@ export function apiAccountMastodon(router: Router): void {
|
||||||
const accessTokens = ctx.headers.authorization;
|
const accessTokens = ctx.headers.authorization;
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const data = await client.unfollowAccount(convertId(ctx.params.id, IdType.CalckeyId));
|
const data = await client.unfollowAccount(
|
||||||
|
convertId(ctx.params.id, IdType.CalckeyId),
|
||||||
|
);
|
||||||
let acct = data.data;
|
let acct = data.data;
|
||||||
acct.id = convertId(acct.id, IdType.MastodonId);
|
acct.id = convertId(acct.id, IdType.MastodonId);
|
||||||
acct.following = false;
|
acct.following = false;
|
||||||
|
@ -250,7 +262,9 @@ export function apiAccountMastodon(router: Router): void {
|
||||||
const accessTokens = ctx.headers.authorization;
|
const accessTokens = ctx.headers.authorization;
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const data = await client.blockAccount(convertId(ctx.params.id, IdType.CalckeyId));
|
const data = await client.blockAccount(
|
||||||
|
convertId(ctx.params.id, IdType.CalckeyId),
|
||||||
|
);
|
||||||
let resp = data.data;
|
let resp = data.data;
|
||||||
resp.id = convertId(resp.id, IdType.MastodonId);
|
resp.id = convertId(resp.id, IdType.MastodonId);
|
||||||
ctx.body = resp;
|
ctx.body = resp;
|
||||||
|
@ -269,7 +283,9 @@ export function apiAccountMastodon(router: Router): void {
|
||||||
const accessTokens = ctx.headers.authorization;
|
const accessTokens = ctx.headers.authorization;
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const data = await client.unblockAccount(convertId(ctx.params.id, IdType.MastodonId));
|
const data = await client.unblockAccount(
|
||||||
|
convertId(ctx.params.id, IdType.MastodonId),
|
||||||
|
);
|
||||||
let resp = data.data;
|
let resp = data.data;
|
||||||
resp.id = convertId(resp.id, IdType.MastodonId);
|
resp.id = convertId(resp.id, IdType.MastodonId);
|
||||||
ctx.body = resp;
|
ctx.body = resp;
|
||||||
|
@ -310,7 +326,9 @@ export function apiAccountMastodon(router: Router): void {
|
||||||
const accessTokens = ctx.headers.authorization;
|
const accessTokens = ctx.headers.authorization;
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const data = await client.unmuteAccount(convertId(ctx.params.id, IdType.CalckeyId));
|
const data = await client.unmuteAccount(
|
||||||
|
convertId(ctx.params.id, IdType.CalckeyId),
|
||||||
|
);
|
||||||
let resp = data.data;
|
let resp = data.data;
|
||||||
resp.id = convertId(resp.id, IdType.MastodonId);
|
resp.id = convertId(resp.id, IdType.MastodonId);
|
||||||
ctx.body = resp;
|
ctx.body = resp;
|
||||||
|
@ -365,15 +383,25 @@ export function apiAccountMastodon(router: Router): void {
|
||||||
const accessTokens = ctx.headers.authorization;
|
const accessTokens = ctx.headers.authorization;
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const data = (await client.getBookmarks(limitToInt(ctx.query as any))) as any;
|
const data = (await client.getBookmarks(
|
||||||
|
limitToInt(ctx.query as any),
|
||||||
|
)) as any;
|
||||||
let resp = data.data;
|
let resp = data.data;
|
||||||
for (let statIdx = 0; statIdx < resp.length; statIdx++) {
|
for (let statIdx = 0; statIdx < resp.length; statIdx++) {
|
||||||
resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId);
|
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_account_id = resp[statIdx]
|
||||||
resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id ? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId) : null;
|
.in_reply_to_account_id
|
||||||
let mentions = resp[statIdx].mentions
|
? 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++) {
|
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;
|
ctx.body = resp;
|
||||||
|
@ -393,11 +421,19 @@ export function apiAccountMastodon(router: Router): void {
|
||||||
let resp = data.data;
|
let resp = data.data;
|
||||||
for (let statIdx = 0; statIdx < resp.length; statIdx++) {
|
for (let statIdx = 0; statIdx < resp.length; statIdx++) {
|
||||||
resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId);
|
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_account_id = resp[statIdx]
|
||||||
resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id ? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId) : null;
|
.in_reply_to_account_id
|
||||||
let mentions = resp[statIdx].mentions
|
? 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++) {
|
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;
|
ctx.body = resp;
|
||||||
|
@ -471,7 +507,9 @@ export function apiAccountMastodon(router: Router): void {
|
||||||
const accessTokens = ctx.headers.authorization;
|
const accessTokens = ctx.headers.authorization;
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const data = await client.acceptFollowRequest(convertId(ctx.params.id, IdType.CalckeyId));
|
const data = await client.acceptFollowRequest(
|
||||||
|
convertId(ctx.params.id, IdType.CalckeyId),
|
||||||
|
);
|
||||||
let resp = data.data;
|
let resp = data.data;
|
||||||
resp.id = convertId(resp.id, IdType.MastodonId);
|
resp.id = convertId(resp.id, IdType.MastodonId);
|
||||||
ctx.body = resp;
|
ctx.body = resp;
|
||||||
|
@ -490,7 +528,9 @@ export function apiAccountMastodon(router: Router): void {
|
||||||
const accessTokens = ctx.headers.authorization;
|
const accessTokens = ctx.headers.authorization;
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const data = await client.rejectFollowRequest(convertId(ctx.params.id, IdType.CalckeyId));
|
const data = await client.rejectFollowRequest(
|
||||||
|
convertId(ctx.params.id, IdType.CalckeyId),
|
||||||
|
);
|
||||||
let resp = data.data;
|
let resp = data.data;
|
||||||
resp.id = convertId(resp.id, IdType.MastodonId);
|
resp.id = convertId(resp.id, IdType.MastodonId);
|
||||||
ctx.body = resp;
|
ctx.body = resp;
|
||||||
|
|
|
@ -44,7 +44,7 @@ const writeScope = [
|
||||||
export function apiAuthMastodon(router: Router): void {
|
export function apiAuthMastodon(router: Router): void {
|
||||||
router.post("/v1/apps", async (ctx) => {
|
router.post("/v1/apps", async (ctx) => {
|
||||||
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
|
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
|
||||||
const client = getClient(BASE_URL, '');
|
const client = getClient(BASE_URL, "");
|
||||||
const body: any = ctx.request.body || ctx.request.query;
|
const body: any = ctx.request.body || ctx.request.query;
|
||||||
try {
|
try {
|
||||||
let scope = body.scopes;
|
let scope = body.scopes;
|
||||||
|
@ -68,9 +68,9 @@ export function apiAuthMastodon(router: Router): void {
|
||||||
website: body.website,
|
website: body.website,
|
||||||
redirect_uri: red,
|
redirect_uri: red,
|
||||||
client_id: Buffer.from(appData.url || "").toString("base64"),
|
client_id: Buffer.from(appData.url || "").toString("base64"),
|
||||||
client_secret: appData.clientSecret
|
client_secret: appData.clientSecret,
|
||||||
};
|
};
|
||||||
console.log(returns)
|
console.log(returns);
|
||||||
ctx.body = returns;
|
ctx.body = returns;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
|
@ -11,17 +11,20 @@ export async function getInstance(response: Entity.Instance) {
|
||||||
return {
|
return {
|
||||||
uri: response.uri,
|
uri: response.uri,
|
||||||
title: response.title || "Calckey",
|
title: response.title || "Calckey",
|
||||||
short_description: response.description.substring(0, 50) || "See real server website",
|
short_description:
|
||||||
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 :)",
|
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 || "",
|
email: response.email || "",
|
||||||
version: "3.0.0 compatible (3.5+ Calckey)", //I hope this version string is correct, we will need to test it.
|
version: "3.0.0 compatible (3.5+ Calckey)", //I hope this version string is correct, we will need to test it.
|
||||||
urls: response.urls,
|
urls: response.urls,
|
||||||
stats: {
|
stats: {
|
||||||
user_count: (await totalUsers),
|
user_count: await totalUsers,
|
||||||
status_count: (await totalStatuses),
|
status_count: await totalStatuses,
|
||||||
domain_count: response.stats.domain_count
|
domain_count: response.stats.domain_count,
|
||||||
},
|
},
|
||||||
thumbnail: response.thumbnail || 'https://http.cat/404',
|
thumbnail: response.thumbnail || "https://http.cat/404",
|
||||||
languages: meta.langs,
|
languages: meta.langs,
|
||||||
registrations: !meta.disableRegistration || response.registrations,
|
registrations: !meta.disableRegistration || response.registrations,
|
||||||
approval_required: !response.registrations,
|
approval_required: !response.registrations,
|
||||||
|
|
|
@ -44,7 +44,7 @@ export function apiSearchMastodon(router: Router): void {
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
ctx.status = (401);
|
ctx.status = 401;
|
||||||
ctx.body = e.response.data;
|
ctx.body = e.response.data;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -52,11 +52,15 @@ export function apiSearchMastodon(router: Router): void {
|
||||||
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
|
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
|
||||||
const accessTokens = ctx.headers.authorization;
|
const accessTokens = ctx.headers.authorization;
|
||||||
try {
|
try {
|
||||||
const data = await getHighlight(BASE_URL, ctx.request.hostname, accessTokens);
|
const data = await getHighlight(
|
||||||
|
BASE_URL,
|
||||||
|
ctx.request.hostname,
|
||||||
|
accessTokens,
|
||||||
|
);
|
||||||
ctx.body = data;
|
ctx.body = data;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
ctx.status = (401);
|
ctx.status = 401;
|
||||||
ctx.body = e.response.data;
|
ctx.body = e.response.data;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -75,7 +79,7 @@ export function apiSearchMastodon(router: Router): void {
|
||||||
ctx.body = data;
|
ctx.body = data;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
ctx.status = (401);
|
ctx.status = 401;
|
||||||
ctx.body = e.response.data;
|
ctx.body = e.response.data;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,13 +2,13 @@ import Router from "@koa/router";
|
||||||
import { getClient } from "../ApiMastodonCompatibleService.js";
|
import { getClient } from "../ApiMastodonCompatibleService.js";
|
||||||
import { emojiRegexAtStartToEnd } from "@/misc/emoji-regex.js";
|
import { emojiRegexAtStartToEnd } from "@/misc/emoji-regex.js";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import querystring from 'node:querystring'
|
import querystring from "node:querystring";
|
||||||
import qs from 'qs'
|
import qs from "qs";
|
||||||
import { limitToInt } from "./timeline.js";
|
import { limitToInt } from "./timeline.js";
|
||||||
|
|
||||||
function normalizeQuery(data: any) {
|
function normalizeQuery(data: any) {
|
||||||
const str = querystring.stringify(data);
|
const str = querystring.stringify(data);
|
||||||
return qs.parse(str);
|
return qs.parse(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function apiStatusMastodon(router: Router): void {
|
export function apiStatusMastodon(router: Router): void {
|
||||||
|
@ -18,11 +18,14 @@ export function apiStatusMastodon(router: Router): void {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
let body: any = ctx.request.body;
|
let body: any = ctx.request.body;
|
||||||
if ((!body.poll && body['poll[options][]']) || (!body.media_ids && body['media_ids[]'])) {
|
if (
|
||||||
body = normalizeQuery(body)
|
(!body.poll && body["poll[options][]"]) ||
|
||||||
|
(!body.media_ids && body["media_ids[]"])
|
||||||
|
) {
|
||||||
|
body = normalizeQuery(body);
|
||||||
}
|
}
|
||||||
const text = body.status;
|
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 isDefaultEmoji = emojiRegexAtStartToEnd.test(removed);
|
||||||
const isCustomEmoji = /^:[a-zA-Z0-9@_]+:$/.test(removed);
|
const isCustomEmoji = /^:[a-zA-Z0-9@_]+:$/.test(removed);
|
||||||
if ((body.in_reply_to_id && isDefaultEmoji) || isCustomEmoji) {
|
if ((body.in_reply_to_id && isDefaultEmoji) || isCustomEmoji) {
|
||||||
|
@ -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 = undefined;
|
||||||
if (body.media_ids && !body.media_ids.length) body.media_ids = undefined;
|
if (body.media_ids && !body.media_ids.length) body.media_ids = undefined;
|
||||||
const { sensitive } = body
|
const { sensitive } = body;
|
||||||
body.sensitive = typeof sensitive === 'string' ? sensitive === 'true' : sensitive
|
body.sensitive =
|
||||||
|
typeof sensitive === "string" ? sensitive === "true" : sensitive;
|
||||||
const data = await client.postStatus(text, body);
|
const data = await client.postStatus(text, body);
|
||||||
ctx.body = data.data;
|
ctx.body = data.data;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
@ -57,38 +61,32 @@ export function apiStatusMastodon(router: Router): void {
|
||||||
ctx.body = e.response.data;
|
ctx.body = e.response.data;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
router.get<{ Params: { id: string } }>(
|
router.get<{ Params: { id: string } }>("/v1/statuses/:id", async (ctx) => {
|
||||||
"/v1/statuses/:id",
|
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
||||||
async (ctx) => {
|
const accessTokens = ctx.headers.authorization;
|
||||||
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
const accessTokens = ctx.headers.authorization;
|
try {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const data = await client.getStatus(ctx.params.id);
|
||||||
try {
|
ctx.body = data.data;
|
||||||
const data = await client.getStatus(ctx.params.id);
|
} catch (e: any) {
|
||||||
ctx.body = data.data;
|
console.error(e);
|
||||||
} catch (e: any) {
|
ctx.status = 401;
|
||||||
console.error(e);
|
ctx.body = e.response.data;
|
||||||
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;
|
||||||
router.delete<{ Params: { id: string } }>(
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
"/v1/statuses/:id",
|
try {
|
||||||
async (ctx) => {
|
const data = await client.deleteStatus(ctx.params.id);
|
||||||
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
ctx.body = data.data;
|
||||||
const accessTokens = ctx.headers.authorization;
|
} catch (e: any) {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
console.error(e.response.data, request.params.id);
|
||||||
try {
|
ctx.status = 401;
|
||||||
const data = await client.deleteStatus(ctx.params.id);
|
ctx.body = e.response.data;
|
||||||
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 {
|
interface IReaction {
|
||||||
id: string;
|
id: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
@ -103,12 +101,15 @@ export function apiStatusMastodon(router: Router): void {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const id = ctx.params.id;
|
const id = ctx.params.id;
|
||||||
const data = await client.getStatusContext(id, limitToInt(ctx.query as any));
|
const data = await client.getStatusContext(
|
||||||
|
id,
|
||||||
|
limitToInt(ctx.query as any),
|
||||||
|
);
|
||||||
const status = await client.getStatus(id);
|
const status = await client.getStatus(id);
|
||||||
let reqInstance = axios.create({
|
let reqInstance = axios.create({
|
||||||
headers: {
|
headers: {
|
||||||
Authorization : ctx.headers.authorization
|
Authorization: ctx.headers.authorization,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
const reactionsAxios = await reqInstance.get(
|
const reactionsAxios = await reqInstance.get(
|
||||||
`${BASE_URL}/api/notes/reactions?noteId=${id}`,
|
`${BASE_URL}/api/notes/reactions?noteId=${id}`,
|
||||||
|
@ -296,57 +297,48 @@ export function apiStatusMastodon(router: Router): void {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
router.get<{ Params: { id: string } }>(
|
router.get<{ Params: { id: string } }>("/v1/media/:id", async (ctx) => {
|
||||||
"/v1/media/:id",
|
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
||||||
async (ctx) => {
|
const accessTokens = ctx.headers.authorization;
|
||||||
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
const accessTokens = ctx.headers.authorization;
|
try {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const data = await client.getMedia(ctx.params.id);
|
||||||
try {
|
ctx.body = data.data;
|
||||||
const data = await client.getMedia(ctx.params.id);
|
} catch (e: any) {
|
||||||
ctx.body = data.data;
|
console.error(e);
|
||||||
} catch (e: any) {
|
ctx.status = 401;
|
||||||
console.error(e);
|
ctx.body = e.response.data;
|
||||||
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;
|
||||||
router.put<{ Params: { id: string } }>(
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
"/v1/media/:id",
|
try {
|
||||||
async (ctx) => {
|
const data = await client.updateMedia(
|
||||||
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
ctx.params.id,
|
||||||
const accessTokens = ctx.headers.authorization;
|
ctx.request.body as any,
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
);
|
||||||
try {
|
ctx.body = data.data;
|
||||||
const data = await client.updateMedia(
|
} catch (e: any) {
|
||||||
ctx.params.id,
|
console.error(e);
|
||||||
ctx.request.body as any,
|
ctx.status = 401;
|
||||||
);
|
ctx.body = e.response.data;
|
||||||
ctx.body = data.data;
|
}
|
||||||
} catch (e: any) {
|
});
|
||||||
console.error(e);
|
router.get<{ Params: { id: string } }>("/v1/polls/:id", async (ctx) => {
|
||||||
ctx.status = 401;
|
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
||||||
ctx.body = e.response.data;
|
const accessTokens = ctx.headers.authorization;
|
||||||
}
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
},
|
try {
|
||||||
);
|
const data = await client.getPoll(ctx.params.id);
|
||||||
router.get<{ Params: { id: string } }>(
|
ctx.body = data.data;
|
||||||
"/v1/polls/:id",
|
} catch (e: any) {
|
||||||
async (ctx) => {
|
console.error(e);
|
||||||
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
ctx.status = 401;
|
||||||
const accessTokens = ctx.headers.authorization;
|
ctx.body = e.response.data;
|
||||||
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 } }>(
|
router.post<{ Params: { id: string } }>(
|
||||||
"/v1/polls/:id/votes",
|
"/v1/polls/:id/votes",
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
|
|
|
@ -16,7 +16,8 @@ export function limitToInt(q: ParsedUrlQuery) {
|
||||||
|
|
||||||
export function argsToBools(q: ParsedUrlQuery) {
|
export function argsToBools(q: ParsedUrlQuery) {
|
||||||
// Values taken from https://docs.joinmastodon.org/client/intro/#boolean
|
// 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;
|
let object: any = q;
|
||||||
if (q.only_media)
|
if (q.only_media)
|
||||||
|
@ -35,26 +36,26 @@ export function toTextWithReaction(status: Entity.Status[], host: string) {
|
||||||
if (!t.emoji_reactions) return t;
|
if (!t.emoji_reactions) return t;
|
||||||
if (t.reblog) t.reblog = toTextWithReaction([t.reblog], host)[0];
|
if (t.reblog) t.reblog = toTextWithReaction([t.reblog], host)[0];
|
||||||
const reactions = t.emoji_reactions.map((r) => {
|
const reactions = t.emoji_reactions.map((r) => {
|
||||||
const emojiNotation = r.url ? `:${r.name.replace('@.', '')}:` : r.name
|
const emojiNotation = r.url ? `:${r.name.replace("@.", "")}:` : r.name;
|
||||||
return `${emojiNotation} (${r.count}${r.me ? `* ` : ''})`
|
return `${emojiNotation} (${r.count}${r.me ? `* ` : ""})`;
|
||||||
});
|
});
|
||||||
const reaction = t.emoji_reactions as Entity.Reaction[];
|
const reaction = t.emoji_reactions as Entity.Reaction[];
|
||||||
const emoji = t.emojis || []
|
const emoji = t.emojis || [];
|
||||||
for (const r of reaction) {
|
for (const r of reaction) {
|
||||||
if (!r.url) continue
|
if (!r.url) continue;
|
||||||
emoji.push({
|
emoji.push({
|
||||||
'shortcode': r.name,
|
shortcode: r.name,
|
||||||
'url': r.url,
|
url: r.url,
|
||||||
'static_url': r.url,
|
static_url: r.url,
|
||||||
'visible_in_picker': true,
|
visible_in_picker: true,
|
||||||
category: ""
|
category: "",
|
||||||
},)
|
});
|
||||||
}
|
}
|
||||||
const isMe = reaction.findIndex((r) => r.me) > -1;
|
const isMe = reaction.findIndex((r) => r.me) > -1;
|
||||||
const total = reaction.reduce((sum, reaction) => sum + reaction.count, 0);
|
const total = reaction.reduce((sum, reaction) => sum + reaction.count, 0);
|
||||||
t.favourited = isMe;
|
t.favourited = isMe;
|
||||||
t.favourites_count = total;
|
t.favourites_count = total;
|
||||||
t.emojis = emoji
|
t.emojis = emoji;
|
||||||
t.content = `<p>${autoLinker(t.content, host)}</p><p>${reactions.join(
|
t.content = `<p>${autoLinker(t.content, host)}</p><p>${reactions.join(
|
||||||
", ",
|
", ",
|
||||||
)}</p>`;
|
)}</p>`;
|
||||||
|
@ -126,23 +127,20 @@ export function apiTimelineMastodon(router: Router): void {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
router.get(
|
router.get("/v1/timelines/home", async (ctx, reply) => {
|
||||||
"/v1/timelines/home",
|
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
||||||
async (ctx, reply) => {
|
const accessTokens = ctx.headers.authorization;
|
||||||
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
const accessTokens = ctx.headers.authorization;
|
try {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const data = await client.getHomeTimeline(limitToInt(ctx.query));
|
||||||
try {
|
ctx.body = toTextWithReaction(data.data, ctx.hostname);
|
||||||
const data = await client.getHomeTimeline(limitToInt(ctx.query));
|
} catch (e: any) {
|
||||||
ctx.body = toTextWithReaction(data.data, ctx.hostname);
|
console.error(e);
|
||||||
} catch (e: any) {
|
console.error(e.response.data);
|
||||||
console.error(e);
|
ctx.status = 401;
|
||||||
console.error(e.response.data);
|
ctx.body = e.response.data;
|
||||||
ctx.status = 401;
|
}
|
||||||
ctx.body = e.response.data;
|
});
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
router.get<{ Params: { listId: string } }>(
|
router.get<{ Params: { listId: string } }>(
|
||||||
"/v1/timelines/list/:listId",
|
"/v1/timelines/list/:listId",
|
||||||
async (ctx, reply) => {
|
async (ctx, reply) => {
|
||||||
|
|
|
@ -12,7 +12,11 @@ import {
|
||||||
} from "@/models/index.js";
|
} from "@/models/index.js";
|
||||||
import type { ILocalUser } from "@/models/entities/user.js";
|
import type { ILocalUser } from "@/models/entities/user.js";
|
||||||
import { genId } from "@/misc/gen-id.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 { verifyLogin, hash } from "../2fa.js";
|
||||||
import { randomBytes } from "node:crypto";
|
import { randomBytes } from "node:crypto";
|
||||||
import { IsNull } from "typeorm";
|
import { IsNull } from "typeorm";
|
||||||
|
|
|
@ -414,7 +414,7 @@ export default class Connection {
|
||||||
const client = getClient(this.host, this.accessToken);
|
const client = getClient(this.host, this.accessToken);
|
||||||
client.getStatus(payload.id).then((data) => {
|
client.getStatus(payload.id).then((data) => {
|
||||||
const newPost = toTextWithReaction([data.data], this.host);
|
const newPost = toTextWithReaction([data.data], this.host);
|
||||||
const targetPost = newPost[0]
|
const targetPost = newPost[0];
|
||||||
for (const stream of this.currentSubscribe) {
|
for (const stream of this.currentSubscribe) {
|
||||||
this.wsConnection.send(
|
this.wsConnection.send(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
|
|
@ -31,7 +31,7 @@ import webServer from "./web/index.js";
|
||||||
import { initializeStreamingServer } from "./api/streaming.js";
|
import { initializeStreamingServer } from "./api/streaming.js";
|
||||||
import { koaBody } from "koa-body";
|
import { koaBody } from "koa-body";
|
||||||
import removeTrailingSlash from "koa-remove-trailing-slashes";
|
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);
|
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;
|
const { client_id, state, redirect_uri } = ctx.request.query;
|
||||||
console.log(ctx.request.req);
|
console.log(ctx.request.req);
|
||||||
let param = "mastodon=true";
|
let param = "mastodon=true";
|
||||||
if (state)
|
if (state) param += `&state=${state}`;
|
||||||
param += `&state=${state}`;
|
if (redirect_uri) param += `&redirect_uri=${redirect_uri}`;
|
||||||
if (redirect_uri)
|
const client = client_id ? client_id : "";
|
||||||
param += `&redirect_uri=${redirect_uri}`;
|
ctx.redirect(
|
||||||
const client = client_id? client_id : "";
|
`${Buffer.from(client.toString(), "base64").toString()}?${param}`,
|
||||||
ctx.redirect(`${Buffer.from(client.toString(), 'base64').toString()}?${param}`);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
mastoRouter.post("/oauth/token", async (ctx) => {
|
mastoRouter.post("/oauth/token", async (ctx) => {
|
||||||
const body: any = ctx.request.body || ctx.request.query;
|
const body: any = ctx.request.body || ctx.request.query;
|
||||||
console.log('token-request', body);
|
console.log("token-request", body);
|
||||||
console.log('token-query', ctx.request.query);
|
console.log("token-query", ctx.request.query);
|
||||||
if (body.grant_type === 'client_credentials') {
|
if (body.grant_type === "client_credentials") {
|
||||||
const ret = {
|
const ret = {
|
||||||
access_token: uuid(),
|
access_token: uuid(),
|
||||||
token_type: "Bearer",
|
token_type: "Bearer",
|
||||||
|
@ -197,8 +197,8 @@ mastoRouter.post("/oauth/token", async (ctx) => {
|
||||||
// return;
|
// return;
|
||||||
//}
|
//}
|
||||||
//token = `${m[1]}-${m[2]}-${m[3]}-${m[4]}-${m[5]}`
|
//token = `${m[1]}-${m[2]}-${m[3]}-${m[4]}-${m[5]}`
|
||||||
console.log(body.code, token)
|
console.log(body.code, token);
|
||||||
token = body.code
|
token = body.code;
|
||||||
}
|
}
|
||||||
if (client_id instanceof Array) {
|
if (client_id instanceof Array) {
|
||||||
client_id = client_id.toString();
|
client_id = client_id.toString();
|
||||||
|
@ -214,10 +214,10 @@ mastoRouter.post("/oauth/token", async (ctx) => {
|
||||||
const ret = {
|
const ret = {
|
||||||
access_token: atData.accessToken,
|
access_token: atData.accessToken,
|
||||||
token_type: "Bearer",
|
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),
|
created_at: Math.floor(new Date().getTime() / 1000),
|
||||||
};
|
};
|
||||||
console.log('token-response', ret)
|
console.log("token-response", ret);
|
||||||
ctx.body = ret;
|
ctx.body = ret;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
|
@ -136,7 +136,9 @@ export const routes = [
|
||||||
{
|
{
|
||||||
path: "/custom-katex-macro",
|
path: "/custom-katex-macro",
|
||||||
name: "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",
|
path: "/account-info",
|
||||||
|
@ -243,7 +245,9 @@ export const routes = [
|
||||||
{
|
{
|
||||||
path: "/custom-katex-macro",
|
path: "/custom-katex-macro",
|
||||||
name: "general",
|
name: "general",
|
||||||
component: page(() => import("./pages/settings/custom-katex-macro.vue")),
|
component: page(
|
||||||
|
() => import("./pages/settings/custom-katex-macro.vue"),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/accounts",
|
path: "/accounts",
|
||||||
|
|
|
@ -262,7 +262,9 @@ export function getUserMenu(user, router: Router = mainRouter) {
|
||||||
menu = menu.concat([
|
menu = menu.concat([
|
||||||
null,
|
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,
|
text: user.isMuted ? i18n.ts.unmute : i18n.ts.mute,
|
||||||
hidden: user.isBlocking === true,
|
hidden: user.isBlocking === true,
|
||||||
action: toggleMute,
|
action: toggleMute,
|
||||||
|
|
|
@ -1,19 +1,17 @@
|
||||||
type KaTeXMacro = {
|
type KaTeXMacro = {
|
||||||
args: number;
|
args: number;
|
||||||
rule: (string | number)[];
|
rule: (string | number)[];
|
||||||
};
|
};
|
||||||
|
|
||||||
function parseSingleKaTeXMacro(src: string): [string, KaTeXMacro] {
|
function parseSingleKaTeXMacro(src: string): [string, KaTeXMacro] {
|
||||||
const invalid: [string, KaTeXMacro] = ["", { args: 0, rule: [] }];
|
const invalid: [string, KaTeXMacro] = ["", { args: 0, rule: [] }];
|
||||||
|
|
||||||
const skipSpaces = (pos: number): number => {
|
const skipSpaces = (pos: number): number => {
|
||||||
while (src[pos] === " ")
|
while (src[pos] === " ") ++pos;
|
||||||
++pos;
|
|
||||||
return pos;
|
return pos;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!src.startsWith("\\newcommand") || src.slice(-1) !== "}")
|
if (!src.startsWith("\\newcommand") || src.slice(-1) !== "}") return invalid;
|
||||||
return invalid;
|
|
||||||
|
|
||||||
// current index we are checking (= "\\newcommand".length)
|
// current index we are checking (= "\\newcommand".length)
|
||||||
let currentPos: number = 11;
|
let currentPos: number = 11;
|
||||||
|
@ -21,28 +19,21 @@ function parseSingleKaTeXMacro(src: string): [string, KaTeXMacro] {
|
||||||
|
|
||||||
// parse {\name}, (\name), or [\name]
|
// parse {\name}, (\name), or [\name]
|
||||||
let bracket: string;
|
let bracket: string;
|
||||||
if (src[currentPos] === "{")
|
if (src[currentPos] === "{") bracket = "{}";
|
||||||
bracket = "{}";
|
else if (src[currentPos] === "(") bracket = "()";
|
||||||
else if (src[currentPos] === "(")
|
else if (src[currentPos] === "[") bracket = "[]";
|
||||||
bracket = "()";
|
else return invalid;
|
||||||
else if (src[currentPos] === "[")
|
|
||||||
bracket = "[]";
|
|
||||||
else
|
|
||||||
return invalid;
|
|
||||||
|
|
||||||
++currentPos;
|
++currentPos;
|
||||||
currentPos = skipSpaces(currentPos);
|
currentPos = skipSpaces(currentPos);
|
||||||
|
|
||||||
if (src[currentPos] !== "\\")
|
if (src[currentPos] !== "\\") return invalid;
|
||||||
return invalid;
|
|
||||||
|
|
||||||
const closeNameBracketPos: number = src.indexOf(bracket[1], currentPos);
|
const closeNameBracketPos: number = src.indexOf(bracket[1], currentPos);
|
||||||
if (closeNameBracketPos === -1)
|
if (closeNameBracketPos === -1) return invalid;
|
||||||
return invalid;
|
|
||||||
|
|
||||||
const name: string = src.slice(currentPos + 1, closeNameBracketPos).trim();
|
const name: string = src.slice(currentPos + 1, closeNameBracketPos).trim();
|
||||||
if (!/^[a-zA-Z]+$/.test(name))
|
if (!/^[a-zA-Z]+$/.test(name)) return invalid;
|
||||||
return invalid;
|
|
||||||
|
|
||||||
currentPos = skipSpaces(closeNameBracketPos + 1);
|
currentPos = skipSpaces(closeNameBracketPos + 1);
|
||||||
|
|
||||||
|
@ -54,8 +45,7 @@ function parseSingleKaTeXMacro(src: string): [string, KaTeXMacro] {
|
||||||
macro.args = Number(src.slice(currentPos + 1, closeArgsBracketPos).trim());
|
macro.args = Number(src.slice(currentPos + 1, closeArgsBracketPos).trim());
|
||||||
currentPos = closeArgsBracketPos + 1;
|
currentPos = closeArgsBracketPos + 1;
|
||||||
|
|
||||||
if (Number.isNaN(macro.args) || macro.args < 0)
|
if (Number.isNaN(macro.args) || macro.args < 0) return invalid;
|
||||||
return invalid;
|
|
||||||
} else if (src[currentPos] === "{") {
|
} else if (src[currentPos] === "{") {
|
||||||
macro.args = 0;
|
macro.args = 0;
|
||||||
} else {
|
} else {
|
||||||
|
@ -65,8 +55,7 @@ function parseSingleKaTeXMacro(src: string): [string, KaTeXMacro] {
|
||||||
currentPos = skipSpaces(currentPos);
|
currentPos = skipSpaces(currentPos);
|
||||||
|
|
||||||
// parse {rule}
|
// parse {rule}
|
||||||
if (src[currentPos] !== "{")
|
if (src[currentPos] !== "{") return invalid;
|
||||||
return invalid;
|
|
||||||
|
|
||||||
++currentPos;
|
++currentPos;
|
||||||
currentPos = skipSpaces(currentPos);
|
currentPos = skipSpaces(currentPos);
|
||||||
|
@ -94,8 +83,11 @@ function parseSingleKaTeXMacro(src: string): [string, KaTeXMacro] {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const argIndexEndPos = src.slice(numbersignPos + 1).search(/[^\d]/) + numbersignPos;
|
const argIndexEndPos =
|
||||||
const argIndex: number = Number(src.slice(numbersignPos + 1, argIndexEndPos + 1));
|
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)
|
if (Number.isNaN(argIndex) || argIndex < 1 || macro.args < argIndex)
|
||||||
return invalid;
|
return invalid;
|
||||||
|
@ -107,10 +99,8 @@ function parseSingleKaTeXMacro(src: string): [string, KaTeXMacro] {
|
||||||
currentPos = argIndexEndPos + 1;
|
currentPos = argIndexEndPos + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (macro.args === 0)
|
if (macro.args === 0) return [name, macro];
|
||||||
return [name, macro];
|
else return [name + bracket[0], macro];
|
||||||
else
|
|
||||||
return [name + bracket[0], macro];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseKaTeXMacros(src: string): string {
|
export function parseKaTeXMacros(src: string): string {
|
||||||
|
@ -118,8 +108,7 @@ export function parseKaTeXMacros(src: string): string {
|
||||||
|
|
||||||
for (const s of src.split("\n")) {
|
for (const s of src.split("\n")) {
|
||||||
const [name, macro]: [string, KaTeXMacro] = parseSingleKaTeXMacro(s.trim());
|
const [name, macro]: [string, KaTeXMacro] = parseSingleKaTeXMacro(s.trim());
|
||||||
if (name !== "")
|
if (name !== "") result[name] = macro;
|
||||||
result[name] = macro;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return JSON.stringify(result);
|
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]
|
// 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)
|
// 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)
|
function expandKaTeXMacroOnce(
|
||||||
: [string, boolean, number] {
|
src: string,
|
||||||
|
macros: { [name: string]: KaTeXMacro },
|
||||||
|
maxNumberOfExpansions: number,
|
||||||
|
): [string, boolean, number] {
|
||||||
const bracketKinds = 3;
|
const bracketKinds = 3;
|
||||||
const openBracketId: { [bracket: string]: number } = {"(": 0, "{": 1, "[": 2};
|
const openBracketId: { [bracket: string]: number } = {
|
||||||
const closeBracketId: { [bracket: string]: number } = {")": 0, "}": 1, "]": 2};
|
"(": 0,
|
||||||
|
"{": 1,
|
||||||
|
"[": 2,
|
||||||
|
};
|
||||||
|
const closeBracketId: { [bracket: string]: number } = {
|
||||||
|
")": 0,
|
||||||
|
"}": 1,
|
||||||
|
"]": 2,
|
||||||
|
};
|
||||||
const openBracketFromId = ["(", "{", "["];
|
const openBracketFromId = ["(", "{", "["];
|
||||||
const closeBracketFromId = [")", "}", "]"];
|
const closeBracketFromId = [")", "}", "]"];
|
||||||
|
|
||||||
|
@ -142,21 +142,29 @@ function expandKaTeXMacroOnce(src: string, macros: { [name: string]: KaTeXMacro
|
||||||
let result: BracketMapping = {};
|
let result: BracketMapping = {};
|
||||||
const n = src.length;
|
const n = src.length;
|
||||||
|
|
||||||
let depths = new Array<number>(bracketKinds).fill(0); // current bracket depth for "()", "{}", and "[]"
|
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;
|
let isEscaped = false;
|
||||||
|
|
||||||
for (let i = 0; i < n; ++i) {
|
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;
|
isEscaped = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (isEscaped
|
if (
|
||||||
|| (src[i] !== "\\"
|
isEscaped ||
|
||||||
&& !openBracketFromId.includes(src[i])
|
(src[i] !== "\\" &&
|
||||||
&& !closeBracketFromId.includes(src[i])))
|
!openBracketFromId.includes(src[i]) &&
|
||||||
{
|
!closeBracketFromId.includes(src[i]))
|
||||||
|
) {
|
||||||
isEscaped = false;
|
isEscaped = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -178,27 +186,29 @@ function expandKaTeXMacroOnce(src: string, macros: { [name: string]: KaTeXMacro
|
||||||
return result;
|
return result;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
function expandSingleKaTeXMacro(expandedArgs: string[], macroName: string): string {
|
function expandSingleKaTeXMacro(
|
||||||
|
expandedArgs: string[],
|
||||||
|
macroName: string,
|
||||||
|
): string {
|
||||||
let result = "";
|
let result = "";
|
||||||
for (const block of macros[macroName].rule) {
|
for (const block of macros[macroName].rule) {
|
||||||
if (typeof block === "string")
|
if (typeof block === "string") result += block;
|
||||||
result += block;
|
else result += expandedArgs[block - 1];
|
||||||
else
|
|
||||||
result += expandedArgs[block - 1];
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// only expand src.slice(beginPos, endPos)
|
// only expand src.slice(beginPos, endPos)
|
||||||
function expandKaTeXMacroImpl(beginPos: number, endPos: number): [string, boolean] {
|
function expandKaTeXMacroImpl(
|
||||||
if (endPos <= beginPos)
|
beginPos: number,
|
||||||
return ["", false];
|
endPos: number,
|
||||||
|
): [string, boolean] {
|
||||||
|
if (endPos <= beginPos) return ["", false];
|
||||||
|
|
||||||
const raw: string = src.slice(beginPos, endPos);
|
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)
|
if (maxNumberOfExpansions <= 0) return [fallback, false];
|
||||||
return [fallback, false];
|
|
||||||
--maxNumberOfExpansions;
|
--maxNumberOfExpansions;
|
||||||
|
|
||||||
// search for a custom macro
|
// search for a custom macro
|
||||||
|
@ -218,14 +228,13 @@ function expandKaTeXMacroOnce(src: string, macros: { [name: string]: KaTeXMacro
|
||||||
checkedPos = src.indexOf("\\", checkedPos + 1);
|
checkedPos = src.indexOf("\\", checkedPos + 1);
|
||||||
|
|
||||||
// there is no macro to expand
|
// there is no macro to expand
|
||||||
if (checkedPos === -1)
|
if (checkedPos === -1) return [raw, false];
|
||||||
return [raw, false];
|
|
||||||
|
|
||||||
// is it a custom macro?
|
// 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)
|
if (nonAlphaPos === checkedPos) nonAlphaPos = endPos;
|
||||||
nonAlphaPos = endPos;
|
|
||||||
|
|
||||||
let macroNameCandidate = src.slice(checkedPos + 1, nonAlphaPos);
|
let macroNameCandidate = src.slice(checkedPos + 1, nonAlphaPos);
|
||||||
if (macros.hasOwnProperty(macroNameCandidate)) {
|
if (macros.hasOwnProperty(macroNameCandidate)) {
|
||||||
|
@ -239,19 +248,17 @@ function expandKaTeXMacroOnce(src: string, macros: { [name: string]: KaTeXMacro
|
||||||
let nextOpenBracketPos = endPos;
|
let nextOpenBracketPos = endPos;
|
||||||
for (let i = 0; i < bracketKinds; ++i) {
|
for (let i = 0; i < bracketKinds; ++i) {
|
||||||
const pos = src.indexOf(openBracketFromId[i], checkedPos + 1);
|
const pos = src.indexOf(openBracketFromId[i], checkedPos + 1);
|
||||||
if (pos !== -1 && pos < nextOpenBracketPos)
|
if (pos !== -1 && pos < nextOpenBracketPos) nextOpenBracketPos = pos;
|
||||||
nextOpenBracketPos = pos;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextOpenBracketPos === endPos)
|
if (nextOpenBracketPos === endPos) continue; // there is no open bracket
|
||||||
continue; // there is no open bracket
|
|
||||||
|
|
||||||
macroNameCandidate += src[nextOpenBracketPos];
|
macroNameCandidate += src[nextOpenBracketPos];
|
||||||
|
|
||||||
if (macros.hasOwnProperty(macroNameCandidate)) {
|
if (macros.hasOwnProperty(macroNameCandidate)) {
|
||||||
macroBackslashPos = checkedPos;
|
macroBackslashPos = checkedPos;
|
||||||
macroArgBeginPos = nextOpenBracketPos;
|
macroArgBeginPos = nextOpenBracketPos;
|
||||||
macroArgEndPos = nextOpenBracketPos; // to search the first arg from here
|
macroArgEndPos = nextOpenBracketPos; // to search the first arg from here
|
||||||
macroName = macroNameCandidate;
|
macroName = macroNameCandidate;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -265,31 +272,46 @@ function expandKaTeXMacroOnce(src: string, macros: { [name: string]: KaTeXMacro
|
||||||
for (let i = 0; i < numArgs; ++i) {
|
for (let i = 0; i < numArgs; ++i) {
|
||||||
// find the first open bracket after what we've searched
|
// find the first open bracket after what we've searched
|
||||||
const nextOpenBracketPos = src.indexOf(openBracket, macroArgEndPos);
|
const nextOpenBracketPos = src.indexOf(openBracket, macroArgEndPos);
|
||||||
if (nextOpenBracketPos === -1)
|
if (nextOpenBracketPos === -1) return [fallback, false]; // not enough arguments are provided
|
||||||
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 (!bracketMapping[nextOpenBracketPos])
|
|
||||||
return [fallback, false]; // found open bracket doesn't correspond to any close bracket
|
|
||||||
|
|
||||||
macroArgEndPos = bracketMapping[nextOpenBracketPos];
|
macroArgEndPos = bracketMapping[nextOpenBracketPos];
|
||||||
expandedArgs[i] = expandKaTeXMacroImpl(nextOpenBracketPos + 1, macroArgEndPos)[0];
|
expandedArgs[i] = expandKaTeXMacroImpl(
|
||||||
|
nextOpenBracketPos + 1,
|
||||||
|
macroArgEndPos,
|
||||||
|
)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [src.slice(beginPos, macroBackslashPos)
|
return [
|
||||||
+ expandSingleKaTeXMacro(expandedArgs, macroName)
|
src.slice(beginPos, macroBackslashPos) +
|
||||||
+ expandKaTeXMacroImpl(macroArgEndPos + 1, endPos)[0], true];
|
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];
|
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);
|
const macros = JSON.parse(macrosAsJSONString);
|
||||||
|
|
||||||
let expandMore = true;
|
let expandMore = true;
|
||||||
|
|
||||||
while (expandMore && (0 < maxNumberOfExpansions))
|
while (expandMore && 0 < maxNumberOfExpansions)
|
||||||
[src, expandMore, maxNumberOfExpansions] = expandKaTeXMacroOnce(src, macros, maxNumberOfExpansions);
|
[src, expandMore, maxNumberOfExpansions] = expandKaTeXMacroOnce(
|
||||||
|
src,
|
||||||
|
macros,
|
||||||
|
maxNumberOfExpansions,
|
||||||
|
);
|
||||||
|
|
||||||
return src;
|
return src;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,19 @@ import { expandKaTeXMacro } from "@/scripts/katex-macro";
|
||||||
|
|
||||||
export function preprocess(text: string): string {
|
export function preprocess(text: string): string {
|
||||||
if (defaultStore.state.enableCustomKaTeXMacro) {
|
if (defaultStore.state.enableCustomKaTeXMacro) {
|
||||||
const parsedKaTeXMacro = localStorage.getItem("customKaTeXMacroParsed") ?? "{}";
|
const parsedKaTeXMacro =
|
||||||
const maxNumberOfExpansions = 200; // to prevent infinite expansion loops
|
localStorage.getItem("customKaTeXMacroParsed") ?? "{}";
|
||||||
|
const maxNumberOfExpansions = 200; // to prevent infinite expansion loops
|
||||||
|
|
||||||
let nodes = mfm.parse(text);
|
let nodes = mfm.parse(text);
|
||||||
|
|
||||||
for (let node of nodes) {
|
for (let node of nodes) {
|
||||||
if (node["type"] === "mathInline" || node["type"] === "mathBlock") {
|
if (node["type"] === "mathInline" || node["type"] === "mathBlock") {
|
||||||
node["props"]["formula"]
|
node["props"]["formula"] = expandKaTeXMacro(
|
||||||
= expandKaTeXMacro(node["props"]["formula"], parsedKaTeXMacro, maxNumberOfExpansions);
|
node["props"]["formula"],
|
||||||
|
parsedKaTeXMacro,
|
||||||
|
maxNumberOfExpansions,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue