[mastodon-client] Remove ID conversion

BREAKING: Please log out and log back in of any clients in use, as their cache is now invalid
This commit is contained in:
Laura Hausmann 2023-10-07 22:16:17 +02:00
parent 011d7f36c3
commit 9d24f8aea5
Signed by: zotan
GPG key ID: D044E84C5BE01605
15 changed files with 139 additions and 436 deletions

View file

@ -1,27 +0,0 @@
export enum IdType {
IceshrimpId,
MastodonId
}
const chars = '0123456789abcdefghijklmnopqrstuvwxyz';
//FIXME: This implementation breaks for IceshrimpIDs with leading zeroes
//FIXME: Make this idempotent
export function convertId(id: string, target: IdType): string {
if (target == IdType.IceshrimpId) {
return BigInt(id).toString(36);
}
else if (target == IdType.MastodonId) {
let result = 0n;
const iter = id.toLowerCase();
for (let i = 0; i < iter.length; i++){
const char = iter[i];
if (!chars.includes(char)) throw new Error('Invalid ID');
result = result * 36n + BigInt(chars.indexOf(char));
}
return result.toString();
}
throw new Error('Unknown ID type');
}

View file

@ -20,10 +20,6 @@ import verifyEmail from "./private/verify-email.js";
import discord from "./service/discord.js";
import github from "./service/github.js";
import twitter from "./service/twitter.js";
import { convertId, IdType } from "@/misc/convert-id.js";
// re-export native rust id conversion (function and enum)
export { IdType, convertId };
// Init app
const app = new Koa();

View file

@ -1,117 +0,0 @@
import { convertId, IdType } from "../index.js";
// It's *very* important to put `param = structuredClone(param)` at the top of each function that doesn't simply call simpleConvertId on the param object directly.
function simpleConvertId(data: any) {
// copy the object to bypass weird pass by reference bugs
data = structuredClone(data);
data.id = convertId(data.id, IdType.MastodonId);
return data;
}
export function convertAccountId(account: MastodonEntity.Account | MastodonEntity.MutedAccount) {
return simpleConvertId(account);
}
export function convertAnnouncementId(announcement: MastodonEntity.Announcement) {
return simpleConvertId(announcement);
}
export function convertAttachmentId(attachment: MastodonEntity.Attachment) {
return simpleConvertId(attachment);
}
export function convertListId(list: MastodonEntity.List) {
return simpleConvertId(list);
}
export function convertPollId(poll: MastodonEntity.Poll) {
return simpleConvertId(poll);
}
export function convertRelationshipId(relationship: MastodonEntity.Relationship) {
return simpleConvertId(relationship);
}
export function convertStatusSourceId(statusSource: MastodonEntity.StatusSource) {
return simpleConvertId(statusSource);
}
export function convertSuggestionIds(suggestion: MastodonEntity.SuggestedAccount) {
suggestion = structuredClone(suggestion);
suggestion.account = convertAccountId(suggestion.account)
return suggestion
}
export function convertNotificationIds(notification: MastodonEntity.Notification) {
notification = structuredClone(notification);
notification.account = convertAccountId(notification.account);
notification.id = convertId(notification.id, IdType.MastodonId);
if (notification.status)
notification.status = convertStatusIds(notification.status);
if (notification.reaction)
notification.reaction = convertReactionIds(notification.reaction);
return notification;
}
export function convertReactionIds(reaction: MastodonEntity.Reaction) {
reaction = structuredClone(reaction);
if (reaction.accounts) {
reaction.accounts = reaction.accounts.map(convertAccountId);
}
return reaction;
}
export function convertSearchIds(search: MastodonEntity.Search) {
search = structuredClone(search);
search.accounts = search.accounts.map(p => convertAccountId(p));
search.statuses = search.statuses.map(p => convertStatusIds(p));
return search;
}
export function convertStatusIds(status: MastodonEntity.Status) {
status = structuredClone(status);
status.account = convertAccountId(status.account);
status.id = convertId(status.id, IdType.MastodonId);
if (status.in_reply_to_account_id)
status.in_reply_to_account_id = convertId(
status.in_reply_to_account_id,
IdType.MastodonId,
);
if (status.in_reply_to_id)
status.in_reply_to_id = convertId(status.in_reply_to_id, IdType.MastodonId);
status.media_attachments = status.media_attachments.map((attachment) =>
convertAttachmentId(attachment),
);
status.mentions = status.mentions.map((mention) => ({
...mention,
id: convertId(mention.id, IdType.MastodonId),
}));
if (status.poll) status.poll = convertPollId(status.poll);
if (status.reblog) status.reblog = convertStatusIds(status.reblog);
if (status.quote) status.quote = convertStatusIds(status.quote);
status.reactions = status.reactions.map(convertReactionIds);
return status;
}
export function convertStatusEditIds(edit: MastodonEntity.StatusEdit) {
edit = structuredClone(edit);
edit.account = convertAccountId(edit.account);
edit.media_attachments = edit.media_attachments.map((attachment) =>
convertAttachmentId(attachment),
);
if (edit.poll) edit.poll = convertPollId(edit.poll);
return edit;
}
export function convertConversationIds(conversation: MastodonEntity.Conversation) {
conversation = structuredClone(conversation);
conversation.id = convertId(conversation.id, IdType.MastodonId);
conversation.accounts = conversation.accounts.map(convertAccountId);
if (conversation.last_status) {
conversation.last_status = convertStatusIds(conversation.last_status);
}
return conversation;
}

View file

@ -1,7 +1,5 @@
import Router from "@koa/router";
import { argsToBools, convertPaginationArgsIds, limitToInt, normalizeUrlQuery } from "./timeline.js";
import { convertId, IdType } from "../../index.js";
import { convertAccountId, convertListId, convertRelationshipId, convertStatusIds, } from "../converters.js";
import { argsToBools, limitToInt, normalizeUrlQuery } from "./timeline.js";
import { UserConverter } from "@/server/api/mastodon/converters/user.js";
import { NoteConverter } from "@/server/api/mastodon/converters/note.js";
import { UserHelpers } from "@/server/api/mastodon/helpers/user.js";
@ -12,53 +10,43 @@ export function setupEndpointsAccount(router: Router): void {
router.get("/v1/accounts/verify_credentials",
auth(true, ['read:accounts']),
async (ctx) => {
const acct = await UserHelpers.verifyCredentials(ctx);
ctx.body = convertAccountId(acct);
ctx.body = await UserHelpers.verifyCredentials(ctx);
}
);
router.patch("/v1/accounts/update_credentials",
auth(true, ['write:accounts']),
async (ctx) => {
const acct = await UserHelpers.updateCredentials(ctx);
ctx.body = convertAccountId(acct)
ctx.body = await UserHelpers.updateCredentials(ctx)
}
);
router.get("/v1/accounts/lookup",
async (ctx) => {
const args = normalizeUrlQuery(ctx.query);
const user = await UserHelpers.getUserFromAcct(args.acct);
const account = await UserConverter.encode(user, ctx);
ctx.body = convertAccountId(account);
ctx.body = await UserConverter.encode(user, ctx);
}
);
router.get("/v1/accounts/relationships",
auth(true, ['read:follows']),
async (ctx) => {
const ids = (normalizeUrlQuery(ctx.query, ['id[]'])['id[]'] ?? [])
.map((id: string) => convertId(id, IdType.IceshrimpId));
const result = await UserHelpers.getUserRelationhipToMany(ids, ctx.user.id);
ctx.body = result.map(rel => convertRelationshipId(rel));
const ids = (normalizeUrlQuery(ctx.query, ['id[]'])['id[]'] ?? []);
ctx.body = await UserHelpers.getUserRelationhipToMany(ids, ctx.user.id);
}
);
router.get<{ Params: { id: string } }>("/v1/accounts/:id",
auth(false),
async (ctx) => {
const userId = convertId(ctx.params.id, IdType.IceshrimpId);
const account = await UserConverter.encode(await UserHelpers.getUserOr404(userId), ctx);
ctx.body = convertAccountId(account);
ctx.body = await UserConverter.encode(await UserHelpers.getUserOr404(ctx.params.id), ctx);
}
);
router.get<{ Params: { id: string } }>(
"/v1/accounts/:id/statuses",
auth(false, ["read:statuses"]),
async (ctx) => {
const userId = convertId(ctx.params.id, IdType.IceshrimpId);
const query = await UserHelpers.getUserCachedOr404(userId, ctx);
const args = normalizeUrlQuery(convertPaginationArgsIds(argsToBools(limitToInt(ctx.query))));
const query = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
const args = normalizeUrlQuery(argsToBools(limitToInt(ctx.query)));
const res = await UserHelpers.getUserStatuses(query, args.max_id, args.since_id, args.min_id, args.limit, args['only_media'], args['exclude_replies'], args['exclude_reblogs'], args.pinned, args.tagged, ctx);
const tl = await NoteConverter.encodeMany(res, ctx);
ctx.body = tl.map(s => convertStatusIds(s));
ctx.body = await NoteConverter.encodeMany(res, ctx);
},
);
router.get<{ Params: { id: string } }>(
@ -71,72 +59,61 @@ export function setupEndpointsAccount(router: Router): void {
"/v1/accounts/:id/followers",
auth(false),
async (ctx) => {
const userId = convertId(ctx.params.id, IdType.IceshrimpId);
const query = await UserHelpers.getUserCachedOr404(userId, ctx);
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
const query = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
const args = normalizeUrlQuery(limitToInt(ctx.query as any));
const res = await UserHelpers.getUserFollowers(query, args.max_id, args.since_id, args.min_id, args.limit, ctx);
const followers = await UserConverter.encodeMany(res, ctx);
ctx.body = followers.map((account) => convertAccountId(account));
ctx.body = await UserConverter.encodeMany(res, ctx);
},
);
router.get<{ Params: { id: string } }>(
"/v1/accounts/:id/following",
auth(false),
async (ctx) => {
const userId = convertId(ctx.params.id, IdType.IceshrimpId);
const query = await UserHelpers.getUserCachedOr404(userId, ctx);
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
const query = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
const args = normalizeUrlQuery(limitToInt(ctx.query as any));
const res = await UserHelpers.getUserFollowing(query, args.max_id, args.since_id, args.min_id, args.limit, ctx);
const following = await UserConverter.encodeMany(res, ctx);
ctx.body = following.map((account) => convertAccountId(account));
ctx.body = await UserConverter.encodeMany(res, ctx);
},
);
router.get<{ Params: { id: string } }>(
"/v1/accounts/:id/lists",
auth(true, ["read:lists"]),
async (ctx) => {
const member = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
const results = await ListHelpers.getListsByMember(member, ctx);
ctx.body = results.map(p => convertListId(p));
const member = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
ctx.body = await ListHelpers.getListsByMember(member, ctx);
},
);
router.post<{ Params: { id: string } }>(
"/v1/accounts/:id/follow",
auth(true, ["write:follows"]),
async (ctx) => {
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
const target = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
//FIXME: Parse form data
const result = await UserHelpers.followUser(target, true, false, ctx);
ctx.body = convertRelationshipId(result);
ctx.body = await UserHelpers.followUser(target, true, false, ctx);
},
);
router.post<{ Params: { id: string } }>(
"/v1/accounts/:id/unfollow",
auth(true, ["write:follows"]),
async (ctx) => {
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
const result = await UserHelpers.unfollowUser(target, ctx);
ctx.body = convertRelationshipId(result);
const target = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
ctx.body = await UserHelpers.unfollowUser(target, ctx);
},
);
router.post<{ Params: { id: string } }>(
"/v1/accounts/:id/block",
auth(true, ["write:blocks"]),
async (ctx) => {
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
const result = await UserHelpers.blockUser(target, ctx);
ctx.body = convertRelationshipId(result);
const target = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
ctx.body = await UserHelpers.blockUser(target, ctx);
},
);
router.post<{ Params: { id: string } }>(
"/v1/accounts/:id/unblock",
auth(true, ["write:blocks"]),
async (ctx) => {
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
const result = await UserHelpers.unblockUser(target, ctx);
ctx.body = convertRelationshipId(result)
const target = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
ctx.body = await UserHelpers.unblockUser(target, ctx);
},
);
router.post<{ Params: { id: string } }>(
@ -145,18 +122,16 @@ export function setupEndpointsAccount(router: Router): void {
async (ctx) => {
//FIXME: parse form data
const args = normalizeUrlQuery(argsToBools(limitToInt(ctx.query, ['duration']), ['notifications']));
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
const result = await UserHelpers.muteUser(target, args.notifications, args.duration, ctx);
ctx.body = convertRelationshipId(result)
const target = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
ctx.body = await UserHelpers.muteUser(target, args.notifications, args.duration, ctx);
},
);
router.post<{ Params: { id: string } }>(
"/v1/accounts/:id/unmute",
auth(true, ["write:mutes"]),
async (ctx) => {
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
const result = await UserHelpers.unmuteUser(target, ctx);
ctx.body = convertRelationshipId(result)
const target = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
ctx.body = await UserHelpers.unmuteUser(target, ctx);
},
);
router.get("/v1/featured_tags",
@ -172,63 +147,56 @@ export function setupEndpointsAccount(router: Router): void {
router.get("/v1/bookmarks",
auth(true, ["read:bookmarks"]),
async (ctx) => {
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
const args = normalizeUrlQuery(limitToInt(ctx.query as any));
const res = await UserHelpers.getUserBookmarks(args.max_id, args.since_id, args.min_id, args.limit, ctx);
const bookmarks = await NoteConverter.encodeMany(res, ctx);
ctx.body = bookmarks.map(s => convertStatusIds(s));
ctx.body = await NoteConverter.encodeMany(res, ctx);
}
);
router.get("/v1/favourites",
auth(true, ["read:favourites"]),
async (ctx) => {
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
const args = normalizeUrlQuery(limitToInt(ctx.query as any));
const res = await UserHelpers.getUserFavorites(args.max_id, args.since_id, args.min_id, args.limit, ctx);
const favorites = await NoteConverter.encodeMany(res, ctx);
ctx.body = favorites.map(s => convertStatusIds(s));
ctx.body = await NoteConverter.encodeMany(res, ctx);
}
);
router.get("/v1/mutes",
auth(true, ["read:mutes"]),
async (ctx) => {
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
const res = await UserHelpers.getUserMutes(args.max_id, args.since_id, args.min_id, args.limit, ctx);
ctx.body = res.map(m => convertAccountId(m));
const args = normalizeUrlQuery(limitToInt(ctx.query as any));
ctx.body = await UserHelpers.getUserMutes(args.max_id, args.since_id, args.min_id, args.limit, ctx);
}
);
router.get("/v1/blocks",
auth(true, ["read:blocks"]),
async (ctx) => {
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
const args = normalizeUrlQuery(limitToInt(ctx.query as any));
const res = await UserHelpers.getUserBlocks(args.max_id, args.since_id, args.min_id, args.limit, ctx);
const blocks = await UserConverter.encodeMany(res, ctx);
ctx.body = blocks.map(b => convertAccountId(b));
ctx.body = await UserConverter.encodeMany(res, ctx);
}
);
router.get("/v1/follow_requests",
auth(true, ["read:follows"]),
async (ctx) => {
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
const args = normalizeUrlQuery(limitToInt(ctx.query as any));
const res = await UserHelpers.getUserFollowRequests(args.max_id, args.since_id, args.min_id, args.limit, ctx);
const requests = await UserConverter.encodeMany(res, ctx);
ctx.body = requests.map(b => convertAccountId(b));
ctx.body = await UserConverter.encodeMany(res, ctx);
}
);
router.post<{ Params: { id: string } }>(
"/v1/follow_requests/:id/authorize",
auth(true, ["write:follows"]),
async (ctx) => {
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
const result = await UserHelpers.acceptFollowRequest(target, ctx);
ctx.body = convertRelationshipId(result);
const target = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
ctx.body = await UserHelpers.acceptFollowRequest(target, ctx);
},
);
router.post<{ Params: { id: string } }>(
"/v1/follow_requests/:id/reject",
auth(true, ["write:follows"]),
async (ctx) => {
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
const result = await UserHelpers.rejectFollowRequest(target, ctx);
ctx.body = convertRelationshipId(result);
const target = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
ctx.body = await UserHelpers.rejectFollowRequest(target, ctx);
},
);
}

View file

@ -1,6 +1,5 @@
import Router from "@koa/router";
import { AuthHelpers } from "@/server/api/mastodon/helpers/auth.js";
import { convertId, IdType } from "@/misc/convert-id.js";
import { AuthConverter } from "@/server/api/mastodon/converters/auth.js";
import { v4 as uuid } from "uuid";
import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js";
@ -15,7 +14,7 @@ export function setupEndpointsAuth(router: Router): void {
const red = body.redirect_uris;
const appData = await AuthHelpers.registerApp(body['client_name'], scopeArr, red, body['website']);
ctx.body = {
id: convertId(appData.id, IdType.MastodonId),
id: appData.id,
name: appData.name,
website: body.website,
redirect_uri: red,

View file

@ -1,7 +1,5 @@
import Router from "@koa/router";
import { convertAccountId, convertListId, } from "../converters.js";
import { convertId, IdType } from "../../index.js";
import { convertPaginationArgsIds, limitToInt, normalizeUrlQuery } from "@/server/api/mastodon/endpoints/timeline.js";
import { 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 { UserLists } from "@/models/index.js";
@ -14,18 +12,14 @@ export function setupEndpointsList(router: Router): void {
router.get("/v1/lists",
auth(true, ['read:lists']),
async (ctx, reply) => {
ctx.body = await ListHelpers.getLists(ctx)
.then(p => p.map(list => convertListId(list)));
ctx.body = await ListHelpers.getLists(ctx);
}
);
router.get<{ Params: { id: string } }>(
"/v1/lists/:id",
auth(true, ['read:lists']),
async (ctx, reply) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
ctx.body = await ListHelpers.getListOr404(id, ctx)
.then(p => convertListId(p));
ctx.body = await ListHelpers.getListOr404(ctx.params.id, ctx);
},
);
router.post("/v1/lists",
@ -33,31 +27,26 @@ export function setupEndpointsList(router: Router): void {
async (ctx, reply) => {
const body = ctx.request.body as any;
const title = (body.title ?? '').trim();
ctx.body = await ListHelpers.createList(title, ctx)
.then(p => convertListId(p));
ctx.body = await ListHelpers.createList(title, ctx);
}
);
router.put<{ Params: { id: string } }>(
"/v1/lists/:id",
auth(true, ['write:lists']),
async (ctx, reply) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const list = await UserLists.findOneBy({ userId: ctx.user.id, id: id });
const list = await UserLists.findOneBy({ userId: ctx.user.id, id: ctx.params.id });
if (!list) throw new MastoApiError(404);
const body = ctx.request.body as any;
const title = (body.title ?? '').trim();
ctx.body = await ListHelpers.updateList(list, title, ctx)
.then(p => convertListId(p));
ctx.body = await ListHelpers.updateList(list, title, ctx);
},
);
router.delete<{ Params: { id: string } }>(
"/v1/lists/:id",
auth(true, ['write:lists']),
async (ctx, reply) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const list = await UserLists.findOneBy({ userId: ctx.user.id, id: id });
const list = await UserLists.findOneBy({ userId: ctx.user.id, id: ctx.params.id });
if (!list) throw new MastoApiError(404);
await ListHelpers.deleteList(list, ctx);
@ -68,26 +57,22 @@ export function setupEndpointsList(router: Router): void {
"/v1/lists/:id/accounts",
auth(true, ['read:lists']),
async (ctx, reply) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)));
const res = await ListHelpers.getListUsers(id, args.max_id, args.since_id, args.min_id, args.limit, ctx);
const accounts = await UserConverter.encodeMany(res, ctx);
ctx.body = accounts.map(account => convertAccountId(account));
const args = normalizeUrlQuery(limitToInt(ctx.query));
const res = await ListHelpers.getListUsers(ctx.params.id, args.max_id, args.since_id, args.min_id, args.limit, ctx);
ctx.body = await UserConverter.encodeMany(res, ctx);
},
);
router.post<{ Params: { id: string } }>(
"/v1/lists/:id/accounts",
auth(true, ['write:lists']),
async (ctx, reply) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const list = await UserLists.findOneBy({ userId: ctx.user.id, id: id });
const list = await UserLists.findOneBy({ userId: ctx.user.id, id: ctx.params.id });
if (!list) throw new MastoApiError(404);
const body = ctx.request.body as any;
if (!body['account_ids']) throw new MastoApiError(400, "Missing account_ids[] field");
const ids = toArray(body['account_ids']).map(p => convertId(p, IdType.IceshrimpId));
const ids = toArray(body['account_ids']);
const targets = await Promise.all(ids.map(p => getUser(p)));
await ListHelpers.addToList(list, targets, ctx);
ctx.body = {}
@ -97,14 +82,13 @@ export function setupEndpointsList(router: Router): void {
"/v1/lists/:id/accounts",
auth(true, ['write:lists']),
async (ctx, reply) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const list = await UserLists.findOneBy({ userId: ctx.user.id, id: id });
const list = await UserLists.findOneBy({ userId: ctx.user.id, id: ctx.params.id });
if (!list) throw new MastoApiError(404);
const body = ctx.request.body as any;
if (!body['account_ids']) throw new MastoApiError(400, "Missing account_ids[] field");
const ids = toArray(body['account_ids']).map(p => convertId(p, IdType.IceshrimpId));
const ids = toArray(body['account_ids']);
const targets = await Promise.all(ids.map(p => getUser(p)));
await ListHelpers.removeFromList(list, targets, ctx);
ctx.body = {}

View file

@ -1,6 +1,4 @@
import Router from "@koa/router";
import { convertId, IdType } from "@/misc/convert-id.js";
import { convertAttachmentId } from "@/server/api/mastodon/converters.js";
import { MediaHelpers } from "@/server/api/mastodon/helpers/media.js";
import { FileConverter } from "@/server/api/mastodon/converters/file.js";
import { auth } from "@/server/api/mastodon/middleware/auth.js";
@ -9,28 +7,23 @@ export function setupEndpointsMedia(router: Router): void {
router.get<{ Params: { id: string } }>("/v1/media/:id",
auth(true, ['write:media']),
async (ctx) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const file = await MediaHelpers.getMediaPackedOr404(id, ctx);
const attachment = FileConverter.encode(file);
ctx.body = convertAttachmentId(attachment);
const file = await MediaHelpers.getMediaPackedOr404(ctx.params.id, ctx);
ctx.body = FileConverter.encode(file);
}
);
router.put<{ Params: { id: string } }>("/v1/media/:id",
auth(true, ['write:media']),
async (ctx) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const file = await MediaHelpers.getMediaOr404(id, ctx);
const result = await MediaHelpers.updateMedia(file, ctx)
const file = await MediaHelpers.getMediaOr404(ctx.params.id, ctx);
ctx.body = await MediaHelpers.updateMedia(file, ctx)
.then(p => FileConverter.encode(p));
ctx.body = convertAttachmentId(result);
}
);
router.post(["/v2/media", "/v1/media"],
auth(true, ['write:media']),
async (ctx) => {
const result = await MediaHelpers.uploadMedia(ctx)
ctx.body = await MediaHelpers.uploadMedia(ctx)
.then(p => FileConverter.encode(p));
ctx.body = convertAttachmentId(result);
}
);
}

View file

@ -2,8 +2,6 @@ import Router from "@koa/router";
import { MiscHelpers } from "@/server/api/mastodon/helpers/misc.js";
import { argsToBools, limitToInt } from "@/server/api/mastodon/endpoints/timeline.js";
import { Announcements } from "@/models/index.js";
import { convertAnnouncementId, convertStatusIds, convertSuggestionIds } from "@/server/api/mastodon/converters.js";
import { convertId, IdType } from "@/misc/convert-id.js";
import { auth } from "@/server/api/mastodon/middleware/auth.js";
import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js";
@ -24,8 +22,7 @@ export function setupEndpointsMisc(router: Router): void {
auth(true),
async (ctx) => {
const args = argsToBools(ctx.query, ['with_dismissed']);
ctx.body = await MiscHelpers.getAnnouncements(args['with_dismissed'], ctx)
.then(p => p.map(x => convertAnnouncementId(x)));
ctx.body = await MiscHelpers.getAnnouncements(args['with_dismissed'], ctx);
}
);
@ -33,8 +30,7 @@ export function setupEndpointsMisc(router: Router): void {
"/v1/announcements/:id/dismiss",
auth(true, ['write:accounts']),
async (ctx) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const announcement = await Announcements.findOneBy({ id: id });
const announcement = await Announcements.findOneBy({ id: ctx.params.id });
if (!announcement) throw new MastoApiError(404);
await MiscHelpers.dismissAnnouncement(announcement, ctx);
@ -54,8 +50,7 @@ export function setupEndpointsMisc(router: Router): void {
router.get("/v1/trends/statuses",
async (ctx) => {
const args = limitToInt(ctx.query);
ctx.body = await MiscHelpers.getTrendingStatuses(args.limit, args.offset, ctx)
.then(p => p.map(x => convertStatusIds(x)));
ctx.body = await MiscHelpers.getTrendingStatuses(args.limit, args.offset, ctx);
}
);
@ -76,8 +71,7 @@ export function setupEndpointsMisc(router: Router): void {
auth(true, ['read']),
async (ctx) => {
const args = limitToInt(ctx.query);
ctx.body = await MiscHelpers.getFollowSuggestions(args.limit, ctx)
.then(p => p.map(x => convertSuggestionIds(x)));
ctx.body = await MiscHelpers.getFollowSuggestions(args.limit, ctx);
}
);
}

View file

@ -1,7 +1,5 @@
import Router from "@koa/router";
import { convertId, IdType } from "../../index.js";
import { convertPaginationArgsIds, limitToInt, normalizeUrlQuery } from "./timeline.js";
import { convertNotificationIds } from "../converters.js";
import { limitToInt, normalizeUrlQuery } from "./timeline.js";
import { NotificationHelpers } from "@/server/api/mastodon/helpers/notification.js";
import { NotificationConverter } from "@/server/api/mastodon/converters/notification.js";
import { auth } from "@/server/api/mastodon/middleware/auth.js";
@ -10,19 +8,17 @@ export function setupEndpointsNotifications(router: Router): void {
router.get("/v1/notifications",
auth(true, ['read:notifications']),
async (ctx) => {
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)), ['types[]', 'exclude_types[]']);
const args = normalizeUrlQuery(limitToInt(ctx.query), ['types[]', 'exclude_types[]']);
const res = await NotificationHelpers.getNotifications(args.max_id, args.since_id, args.min_id, args.limit, args['types[]'], args['exclude_types[]'], args.account_id, ctx);
const data = await NotificationConverter.encodeMany(res, ctx);
ctx.body = data.map(n => convertNotificationIds(n));
ctx.body = await NotificationConverter.encodeMany(res, ctx);
}
);
router.get("/v1/notifications/:id",
auth(true, ['read:notifications']),
async (ctx) => {
const notification = await NotificationHelpers.getNotificationOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
ctx.body = convertNotificationIds(await NotificationConverter.encode(notification, ctx));
const notification = await NotificationHelpers.getNotificationOr404(ctx.params.id, ctx);
ctx.body = await NotificationConverter.encode(notification, ctx);
}
);
@ -37,7 +33,7 @@ export function setupEndpointsNotifications(router: Router): void {
router.post("/v1/notifications/:id/dismiss",
auth(true, ['write:notifications']),
async (ctx) => {
const notification = await NotificationHelpers.getNotificationOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
const notification = await NotificationHelpers.getNotificationOr404(ctx.params.id, ctx);
await NotificationHelpers.dismissNotification(notification.id, ctx);
ctx.body = {};
}
@ -46,8 +42,7 @@ export function setupEndpointsNotifications(router: Router): void {
router.post("/v1/conversations/:id/read",
auth(true, ['write:conversations']),
async (ctx, reply) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
await NotificationHelpers.markConversationAsRead(id, ctx);
await NotificationHelpers.markConversationAsRead(ctx.params.id, ctx);
ctx.body = {};
}
);

View file

@ -1,6 +1,5 @@
import Router from "@koa/router";
import { argsToBools, convertPaginationArgsIds, limitToInt, normalizeUrlQuery } from "./timeline.js";
import { convertSearchIds } from "../converters.js";
import { argsToBools, limitToInt, normalizeUrlQuery } from "./timeline.js";
import { SearchHelpers } from "@/server/api/mastodon/helpers/search.js";
import { auth } from "@/server/api/mastodon/middleware/auth.js";
@ -8,15 +7,13 @@ export function setupEndpointsSearch(router: Router): void {
router.get(["/v1/search", "/v2/search"],
auth(true, ['read:search']),
async (ctx) => {
const args = normalizeUrlQuery(convertPaginationArgsIds(argsToBools(limitToInt(ctx.query), ['resolve', 'following', 'exclude_unreviewed'])));
const result = await SearchHelpers.search(args.q, args.type, args.resolve, args.following, args.account_id, args['exclude_unreviewed'], args.max_id, args.min_id, args.limit, args.offset, ctx);
ctx.body = convertSearchIds(result);
const args = normalizeUrlQuery(argsToBools(limitToInt(ctx.query), ['resolve', 'following', 'exclude_unreviewed']));
ctx.body = await SearchHelpers.search(args.q, args.type, args.resolve, args.following, args.account_id, args['exclude_unreviewed'], args.max_id, args.min_id, args.limit, args.offset, ctx);
if (ctx.path === "/v1/search") {
ctx.body = {
...ctx.body,
hashtags: result.hashtags.map(p => p.name),
hashtags: ctx.body.hashtags.map((p: MastodonEntity.Tag) => p.name),
};
}
}

View file

@ -1,15 +1,7 @@
import Router from "@koa/router";
import { convertId, IdType } from "../../index.js";
import {
convertAccountId,
convertPollId,
convertStatusEditIds,
convertStatusIds,
convertStatusSourceId,
} from "../converters.js";
import { NoteConverter } from "@/server/api/mastodon/converters/note.js";
import { NoteHelpers } from "@/server/api/mastodon/helpers/note.js";
import { convertPaginationArgsIds, limitToInt, normalizeUrlQuery } from "@/server/api/mastodon/endpoints/timeline.js";
import { limitToInt, normalizeUrlQuery } from "@/server/api/mastodon/endpoints/timeline.js";
import { UserConverter } from "@/server/api/mastodon/converters/user.js";
import { PollHelpers } from "@/server/api/mastodon/helpers/poll.js";
import { toArray } from "@/prelude/array.js";
@ -32,8 +24,7 @@ export function setupEndpointsStatus(router: Router): void {
let request = NoteHelpers.normalizeComposeOptions(ctx.request.body);
ctx.body = await NoteHelpers.createNote(request, ctx)
.then(p => NoteConverter.encode(p, ctx))
.then(p => convertStatusIds(p));
.then(p => NoteConverter.encode(p, ctx));
if (key !== null) NoteHelpers.postIdempotencyCache.set(key, { status: ctx.body });
}
@ -41,31 +32,25 @@ export function setupEndpointsStatus(router: Router): void {
router.put("/v1/statuses/:id",
auth(true, ['write:statuses']),
async (ctx) => {
const noteId = convertId(ctx.params.id, IdType.IceshrimpId);
const note = await NoteHelpers.getNoteOr404(noteId, ctx);
const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
let request = NoteHelpers.normalizeEditOptions(ctx.request.body);
ctx.body = await NoteHelpers.editNote(request, note, ctx)
.then(p => NoteConverter.encode(p, ctx))
.then(p => convertStatusIds(p));
.then(p => NoteConverter.encode(p, ctx));
}
);
router.get<{ Params: { id: string } }>("/v1/statuses/:id",
auth(false, ["read:statuses"]),
async (ctx) => {
const noteId = convertId(ctx.params.id, IdType.IceshrimpId);
const note = await NoteHelpers.getNoteOr404(noteId, ctx);
const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
const status = await NoteConverter.encode(note, ctx);
ctx.body = convertStatusIds(status);
ctx.body = await NoteConverter.encode(note, ctx);
}
);
router.delete<{ Params: { id: string } }>("/v1/statuses/:id",
auth(true, ['write:statuses']),
async (ctx) => {
const noteId = convertId(ctx.params.id, IdType.IceshrimpId);
const note = await NoteHelpers.getNoteOr404(noteId, ctx);
ctx.body = await NoteHelpers.deleteNote(note, ctx)
.then(p => convertStatusIds(p));
const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
ctx.body = await NoteHelpers.deleteNote(note, ctx);
}
);
@ -74,14 +59,11 @@ export function setupEndpointsStatus(router: Router): void {
auth(false, ["read:statuses"]),
async (ctx) => {
//FIXME: determine final limits within helper functions instead of here
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const note = await NoteHelpers.getNoteOr404(id, ctx);
const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
const ancestors = await NoteHelpers.getNoteAncestors(note, ctx.user ? 4096 : 60, ctx)
.then(n => NoteConverter.encodeMany(n, ctx))
.then(n => n.map(s => convertStatusIds(s)));
.then(n => NoteConverter.encodeMany(n, ctx));
const descendants = await NoteHelpers.getNoteDescendants(note, ctx.user ? 4096 : 40, ctx.user ? 4096 : 20, ctx)
.then(n => NoteConverter.encodeMany(n, ctx))
.then(n => n.map(s => convertStatusIds(s)));
.then(n => NoteConverter.encodeMany(n, ctx));
ctx.body = {
ancestors,
@ -93,69 +75,57 @@ export function setupEndpointsStatus(router: Router): void {
"/v1/statuses/:id/history",
auth(false, ["read:statuses"]),
async (ctx) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const note = await NoteHelpers.getNoteOr404(id, ctx);
const res = await NoteHelpers.getNoteEditHistory(note, ctx);
ctx.body = res.map(p => convertStatusEditIds(p));
const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
ctx.body = await NoteHelpers.getNoteEditHistory(note, ctx);
}
);
router.get<{ Params: { id: string } }>(
"/v1/statuses/:id/source",
auth(true, ["read:statuses"]),
async (ctx) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const note = await NoteHelpers.getNoteOr404(id, ctx);
const src = NoteHelpers.getNoteSource(note);
ctx.body = convertStatusSourceId(src);
const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
ctx.body = NoteHelpers.getNoteSource(note);
}
);
router.get<{ Params: { id: string } }>(
"/v1/statuses/:id/reblogged_by",
auth(false, ["read:statuses"]),
async (ctx) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const note = await NoteHelpers.getNoteOr404(id, ctx);
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
const args = normalizeUrlQuery(limitToInt(ctx.query as any));
const res = await NoteHelpers.getNoteRebloggedBy(note, args.max_id, args.since_id, args.min_id, args.limit, ctx);
const users = await UserConverter.encodeMany(res, ctx);
ctx.body = users.map(m => convertAccountId(m));
ctx.body = await UserConverter.encodeMany(res, ctx);
}
);
router.get<{ Params: { id: string } }>(
"/v1/statuses/:id/favourited_by",
auth(false, ["read:statuses"]),
async (ctx) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const note = await NoteHelpers.getNoteOr404(id, ctx);
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
const args = normalizeUrlQuery(limitToInt(ctx.query as any));
const res = await NoteHelpers.getNoteFavoritedBy(note, args.max_id, args.since_id, args.min_id, args.limit, ctx);
const users = await UserConverter.encodeMany(res, ctx);
ctx.body = users.map(m => convertAccountId(m));
ctx.body = await UserConverter.encodeMany(res, ctx);
}
);
router.post<{ Params: { id: string } }>(
"/v1/statuses/:id/favourite",
auth(true, ["write:favourites"]),
async (ctx) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const note = await NoteHelpers.getNoteOr404(id, ctx);
const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
const reaction = await NoteHelpers.getDefaultReaction();
ctx.body = await NoteHelpers.reactToNote(note, reaction, ctx)
.then(p => NoteConverter.encode(p, ctx))
.then(p => convertStatusIds(p));
.then(p => NoteConverter.encode(p, ctx));
}
);
router.post<{ Params: { id: string } }>(
"/v1/statuses/:id/unfavourite",
auth(true, ["write:favourites"]),
async (ctx) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const note = await NoteHelpers.getNoteOr404(id, ctx);
const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
ctx.body = await NoteHelpers.removeReactFromNote(note, ctx)
.then(p => NoteConverter.encode(p, ctx))
.then(p => convertStatusIds(p));
.then(p => NoteConverter.encode(p, ctx));
},
);
@ -163,12 +133,10 @@ export function setupEndpointsStatus(router: Router): void {
"/v1/statuses/:id/reblog",
auth(true, ["write:statuses"]),
async (ctx) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const note = await NoteHelpers.getNoteOr404(id, ctx);
const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
ctx.body = await NoteHelpers.reblogNote(note, ctx)
.then(p => NoteConverter.encode(p, ctx))
.then(p => convertStatusIds(p));
.then(p => NoteConverter.encode(p, ctx));
},
);
@ -176,12 +144,10 @@ export function setupEndpointsStatus(router: Router): void {
"/v1/statuses/:id/unreblog",
auth(true, ["write:statuses"]),
async (ctx) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const note = await NoteHelpers.getNoteOr404(id, ctx);
const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
ctx.body = await NoteHelpers.unreblogNote(note, ctx)
.then(p => NoteConverter.encode(p, ctx))
.then(p => convertStatusIds(p));
.then(p => NoteConverter.encode(p, ctx));
},
);
@ -189,12 +155,10 @@ export function setupEndpointsStatus(router: Router): void {
"/v1/statuses/:id/bookmark",
auth(true, ["write:bookmarks"]),
async (ctx) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const note = await NoteHelpers.getNoteOr404(id, ctx);
const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
ctx.body = await NoteHelpers.bookmarkNote(note, ctx)
.then(p => NoteConverter.encode(p, ctx))
.then(p => convertStatusIds(p));
.then(p => NoteConverter.encode(p, ctx));
},
);
@ -202,12 +166,10 @@ export function setupEndpointsStatus(router: Router): void {
"/v1/statuses/:id/unbookmark",
auth(true, ["write:bookmarks"]),
async (ctx) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const note = await NoteHelpers.getNoteOr404(id, ctx);
const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
ctx.body = await NoteHelpers.unbookmarkNote(note, ctx)
.then(p => NoteConverter.encode(p, ctx))
.then(p => convertStatusIds(p));
.then(p => NoteConverter.encode(p, ctx));
},
);
@ -215,12 +177,10 @@ export function setupEndpointsStatus(router: Router): void {
"/v1/statuses/:id/pin",
auth(true, ["write:accounts"]),
async (ctx) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const note = await NoteHelpers.getNoteOr404(id, ctx);
const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
ctx.body = await NoteHelpers.pinNote(note, ctx)
.then(p => NoteConverter.encode(p, ctx))
.then(p => convertStatusIds(p));
.then(p => NoteConverter.encode(p, ctx));
},
);
@ -228,12 +188,10 @@ export function setupEndpointsStatus(router: Router): void {
"/v1/statuses/:id/unpin",
auth(true, ["write:accounts"]),
async (ctx) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const note = await NoteHelpers.getNoteOr404(id, ctx);
const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
ctx.body = await NoteHelpers.unpinNote(note, ctx)
.then(p => NoteConverter.encode(p, ctx))
.then(p => convertStatusIds(p));
.then(p => NoteConverter.encode(p, ctx));
},
);
@ -241,12 +199,10 @@ export function setupEndpointsStatus(router: Router): void {
"/v1/statuses/:id/react/:name",
auth(true, ["write:favourites"]),
async (ctx) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const note = await NoteHelpers.getNoteOr404(id, ctx);
const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
ctx.body = await NoteHelpers.reactToNote(note, ctx.params.name, ctx)
.then(p => NoteConverter.encode(p, ctx))
.then(p => convertStatusIds(p));
.then(p => NoteConverter.encode(p, ctx));
},
);
@ -254,35 +210,29 @@ export function setupEndpointsStatus(router: Router): void {
"/v1/statuses/:id/unreact/:name",
auth(true, ["write:favourites"]),
async (ctx) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const note = await NoteHelpers.getNoteOr404(id, ctx);
const note = await NoteHelpers.getNoteOr404(ctx.params.name, ctx);
ctx.body = await NoteHelpers.removeReactFromNote(note, ctx)
.then(p => NoteConverter.encode(p, ctx))
.then(p => convertStatusIds(p));
.then(p => NoteConverter.encode(p, ctx));
},
);
router.get<{ Params: { id: string } }>("/v1/polls/:id",
auth(false, ["read:statuses"]),
async (ctx) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const note = await NoteHelpers.getNoteOr404(id, ctx);
const data = await PollHelpers.getPoll(note, ctx);
ctx.body = convertPollId(data);
const note = await NoteHelpers.getNoteOr404(ctx.params.name, ctx);
ctx.body = await PollHelpers.getPoll(note, ctx);
});
router.post<{ Params: { id: string } }>(
"/v1/polls/:id/votes",
auth(true, ["write:statuses"]),
async (ctx) => {
const id = convertId(ctx.params.id, IdType.IceshrimpId);
const note = await NoteHelpers.getNoteOr404(id, ctx);
const note = await NoteHelpers.getNoteOr404(ctx.params.name, ctx);
const body: any = ctx.request.body;
const choices = toArray(body.choices ?? []).map(p => parseInt(p));
if (choices.length < 1) throw new MastoApiError(400, "Must vote for at least one option");
const data = await PollHelpers.voteInPoll(choices, note, ctx);
ctx.body = convertPollId(data);
ctx.body = await PollHelpers.voteInPoll(choices, note, ctx);
},
);
}

View file

@ -1,7 +1,5 @@
import Router from "@koa/router";
import { ParsedUrlQuery } from "querystring";
import { convertConversationIds, convertStatusIds, } from "../converters.js";
import { convertId, IdType } from "../../index.js";
import { TimelineHelpers } from "@/server/api/mastodon/helpers/timeline.js";
import { NoteConverter } from "@/server/api/mastodon/converters/note.js";
import { UserLists } from "@/models/index.js";
@ -39,16 +37,6 @@ export function argsToBools(q: ParsedUrlQuery, additional: string[] = []) {
return object;
}
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")
q.max_id = convertId(q.max_id, IdType.IceshrimpId);
if (typeof q.since_id === "string")
q.since_id = convertId(q.since_id, IdType.IceshrimpId);
return q;
}
export function normalizeUrlQuery(q: ParsedUrlQuery, arrayKeys: string[] = []): any {
const dict: any = {};
@ -66,55 +54,44 @@ export function setupEndpointsTimeline(router: Router): void {
router.get("/v1/timelines/public",
auth(true, ['read:statuses']),
async (ctx, reply) => {
const args = normalizeUrlQuery(convertPaginationArgsIds(argsToBools(limitToInt(ctx.query))));
const args = normalizeUrlQuery(argsToBools(limitToInt(ctx.query)));
const res = await TimelineHelpers.getPublicTimeline(args.max_id, args.since_id, args.min_id, args.limit, args.only_media, args.local, args.remote, ctx);
const tl = await NoteConverter.encodeMany(res, ctx);
ctx.body = tl.map(s => convertStatusIds(s));
ctx.body = await NoteConverter.encodeMany(res, ctx);
});
router.get<{ Params: { hashtag: string } }>(
"/v1/timelines/tag/:hashtag",
auth(false, ['read:statuses']),
async (ctx, reply) => {
const tag = (ctx.params.hashtag ?? '').trim();
const args = normalizeUrlQuery(convertPaginationArgsIds(argsToBools(limitToInt(ctx.query))), ['any[]', 'all[]', 'none[]']);
const args = normalizeUrlQuery(argsToBools(limitToInt(ctx.query)), ['any[]', 'all[]', 'none[]']);
const res = await TimelineHelpers.getTagTimeline(tag, args.max_id, args.since_id, args.min_id, args.limit, args['any[]'] ?? [], args['all[]'] ?? [], args['none[]'] ?? [], args.only_media, args.local, args.remote, ctx);
const tl = await NoteConverter.encodeMany(res, ctx);
ctx.body = tl.map(s => convertStatusIds(s));
ctx.body = await NoteConverter.encodeMany(res, ctx);
},
);
router.get("/v1/timelines/home",
auth(true, ['read:statuses']),
async (ctx, reply) => {
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)));
const args = normalizeUrlQuery(limitToInt(ctx.query));
const res = await TimelineHelpers.getHomeTimeline(args.max_id, args.since_id, args.min_id, args.limit, ctx);
const tl = await NoteConverter.encodeMany(res, ctx);
ctx.body = tl.map(s => convertStatusIds(s));
ctx.body = await NoteConverter.encodeMany(res, ctx);
});
router.get<{ Params: { listId: string } }>(
"/v1/timelines/list/:listId",
auth(true, ['read:lists']),
async (ctx, reply) => {
const listId = convertId(ctx.params.listId, IdType.IceshrimpId);
const list = await UserLists.findOneBy({ userId: ctx.user.id, id: listId });
const list = await UserLists.findOneBy({ userId: ctx.user.id, id: ctx.params.listId });
if (!list) throw new MastoApiError(404);
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)));
const args = normalizeUrlQuery(limitToInt(ctx.query));
const res = await TimelineHelpers.getListTimeline(list, args.max_id, args.since_id, args.min_id, args.limit, ctx);
const tl = await NoteConverter.encodeMany(res, ctx);
ctx.body = tl.map(s => convertStatusIds(s));
ctx.body = await NoteConverter.encodeMany(res, ctx);
},
);
router.get("/v1/conversations",
auth(true, ['read:statuses']),
async (ctx, reply) => {
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)));
const res = await TimelineHelpers.getConversations(args.max_id, args.since_id, args.min_id, args.limit, ctx);
ctx.body = res.map(c => convertConversationIds(c));
const args = normalizeUrlQuery(limitToInt(ctx.query));
ctx.body = await TimelineHelpers.getConversations(args.max_id, args.since_id, args.min_id, args.limit, ctx);
}
);
}

View file

@ -5,7 +5,6 @@ import { AnnouncementReads, Announcements, Emojis, Instances, Notes, UserProfile
import { IsNull } from "typeorm";
import { awaitAll } from "@/prelude/await-all.js";
import { UserConverter } from "@/server/api/mastodon/converters/user.js";
import { convertAccountId } from "@/server/api/mastodon/converters.js";
import { Announcement } from "@/models/entities/announcement.js";
import { ILocalUser, User } from "@/models/entities/user.js";
import { AnnouncementConverter } from "@/server/api/mastodon/converters/announcement.js";
@ -35,8 +34,7 @@ export class MiscHelpers {
},
order: { id: "ASC" },
})
.then(p => p ? UserConverter.encode(p, ctx) : null)
.then(p => p ? convertAccountId(p) : null);
.then(p => p ? UserConverter.encode(p, ctx) : null);
const meta = await fetchMeta(true);
const res = {

View file

@ -18,7 +18,6 @@ import { UserHelpers } from "@/server/api/mastodon/helpers/user.js";
import { generatePaginationData } from "@/server/api/mastodon/middleware/pagination.js"
import { addPinned, removePinned } from "@/services/i/pin.js";
import { NoteConverter } from "@/server/api/mastodon/converters/note.js";
import { convertId, IdType } from "@/misc/convert-id.js";
import { awaitAll } from "@/prelude/await-all.js";
import { VisibilityConverter } from "@/server/api/mastodon/converters/visibility.js";
import mfm from "mfm-js";
@ -360,11 +359,10 @@ export class NoteHelpers {
if (body.scheduled_at != null)
result.scheduled_at = new Date(Date.parse(body.scheduled_at));
if (body.in_reply_to_id)
result.in_reply_to_id = convertId(body.in_reply_to_id, IdType.IceshrimpId);
result.in_reply_to_id = body.in_reply_to_id;
if (body.media_ids)
result.media_ids = body.media_ids && body.media_ids.length > 0
? toArray(body.media_ids)
.map(p => convertId(p, IdType.IceshrimpId))
: undefined;
if (body.poll) {
@ -392,7 +390,6 @@ export class NoteHelpers {
if (body.media_ids)
result.media_ids = body.media_ids && body.media_ids.length > 0
? toArray(body.media_ids)
.map(p => convertId(p, IdType.IceshrimpId))
: undefined;
if (body.poll) {

View file

@ -1,6 +1,5 @@
import { MastoContext } from "@/server/api/mastodon/index.js";
import config from "@/config/index.js";
import { convertId, IdType } from "@/misc/convert-id.js";
type PaginationData = {
limit: number;
@ -15,11 +14,11 @@ export async function PaginationMiddleware(ctx: MastoContext, next: () => Promis
const link: string[] = [];
const limit = ctx.pagination.limit;
if (ctx.pagination.maxId) {
const l = `<${config.url}/api${ctx.path}?limit=${limit}&max_id=${convertId(ctx.pagination.maxId, IdType.MastodonId)}>; rel="next"`;
const l = `<${config.url}/api${ctx.path}?limit=${limit}&max_id=${ctx.pagination.maxId}>; rel="next"`;
link.push(l);
}
if (ctx.pagination.minId) {
const l = `<${config.url}/api${ctx.path}?limit=${limit}&min_id=${convertId(ctx.pagination.maxId, IdType.MastodonId)}>; rel="prev"`;
const l = `<${config.url}/api${ctx.path}?limit=${limit}&min_id=${ctx.pagination.maxId}>; rel="prev"`;
link.push(l);
}
if (link.length > 0) {