mirror of
https://iceshrimp.dev/limepotato/jormungandr-bite.git
synced 2024-11-25 03:17:38 -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) {
|
||||
return simpleConvert(filter);
|
||||
}
|
||||
export function convertList(list: Entity.List) {
|
||||
export function convertList(list: MastodonEntity.List) {
|
||||
return simpleConvert(list);
|
||||
}
|
||||
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;
|
||||
}
|
||||
});
|
||||
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 { koaBody } from "koa-body";
|
||||
import multer from "@koa/multer";
|
||||
import { setupEndpointsList } from "@/server/api/mastodon/endpoints/list.js";
|
||||
|
||||
export function getClient(
|
||||
BASE_URL: string,
|
||||
|
@ -50,5 +51,6 @@ export function setupMastodonApi(router: Router, fileRouter: Router, upload: mul
|
|||
setupEndpointsNotifications(router);
|
||||
setupEndpointsSearch(router);
|
||||
setupEndpointsMedia(router, fileRouter, upload);
|
||||
setupEndpointsList(router);
|
||||
setupEndpointsMisc(router);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue