mirror of
https://iceshrimp.dev/limepotato/jormungandr-bite.git
synced 2024-11-28 21:08:52 -07:00
[mastodon-client] GET /lists, /lists/:id, /lists/:id/accounts
This commit is contained in:
parent
a7ce94fb29
commit
633fe46fb5
5 changed files with 234 additions and 170 deletions
|
@ -20,7 +20,7 @@ export function convertAttachment(attachment: Entity.Attachment) {
|
||||||
export function convertFilter(filter: Entity.Filter) {
|
export function convertFilter(filter: Entity.Filter) {
|
||||||
return simpleConvert(filter);
|
return simpleConvert(filter);
|
||||||
}
|
}
|
||||||
export function convertList(list: Entity.List) {
|
export function convertList(list: MastodonEntity.List) {
|
||||||
return simpleConvert(list);
|
return simpleConvert(list);
|
||||||
}
|
}
|
||||||
export function convertFeaturedTag(tag: Entity.FeaturedTag) {
|
export function convertFeaturedTag(tag: Entity.FeaturedTag) {
|
||||||
|
|
181
packages/backend/src/server/api/mastodon/endpoints/list.ts
Normal file
181
packages/backend/src/server/api/mastodon/endpoints/list.ts
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
import Router from "@koa/router";
|
||||||
|
import { getClient } from "../index.js";
|
||||||
|
import { ParsedUrlQuery } from "querystring";
|
||||||
|
import {
|
||||||
|
convertAccount,
|
||||||
|
convertConversation,
|
||||||
|
convertList,
|
||||||
|
convertStatus,
|
||||||
|
} from "../converters.js";
|
||||||
|
import { convertId, IdType } from "../../index.js";
|
||||||
|
import authenticate from "@/server/api/authenticate.js";
|
||||||
|
import { TimelineHelpers } from "@/server/api/mastodon/helpers/timeline.js";
|
||||||
|
import { NoteConverter } from "@/server/api/mastodon/converters/note.js";
|
||||||
|
import { UserHelpers } from "@/server/api/mastodon/helpers/user.js";
|
||||||
|
import { convertPaginationArgsIds, limitToInt, normalizeUrlQuery } from "@/server/api/mastodon/endpoints/timeline.js";
|
||||||
|
import { ListHelpers } from "@/server/api/mastodon/helpers/list.js";
|
||||||
|
import { UserConverter } from "@/server/api/mastodon/converters/user.js";
|
||||||
|
import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js";
|
||||||
|
|
||||||
|
export function setupEndpointsList(router: Router): void {
|
||||||
|
router.get("/v1/lists", async (ctx, reply) => {
|
||||||
|
try {
|
||||||
|
const auth = await authenticate(ctx.headers.authorization, null);
|
||||||
|
const user = auth[0] ?? undefined;
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
ctx.status = 401;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.body = await ListHelpers.getLists(user)
|
||||||
|
.then(p => p.map(list => convertList(list)));
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error(e);
|
||||||
|
console.error(e.response.data);
|
||||||
|
ctx.status = 401;
|
||||||
|
ctx.body = e.response.data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router.get<{ Params: { id: string } }>(
|
||||||
|
"/v1/lists/:id",
|
||||||
|
async (ctx, reply) => {
|
||||||
|
try {
|
||||||
|
const auth = await authenticate(ctx.headers.authorization, null);
|
||||||
|
const user = auth[0] ?? undefined;
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
ctx.status = 401;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
|
|
||||||
|
ctx.body = await ListHelpers.getList(user, id)
|
||||||
|
.then(p => convertList(p));
|
||||||
|
} catch (e: any) {
|
||||||
|
ctx.status = 404;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
router.post("/v1/lists", async (ctx, reply) => {
|
||||||
|
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
||||||
|
const accessTokens = ctx.headers.authorization;
|
||||||
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
|
try {
|
||||||
|
const data = await client.createList((ctx.request.body as any).title);
|
||||||
|
ctx.body = convertList(data.data);
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error(e);
|
||||||
|
console.error(e.response.data);
|
||||||
|
ctx.status = 401;
|
||||||
|
ctx.body = e.response.data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router.put<{ Params: { id: string } }>(
|
||||||
|
"/v1/lists/:id",
|
||||||
|
async (ctx, reply) => {
|
||||||
|
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
||||||
|
const accessTokens = ctx.headers.authorization;
|
||||||
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
|
try {
|
||||||
|
const data = await client.updateList(
|
||||||
|
convertId(ctx.params.id, IdType.IceshrimpId),
|
||||||
|
(ctx.request.body as any).title,
|
||||||
|
);
|
||||||
|
ctx.body = convertList(data.data);
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error(e);
|
||||||
|
console.error(e.response.data);
|
||||||
|
ctx.status = 401;
|
||||||
|
ctx.body = e.response.data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
router.delete<{ Params: { id: string } }>(
|
||||||
|
"/v1/lists/:id",
|
||||||
|
async (ctx, reply) => {
|
||||||
|
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
||||||
|
const accessTokens = ctx.headers.authorization;
|
||||||
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
|
try {
|
||||||
|
const data = await client.deleteList(
|
||||||
|
convertId(ctx.params.id, IdType.IceshrimpId),
|
||||||
|
);
|
||||||
|
ctx.body = data.data;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error(e);
|
||||||
|
console.error(e.response.data);
|
||||||
|
ctx.status = 401;
|
||||||
|
ctx.body = e.response.data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
router.get<{ Params: { id: string } }>(
|
||||||
|
"/v1/lists/:id/accounts",
|
||||||
|
async (ctx, reply) => {
|
||||||
|
try {
|
||||||
|
const auth = await authenticate(ctx.headers.authorization, null);
|
||||||
|
const user = auth[0] ?? undefined;
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
ctx.status = 401;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
|
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)));
|
||||||
|
const res = await ListHelpers.getListUsers(user, id, args.max_id, args.since_id, args.min_id, args.limit);
|
||||||
|
const accounts = await UserConverter.encodeMany(res.data);
|
||||||
|
ctx.body = accounts.map(account => convertAccount(account));
|
||||||
|
PaginationHelpers.appendLinkPaginationHeader(args, ctx, res);
|
||||||
|
} catch (e: any) {
|
||||||
|
ctx.status = 404;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
router.post<{ Params: { id: string } }>(
|
||||||
|
"/v1/lists/:id/accounts",
|
||||||
|
async (ctx, reply) => {
|
||||||
|
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
||||||
|
const accessTokens = ctx.headers.authorization;
|
||||||
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
|
try {
|
||||||
|
const data = await client.addAccountsToList(
|
||||||
|
convertId(ctx.params.id, IdType.IceshrimpId),
|
||||||
|
(ctx.query.account_ids as string[]).map((id) =>
|
||||||
|
convertId(id, IdType.IceshrimpId),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
ctx.body = data.data;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error(e);
|
||||||
|
console.error(e.response.data);
|
||||||
|
ctx.status = 401;
|
||||||
|
ctx.body = e.response.data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
router.delete<{ Params: { id: string } }>(
|
||||||
|
"/v1/lists/:id/accounts",
|
||||||
|
async (ctx, reply) => {
|
||||||
|
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
||||||
|
const accessTokens = ctx.headers.authorization;
|
||||||
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
|
try {
|
||||||
|
const data = await client.deleteAccountsFromList(
|
||||||
|
convertId(ctx.params.id, IdType.IceshrimpId),
|
||||||
|
(ctx.query.account_ids as string[]).map((id) =>
|
||||||
|
convertId(id, IdType.IceshrimpId),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
ctx.body = data.data;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error(e);
|
||||||
|
console.error(e.response.data);
|
||||||
|
ctx.status = 401;
|
||||||
|
ctx.body = e.response.data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
|
@ -171,173 +171,4 @@ export function setupEndpointsTimeline(router: Router): void {
|
||||||
ctx.body = e.response.data;
|
ctx.body = e.response.data;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
router.get("/v1/lists", async (ctx, reply) => {
|
|
||||||
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
|
||||||
const accessTokens = ctx.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.getLists();
|
|
||||||
ctx.body = data.data.map((list) => convertList(list));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
ctx.status = 401;
|
|
||||||
ctx.body = e.response.data;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
router.get<{ Params: { id: string } }>(
|
|
||||||
"/v1/lists/:id",
|
|
||||||
async (ctx, reply) => {
|
|
||||||
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
|
||||||
const accessTokens = ctx.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.getList(
|
|
||||||
convertId(ctx.params.id, IdType.IceshrimpId),
|
|
||||||
);
|
|
||||||
ctx.body = convertList(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
ctx.status = 401;
|
|
||||||
ctx.body = e.response.data;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
router.post("/v1/lists", async (ctx, reply) => {
|
|
||||||
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
|
||||||
const accessTokens = ctx.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.createList((ctx.request.body as any).title);
|
|
||||||
ctx.body = convertList(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
ctx.status = 401;
|
|
||||||
ctx.body = e.response.data;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
router.put<{ Params: { id: string } }>(
|
|
||||||
"/v1/lists/:id",
|
|
||||||
async (ctx, reply) => {
|
|
||||||
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
|
||||||
const accessTokens = ctx.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.updateList(
|
|
||||||
convertId(ctx.params.id, IdType.IceshrimpId),
|
|
||||||
(ctx.request.body as any).title,
|
|
||||||
);
|
|
||||||
ctx.body = convertList(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
ctx.status = 401;
|
|
||||||
ctx.body = e.response.data;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
router.delete<{ Params: { id: string } }>(
|
|
||||||
"/v1/lists/:id",
|
|
||||||
async (ctx, reply) => {
|
|
||||||
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
|
||||||
const accessTokens = ctx.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.deleteList(
|
|
||||||
convertId(ctx.params.id, IdType.IceshrimpId),
|
|
||||||
);
|
|
||||||
ctx.body = data.data;
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
ctx.status = 401;
|
|
||||||
ctx.body = e.response.data;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
router.get<{ Params: { id: string } }>(
|
|
||||||
"/v1/lists/:id/accounts",
|
|
||||||
async (ctx, reply) => {
|
|
||||||
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
|
||||||
const accessTokens = ctx.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.getAccountsInList(
|
|
||||||
convertId(ctx.params.id, IdType.IceshrimpId),
|
|
||||||
convertPaginationArgsIds(ctx.query as any),
|
|
||||||
);
|
|
||||||
ctx.body = data.data.map((account) => convertAccount(account));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
ctx.status = 401;
|
|
||||||
ctx.body = e.response.data;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
router.post<{ Params: { id: string } }>(
|
|
||||||
"/v1/lists/:id/accounts",
|
|
||||||
async (ctx, reply) => {
|
|
||||||
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
|
||||||
const accessTokens = ctx.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.addAccountsToList(
|
|
||||||
convertId(ctx.params.id, IdType.IceshrimpId),
|
|
||||||
(ctx.query.account_ids as string[]).map((id) =>
|
|
||||||
convertId(id, IdType.IceshrimpId),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
ctx.body = data.data;
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
ctx.status = 401;
|
|
||||||
ctx.body = e.response.data;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
router.delete<{ Params: { id: string } }>(
|
|
||||||
"/v1/lists/:id/accounts",
|
|
||||||
async (ctx, reply) => {
|
|
||||||
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
|
||||||
const accessTokens = ctx.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.deleteAccountsFromList(
|
|
||||||
convertId(ctx.params.id, IdType.IceshrimpId),
|
|
||||||
(ctx.query.account_ids as string[]).map((id) =>
|
|
||||||
convertId(id, IdType.IceshrimpId),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
ctx.body = data.data;
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
ctx.status = 401;
|
|
||||||
ctx.body = e.response.data;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
function escapeHTML(str: string) {
|
|
||||||
if (!str) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return str
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/"/g, """)
|
|
||||||
.replace(/'/g, "'");
|
|
||||||
}
|
|
||||||
function nl2br(str: string) {
|
|
||||||
if (!str) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
str = str.replace(/\r\n/g, "<br />");
|
|
||||||
str = str.replace(/(\n|\r)/g, "<br />");
|
|
||||||
return str;
|
|
||||||
}
|
}
|
||||||
|
|
50
packages/backend/src/server/api/mastodon/helpers/list.ts
Normal file
50
packages/backend/src/server/api/mastodon/helpers/list.ts
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import { ILocalUser, User } from "@/models/entities/user.js";
|
||||||
|
import { UserListJoinings, UserLists } from "@/models/index.js";
|
||||||
|
import { LinkPaginationObject } from "@/server/api/mastodon/helpers/user.js";
|
||||||
|
import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js";
|
||||||
|
|
||||||
|
export class ListHelpers {
|
||||||
|
public static async getLists(user: ILocalUser): Promise<MastodonEntity.List[]> {
|
||||||
|
return UserLists.findBy({userId: user.id}).then(p => p.map(list => {
|
||||||
|
return {
|
||||||
|
id: list.id,
|
||||||
|
title: list.name
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getList(user: ILocalUser, id: string): Promise<MastodonEntity.List> {
|
||||||
|
return UserLists.findOneByOrFail({userId: user.id, id: id}).then(list => {
|
||||||
|
return {
|
||||||
|
id: list.id,
|
||||||
|
title: list.name
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getListUsers(user: ILocalUser, id: string, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise<LinkPaginationObject<User[]>> {
|
||||||
|
if (limit > 80) limit = 80;
|
||||||
|
const list = await UserLists.findOneByOrFail({userId: user.id, id: id});
|
||||||
|
const query = PaginationHelpers.makePaginationQuery(
|
||||||
|
UserListJoinings.createQueryBuilder('member'),
|
||||||
|
sinceId,
|
||||||
|
maxId,
|
||||||
|
minId
|
||||||
|
)
|
||||||
|
.andWhere("member.userListId = :listId", {listId: id})
|
||||||
|
.innerJoinAndSelect("member.user", "user");
|
||||||
|
|
||||||
|
return query.take(limit).getMany().then(async p => {
|
||||||
|
if (minId !== undefined) p = p.reverse();
|
||||||
|
const users = p
|
||||||
|
.map(p => p.user)
|
||||||
|
.filter(p => p) as User[];
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: users,
|
||||||
|
maxId: p.map(p => p.id).at(-1),
|
||||||
|
minId: p.map(p => p.id)[0],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import { setupEndpointsMedia } from "@/server/api/mastodon/endpoints/media.js";
|
||||||
import { setupEndpointsMisc } from "@/server/api/mastodon/endpoints/misc.js";
|
import { setupEndpointsMisc } from "@/server/api/mastodon/endpoints/misc.js";
|
||||||
import { koaBody } from "koa-body";
|
import { koaBody } from "koa-body";
|
||||||
import multer from "@koa/multer";
|
import multer from "@koa/multer";
|
||||||
|
import { setupEndpointsList } from "@/server/api/mastodon/endpoints/list.js";
|
||||||
|
|
||||||
export function getClient(
|
export function getClient(
|
||||||
BASE_URL: string,
|
BASE_URL: string,
|
||||||
|
@ -50,5 +51,6 @@ export function setupMastodonApi(router: Router, fileRouter: Router, upload: mul
|
||||||
setupEndpointsNotifications(router);
|
setupEndpointsNotifications(router);
|
||||||
setupEndpointsSearch(router);
|
setupEndpointsSearch(router);
|
||||||
setupEndpointsMedia(router, fileRouter, upload);
|
setupEndpointsMedia(router, fileRouter, upload);
|
||||||
|
setupEndpointsList(router);
|
||||||
setupEndpointsMisc(router);
|
setupEndpointsMisc(router);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue