diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts index e16f89261..bb61bbc79 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/account.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts @@ -1,6 +1,6 @@ import Router from "@koa/router"; import { getClient } from "../ApiMastodonCompatibleService.js"; -import { argsToBools, convertTimelinesArgsId, limitToInt, normalizeUrlQuery } from "./timeline.js"; +import { argsToBools, convertPaginationArgsIds, limitToInt, normalizeUrlQuery } from "./timeline.js"; import { convertId, IdType } from "../../index.js"; import { convertAccount, convertFeaturedTag, convertList, convertRelationship, convertStatus, } from "../converters.js"; import { getUser } from "@/server/api/common/getters.js"; @@ -138,7 +138,7 @@ export function apiAccountMastodon(router: Router): void { const userId = convertId(ctx.params.id, IdType.IceshrimpId); const cache = UserHelpers.getFreshAccountCache(); const query = await UserHelpers.getUserCached(userId, cache); - const args = normalizeUrlQuery(convertTimelinesArgsId(argsToBools(limitToInt(ctx.query)))); + const args = normalizeUrlQuery(convertPaginationArgsIds(argsToBools(limitToInt(ctx.query)))); const tl = await UserHelpers.getUserStatuses(query, user, args.max_id, args.since_id, args.min_id, args.limit, args.only_media, args.exclude_replies, args.exclude_reblogs, args.pinned, args.tagged) .then(n => NoteConverter.encodeMany(n, user, cache)); @@ -180,7 +180,7 @@ export function apiAccountMastodon(router: Router): void { const userId = convertId(ctx.params.id, IdType.IceshrimpId); const cache = UserHelpers.getFreshAccountCache(); const query = await UserHelpers.getUserCached(userId, cache); - const args = normalizeUrlQuery(convertTimelinesArgsId(limitToInt(ctx.query as any))); + const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); const res = await UserHelpers.getUserFollowers(query, user, args.max_id, args.since_id, args.min_id, args.limit); const followers = await UserConverter.encodeMany(res.data, cache); @@ -205,7 +205,7 @@ export function apiAccountMastodon(router: Router): void { const userId = convertId(ctx.params.id, IdType.IceshrimpId); const cache = UserHelpers.getFreshAccountCache(); const query = await UserHelpers.getUserCached(userId, cache); - const args = normalizeUrlQuery(convertTimelinesArgsId(limitToInt(ctx.query as any))); + const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); const res = await UserHelpers.getUserFollowing(query, user, args.max_id, args.since_id, args.min_id, args.limit); const following = await UserConverter.encodeMany(res.data, cache); @@ -419,7 +419,7 @@ export function apiAccountMastodon(router: Router): void { } const cache = UserHelpers.getFreshAccountCache(); - const args = normalizeUrlQuery(convertTimelinesArgsId(limitToInt(ctx.query as any))); + const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); const res = await UserHelpers.getUserBookmarks(user, args.max_id, args.since_id, args.min_id, args.limit); const bookmarks = await NoteConverter.encodeMany(res.data, user, cache); @@ -443,7 +443,7 @@ export function apiAccountMastodon(router: Router): void { } const cache = UserHelpers.getFreshAccountCache(); - const args = normalizeUrlQuery(convertTimelinesArgsId(limitToInt(ctx.query as any))); + const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); const res = await UserHelpers.getUserFavorites(user, args.max_id, args.since_id, args.min_id, args.limit); const favorites = await NoteConverter.encodeMany(res.data, user, cache); @@ -467,7 +467,7 @@ export function apiAccountMastodon(router: Router): void { } const cache = UserHelpers.getFreshAccountCache(); - const args = normalizeUrlQuery(convertTimelinesArgsId(limitToInt(ctx.query as any))); + const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); const res = await UserHelpers.getUserMutes(user, args.max_id, args.since_id, args.min_id, args.limit, cache); ctx.body = res.data.map(m => convertAccount(m)); PaginationHelpers.appendLinkPaginationHeader(args, ctx, res); @@ -489,7 +489,7 @@ export function apiAccountMastodon(router: Router): void { } const cache = UserHelpers.getFreshAccountCache(); - const args = normalizeUrlQuery(convertTimelinesArgsId(limitToInt(ctx.query as any))); + const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); const res = await UserHelpers.getUserBlocks(user, args.max_id, args.since_id, args.min_id, args.limit); const blocks = await UserConverter.encodeMany(res.data, cache); ctx.body = blocks.map(b => convertAccount(b)); @@ -502,14 +502,21 @@ export function apiAccountMastodon(router: Router): void { } }); router.get("/v1/follow_requests", async (ctx) => { - const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; - const accessTokens = ctx.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { - const data = await client.getFollowRequests( - ((ctx.query as any) || { limit: 20 }).limit, - ); - ctx.body = data.data.map((account) => convertAccount(account)); + const auth = await authenticate(ctx.headers.authorization, null); + const user = auth[0] ?? null; + + if (!user) { + ctx.status = 401; + return; + } + + const cache = UserHelpers.getFreshAccountCache(); + const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); + const res = await UserHelpers.getUserFollowRequests(user, args.max_id, args.since_id, args.min_id, args.limit); + const requests = await UserConverter.encodeMany(res.data, cache); + ctx.body = requests.map(b => convertAccount(b)); + PaginationHelpers.appendLinkPaginationHeader(args, ctx, res); } catch (e: any) { console.error(e); console.error(e.response.data); diff --git a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts index 97a992945..91c73d374 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts @@ -1,6 +1,6 @@ import Router from "@koa/router"; import { convertId, IdType } from "../../index.js"; -import { convertTimelinesArgsId, limitToInt, normalizeUrlQuery } from "./timeline.js"; +import { convertPaginationArgsIds, limitToInt, normalizeUrlQuery } from "./timeline.js"; import { convertNotification } from "../converters.js"; import authenticate from "@/server/api/authenticate.js"; import { UserHelpers } from "@/server/api/mastodon/helpers/user.js"; @@ -19,7 +19,7 @@ export function apiNotificationsMastodon(router: Router): void { } const cache = UserHelpers.getFreshAccountCache(); - const args = normalizeUrlQuery(convertTimelinesArgsId(limitToInt(ctx.query)), ['types[]', 'exclude_types[]']); + const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)), ['types[]', 'exclude_types[]']); const data = NotificationHelpers.getNotifications(user, args.max_id, args.since_id, args.min_id, args.limit, args['types[]'], args['exclude_types[]'], args.account_id) .then(p => NotificationConverter.encodeMany(p, user, cache)) .then(p => p.map(n => convertNotification(n))); diff --git a/packages/backend/src/server/api/mastodon/endpoints/search.ts b/packages/backend/src/server/api/mastodon/endpoints/search.ts index 8a4817557..c08c52b24 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/search.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/search.ts @@ -3,7 +3,7 @@ import Router from "@koa/router"; import { getClient } from "../ApiMastodonCompatibleService.js"; import axios from "axios"; import { Converter } from "megalodon"; -import { convertTimelinesArgsId, limitToInt } from "./timeline.js"; +import { convertPaginationArgsIds, limitToInt } from "./timeline.js"; import { convertAccount, convertStatus } from "../converters.js"; export function apiSearchMastodon(router: Router): void { @@ -13,7 +13,7 @@ export function apiSearchMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); const body: any = ctx.request.body; try { - const query: any = convertTimelinesArgsId(limitToInt(ctx.query)); + const query: any = convertPaginationArgsIds(limitToInt(ctx.query)); const type = query.type || ""; const data = await client.search(query.q, type, query); ctx.body = data.data; @@ -28,7 +28,7 @@ export function apiSearchMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const query: any = convertTimelinesArgsId(limitToInt(ctx.query)); + const query: any = convertPaginationArgsIds(limitToInt(ctx.query)); const type = query.type; const acct = !type || type === "accounts" diff --git a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts index f31dd22cb..82b8c8ed5 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts @@ -43,7 +43,7 @@ export function argsToBools(q: ParsedUrlQuery, additional: string[] = []) { return object; } -export function convertTimelinesArgsId(q: ParsedUrlQuery) { +export function convertPaginationArgsIds(q: ParsedUrlQuery) { if (typeof q.min_id === "string") q.min_id = convertId(q.min_id, IdType.IceshrimpId); if (typeof q.max_id === "string") @@ -77,7 +77,7 @@ export function apiTimelineMastodon(router: Router): void { return; } - const args = normalizeUrlQuery(convertTimelinesArgsId(argsToBools(limitToInt(ctx.query)))); + const args = normalizeUrlQuery(convertPaginationArgsIds(argsToBools(limitToInt(ctx.query)))); const cache = UserHelpers.getFreshAccountCache(); const tl = await TimelineHelpers.getPublicTimeline(user, args.max_id, args.since_id, args.min_id, args.limit, args.only_media, args.local, args.remote) .then(n => NoteConverter.encodeMany(n, user, cache)); @@ -99,7 +99,7 @@ export function apiTimelineMastodon(router: Router): void { try { const data = await client.getTagTimeline( ctx.params.hashtag, - convertTimelinesArgsId(argsToBools(limitToInt(ctx.query))), + convertPaginationArgsIds(argsToBools(limitToInt(ctx.query))), ); ctx.body = data.data.map((status) => convertStatus(status)); } catch (e: any) { @@ -120,7 +120,7 @@ export function apiTimelineMastodon(router: Router): void { return; } - const args = normalizeUrlQuery(convertTimelinesArgsId(limitToInt(ctx.query))); + const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query))); const cache = UserHelpers.getFreshAccountCache(); const tl = await TimelineHelpers.getHomeTimeline(user, args.max_id, args.since_id, args.min_id, args.limit) .then(n => NoteConverter.encodeMany(n, user, cache)); @@ -142,7 +142,7 @@ export function apiTimelineMastodon(router: Router): void { try { const data = await client.getListTimeline( convertId(ctx.params.listId, IdType.IceshrimpId), - convertTimelinesArgsId(limitToInt(ctx.query)), + convertPaginationArgsIds(limitToInt(ctx.query)), ); ctx.body = data.data.map((status) => convertStatus(status)); } catch (e: any) { @@ -159,7 +159,7 @@ export function apiTimelineMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.getConversationTimeline( - convertTimelinesArgsId(limitToInt(ctx.query)), + convertPaginationArgsIds(limitToInt(ctx.query)), ); ctx.body = data.data.map((conversation) => convertConversation(conversation), @@ -266,7 +266,7 @@ export function apiTimelineMastodon(router: Router): void { try { const data = await client.getAccountsInList( convertId(ctx.params.id, IdType.IceshrimpId), - convertTimelinesArgsId(ctx.query as any), + convertPaginationArgsIds(ctx.query as any), ); ctx.body = data.data.map((account) => convertAccount(account)); } catch (e: any) { diff --git a/packages/backend/src/server/api/mastodon/helpers/user.ts b/packages/backend/src/server/api/mastodon/helpers/user.ts index 0e2d9f98a..f40ec5837 100644 --- a/packages/backend/src/server/api/mastodon/helpers/user.ts +++ b/packages/backend/src/server/api/mastodon/helpers/user.ts @@ -169,7 +169,7 @@ export class UserHelpers { query.andWhere("blocking.blockerId = :userId", {userId: user.id}) .innerJoinAndSelect("blocking.blockee", "blockee"); - return query.take(limit).getMany().then(async p => { + return query.take(limit).getMany().then(p => { if (minId !== undefined) p = p.reverse(); const users = p .map(p => p.blockee) @@ -183,6 +183,33 @@ export class UserHelpers { }); } + public static async getUserFollowRequests(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise> { + if (limit > 80) limit = 80; + + const query = PaginationHelpers.makePaginationQuery( + FollowRequests.createQueryBuilder("request"), + sinceId, + maxId, + minId + ); + + query.andWhere("request.followeeId = :userId", {userId: user.id}) + .innerJoinAndSelect("request.follower", "follower"); + + return query.take(limit).getMany().then(p => { + if (minId !== undefined) p = p.reverse(); + const users = p + .map(p => p.follower) + .filter(p => p) as User[]; + + return { + data: users, + maxId: p.map(p => p.id).at(-1), + minId: p.map(p => p.id)[0], + }; + }); + } + public static async getUserStatuses(user: User, localUser: ILocalUser | null, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, onlyMedia: boolean = false, excludeReplies: boolean = false, excludeReblogs: boolean = false, pinned: boolean = false, tagged: string | undefined): Promise { if (limit > 40) limit = 40;