diff --git a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts index 3751c8f6f..f6575bc5a 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts @@ -7,6 +7,7 @@ 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 { UserLists } from "@/models/index.js"; export function limitToInt(q: ParsedUrlQuery, additional: string[] = []) { let object: any = q; @@ -131,15 +132,30 @@ export function setupEndpointsTimeline(router: Router): void { router.get<{ Params: { listId: string } }>( "/v1/timelines/list/:listId", 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.getListTimeline( - convertId(ctx.params.listId, IdType.IceshrimpId), - convertPaginationArgsIds(limitToInt(ctx.query)), - ); - ctx.body = data.data.map((status) => convertStatus(status)); + const auth = await authenticate(ctx.headers.authorization, null); + const user = auth[0] ?? undefined; + + if (!user) { + ctx.status = 401; + return; + } + + const listId = convertId(ctx.params.listId, IdType.IceshrimpId); + const list = await UserLists.findOneBy({userId: user.id, id: listId}); + + if (!list) { + ctx.status = 404; + return; + } + + const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query))); + const cache = UserHelpers.getFreshAccountCache(); + const tl = await TimelineHelpers.getListTimeline(user, list, args.max_id, args.since_id, args.min_id, args.limit) + .then(n => NoteConverter.encodeMany(n, user, cache)); + + ctx.body = tl.map(s => convertStatus(s)); + } catch (e: any) { console.error(e); console.error(e.response.data); diff --git a/packages/backend/src/server/api/mastodon/helpers/list.ts b/packages/backend/src/server/api/mastodon/helpers/list.ts index 299791534..ff3d52767 100644 --- a/packages/backend/src/server/api/mastodon/helpers/list.ts +++ b/packages/backend/src/server/api/mastodon/helpers/list.ts @@ -31,7 +31,7 @@ export class ListHelpers { maxId, minId ) - .andWhere("member.userListId = :listId", {listId: id}) + .andWhere("member.userListId = :listId", {listId: list.id}) .innerJoinAndSelect("member.user", "user"); return query.take(limit).getMany().then(async p => { diff --git a/packages/backend/src/server/api/mastodon/helpers/timeline.ts b/packages/backend/src/server/api/mastodon/helpers/timeline.ts index de899d0ce..7c032b590 100644 --- a/packages/backend/src/server/api/mastodon/helpers/timeline.ts +++ b/packages/backend/src/server/api/mastodon/helpers/timeline.ts @@ -1,6 +1,6 @@ import { Note } from "@/models/entities/note.js"; import { ILocalUser } from "@/models/entities/user.js"; -import { Followings, Notes } from "@/models/index.js"; +import { Followings, Notes, UserListJoinings } from "@/models/index.js"; import { Brackets } from "typeorm"; import { generateChannelQuery } from "@/server/api/common/generate-channel-query.js"; import { generateRepliesQuery } from "@/server/api/common/generate-replies-query.js"; @@ -13,6 +13,7 @@ import { fetchMeta } from "@/misc/fetch-meta.js"; import { ApiError } from "@/server/api/error.js"; import { meta } from "@/server/api/endpoints/notes/global-timeline.js"; import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js"; +import { UserList } from "@/models/entities/user-list.js"; export class TimelineHelpers { public static async getHomeTimeline(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20): Promise { @@ -89,4 +90,28 @@ export class TimelineHelpers { return PaginationHelpers.execQuery(query, limit, minId !== undefined); } + + public static async getListTimeline(user: ILocalUser, list: UserList, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20): Promise { + if (limit > 40) limit = 40; + if (user.id != list.userId) throw new Error("List is not owned by user"); + + const listQuery = UserListJoinings.createQueryBuilder("member") + .select("member.userId", 'userId') + .where("member.userListId = :listId"); + + const query = PaginationHelpers.makePaginationQuery( + Notes.createQueryBuilder("note"), + sinceId, + maxId, + minId + ) + .andWhere(`note.userId IN (${listQuery.getQuery()})`) + .andWhere("note.visibility != 'specified'") + .leftJoinAndSelect("note.renote", "renote") + .setParameters({listId: list.id}); + + generateVisibilityQuery(query, user); + + return PaginationHelpers.execQuery(query, limit, minId !== undefined); + } }