mirror of
https://iceshrimp.dev/limepotato/jormungandr-bite.git
synced 2024-11-25 11:27:31 -07:00
[mastodon-client] Use ctx instead of ctx.user as arguments everywhere
This commit is contained in:
parent
79c3e56989
commit
cc96b0ba72
19 changed files with 312 additions and 229 deletions
|
@ -21,7 +21,8 @@ import { MfmHelpers } from "@/server/api/mastodon/helpers/mfm.js";
|
||||||
import { MastoContext } from "@/server/api/mastodon/index.js";
|
import { MastoContext } from "@/server/api/mastodon/index.js";
|
||||||
|
|
||||||
export class NoteConverter {
|
export class NoteConverter {
|
||||||
public static async encode(note: Note, user: ILocalUser | null, ctx: MastoContext, recurse: boolean = true): Promise<MastodonEntity.Status> {
|
public static async encode(note: Note, ctx: MastoContext, recurse: boolean = true): Promise<MastodonEntity.Status> {
|
||||||
|
const user = ctx.user as ILocalUser | null;
|
||||||
const noteUser = note.user ?? UserHelpers.getUserCached(note.userId, ctx);
|
const noteUser = note.user ?? UserHelpers.getUserCached(note.userId, ctx);
|
||||||
|
|
||||||
if (!await Notes.isVisibleForMe(note, user?.id ?? null))
|
if (!await Notes.isVisibleForMe(note, user?.id ?? null))
|
||||||
|
@ -109,7 +110,7 @@ export class NoteConverter {
|
||||||
account: Promise.resolve(noteUser).then(p => UserConverter.encode(p, ctx)),
|
account: Promise.resolve(noteUser).then(p => UserConverter.encode(p, ctx)),
|
||||||
in_reply_to_id: note.replyId,
|
in_reply_to_id: note.replyId,
|
||||||
in_reply_to_account_id: note.replyUserId,
|
in_reply_to_account_id: note.replyUserId,
|
||||||
reblog: Promise.resolve(renote).then(renote => recurse && renote && note.text === null ? this.encode(renote, user, ctx, false) : null),
|
reblog: Promise.resolve(renote).then(renote => recurse && renote && note.text === null ? this.encode(renote, ctx, false) : null),
|
||||||
content: text.then(text => text !== null ? MfmHelpers.toHtml(mfm.parse(text), JSON.parse(note.mentionedRemoteUsers)) ?? escapeMFM(text) : ""),
|
content: text.then(text => text !== null ? MfmHelpers.toHtml(mfm.parse(text), JSON.parse(note.mentionedRemoteUsers)) ?? escapeMFM(text) : ""),
|
||||||
text: text,
|
text: text,
|
||||||
created_at: note.createdAt.toISOString(),
|
created_at: note.createdAt.toISOString(),
|
||||||
|
@ -135,13 +136,13 @@ export class NoteConverter {
|
||||||
// Use emojis list to provide URLs for emoji reactions.
|
// Use emojis list to provide URLs for emoji reactions.
|
||||||
reactions: [], //FIXME: this.mapReactions(n.emojis, n.reactions, n.myReaction),
|
reactions: [], //FIXME: this.mapReactions(n.emojis, n.reactions, n.myReaction),
|
||||||
bookmarked: isBookmarked,
|
bookmarked: isBookmarked,
|
||||||
quote: Promise.resolve(renote).then(renote => recurse && renote && note.text !== null ? this.encode(renote, user, ctx, false) : null),
|
quote: Promise.resolve(renote).then(renote => recurse && renote && note.text !== null ? this.encode(renote, ctx, false) : null),
|
||||||
edited_at: note.updatedAt?.toISOString()
|
edited_at: note.updatedAt?.toISOString()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async encodeMany(notes: Note[], user: ILocalUser | null, ctx: MastoContext): Promise<MastodonEntity.Status[]> {
|
public static async encodeMany(notes: Note[], ctx: MastoContext): Promise<MastodonEntity.Status[]> {
|
||||||
const encoded = notes.map(n => this.encode(n, user, ctx));
|
const encoded = notes.map(n => this.encode(n, ctx));
|
||||||
return Promise.all(encoded);
|
return Promise.all(encoded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,8 @@ import { MastoContext } from "@/server/api/mastodon/index.js";
|
||||||
type NotificationType = typeof notificationTypes[number];
|
type NotificationType = typeof notificationTypes[number];
|
||||||
|
|
||||||
export class NotificationConverter {
|
export class NotificationConverter {
|
||||||
public static async encode(notification: Notification, localUser: ILocalUser, ctx: MastoContext): Promise<MastodonEntity.Notification> {
|
public static async encode(notification: Notification, ctx: MastoContext): Promise<MastodonEntity.Notification> {
|
||||||
|
const localUser = ctx.user as ILocalUser;
|
||||||
if (notification.notifieeId !== localUser.id) throw new Error('User is not recipient of notification');
|
if (notification.notifieeId !== localUser.id) throw new Error('User is not recipient of notification');
|
||||||
|
|
||||||
const account = notification.notifierId
|
const account = notification.notifierId
|
||||||
|
@ -28,8 +29,8 @@ export class NotificationConverter {
|
||||||
if (notification.note) {
|
if (notification.note) {
|
||||||
const isPureRenote = notification.note.renoteId !== null && notification.note.text === null;
|
const isPureRenote = notification.note.renoteId !== null && notification.note.text === null;
|
||||||
const encodedNote = isPureRenote
|
const encodedNote = isPureRenote
|
||||||
? getNote(notification.note.renoteId!, localUser).then(note => NoteConverter.encode(note, localUser, ctx))
|
? getNote(notification.note.renoteId!, localUser).then(note => NoteConverter.encode(note, ctx))
|
||||||
: NoteConverter.encode(notification.note, localUser, ctx);
|
: NoteConverter.encode(notification.note, ctx);
|
||||||
result = Object.assign(result, {
|
result = Object.assign(result, {
|
||||||
status: encodedNote,
|
status: encodedNote,
|
||||||
});
|
});
|
||||||
|
@ -45,8 +46,8 @@ export class NotificationConverter {
|
||||||
return awaitAll(result);
|
return awaitAll(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async encodeMany(notifications: Notification[], localUser: ILocalUser, ctx: MastoContext): Promise<MastodonEntity.Notification[]> {
|
public static async encodeMany(notifications: Notification[], ctx: MastoContext): Promise<MastodonEntity.Notification[]> {
|
||||||
const encoded = notifications.map(u => this.encode(u, localUser, ctx));
|
const encoded = notifications.map(u => this.encode(u, ctx));
|
||||||
return Promise.all(encoded)
|
return Promise.all(encoded)
|
||||||
.then(p => p.filter(n => n !== null) as MastodonEntity.Notification[]);
|
.then(p => p.filter(n => n !== null) as MastodonEntity.Notification[]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,22 +6,20 @@ import { UserConverter } from "@/server/api/mastodon/converters/user.js";
|
||||||
import { NoteConverter } from "@/server/api/mastodon/converters/note.js";
|
import { NoteConverter } from "@/server/api/mastodon/converters/note.js";
|
||||||
import { UserHelpers } from "@/server/api/mastodon/helpers/user.js";
|
import { UserHelpers } from "@/server/api/mastodon/helpers/user.js";
|
||||||
import { ListHelpers } from "@/server/api/mastodon/helpers/list.js";
|
import { ListHelpers } from "@/server/api/mastodon/helpers/list.js";
|
||||||
import { Files } from "formidable";
|
|
||||||
import { auth } from "@/server/api/mastodon/middleware/auth.js";
|
import { auth } from "@/server/api/mastodon/middleware/auth.js";
|
||||||
|
|
||||||
export function setupEndpointsAccount(router: Router): void {
|
export function setupEndpointsAccount(router: Router): void {
|
||||||
router.get("/v1/accounts/verify_credentials",
|
router.get("/v1/accounts/verify_credentials",
|
||||||
auth(true, ['read:accounts']),
|
auth(true, ['read:accounts']),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const acct = await UserHelpers.verifyCredentials(ctx.user, ctx);
|
const acct = await UserHelpers.verifyCredentials(ctx);
|
||||||
ctx.body = convertAccountId(acct);
|
ctx.body = convertAccountId(acct);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
router.patch("/v1/accounts/update_credentials",
|
router.patch("/v1/accounts/update_credentials",
|
||||||
auth(true, ['write:accounts']),
|
auth(true, ['write:accounts']),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const files = (ctx.request as any).files as Files | undefined;
|
const acct = await UserHelpers.updateCredentials(ctx);
|
||||||
const acct = await UserHelpers.updateCredentials(ctx.user, (ctx.request as any).body as any, files, ctx);
|
|
||||||
ctx.body = convertAccountId(acct)
|
ctx.body = convertAccountId(acct)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -57,8 +55,8 @@ export function setupEndpointsAccount(router: Router): void {
|
||||||
const userId = convertId(ctx.params.id, IdType.IceshrimpId);
|
const userId = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const query = await UserHelpers.getUserCachedOr404(userId, ctx);
|
const query = await UserHelpers.getUserCachedOr404(userId, ctx);
|
||||||
const args = normalizeUrlQuery(convertPaginationArgsIds(argsToBools(limitToInt(ctx.query))));
|
const args = normalizeUrlQuery(convertPaginationArgsIds(argsToBools(limitToInt(ctx.query))));
|
||||||
const res = await UserHelpers.getUserStatuses(query, ctx.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);
|
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.data, ctx.user, ctx);
|
const tl = await NoteConverter.encodeMany(res.data, ctx);
|
||||||
|
|
||||||
ctx.body = tl.map(s => convertStatusIds(s));
|
ctx.body = tl.map(s => convertStatusIds(s));
|
||||||
ctx.pagination = res.pagination;
|
ctx.pagination = res.pagination;
|
||||||
|
@ -77,7 +75,7 @@ export function setupEndpointsAccount(router: Router): void {
|
||||||
const userId = convertId(ctx.params.id, IdType.IceshrimpId);
|
const userId = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const query = await UserHelpers.getUserCachedOr404(userId, ctx);
|
const query = await UserHelpers.getUserCachedOr404(userId, ctx);
|
||||||
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
|
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
|
||||||
const res = await UserHelpers.getUserFollowers(query, ctx.user, args.max_id, args.since_id, args.min_id, args.limit);
|
const res = await UserHelpers.getUserFollowers(query, args.max_id, args.since_id, args.min_id, args.limit, ctx);
|
||||||
const followers = await UserConverter.encodeMany(res.data, ctx);
|
const followers = await UserConverter.encodeMany(res.data, ctx);
|
||||||
|
|
||||||
ctx.body = followers.map((account) => convertAccountId(account));
|
ctx.body = followers.map((account) => convertAccountId(account));
|
||||||
|
@ -91,7 +89,7 @@ export function setupEndpointsAccount(router: Router): void {
|
||||||
const userId = convertId(ctx.params.id, IdType.IceshrimpId);
|
const userId = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const query = await UserHelpers.getUserCachedOr404(userId, ctx);
|
const query = await UserHelpers.getUserCachedOr404(userId, ctx);
|
||||||
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
|
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
|
||||||
const res = await UserHelpers.getUserFollowing(query, ctx.user, args.max_id, args.since_id, args.min_id, args.limit);
|
const res = await UserHelpers.getUserFollowing(query, args.max_id, args.since_id, args.min_id, args.limit, ctx);
|
||||||
const following = await UserConverter.encodeMany(res.data, ctx);
|
const following = await UserConverter.encodeMany(res.data, ctx);
|
||||||
|
|
||||||
ctx.body = following.map((account) => convertAccountId(account));
|
ctx.body = following.map((account) => convertAccountId(account));
|
||||||
|
@ -103,7 +101,7 @@ export function setupEndpointsAccount(router: Router): void {
|
||||||
auth(true, ["read:lists"]),
|
auth(true, ["read:lists"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const member = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
|
const member = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
|
||||||
const results = await ListHelpers.getListsByMember(ctx.user, member);
|
const results = await ListHelpers.getListsByMember(member, ctx);
|
||||||
ctx.body = results.map(p => convertListId(p));
|
ctx.body = results.map(p => convertListId(p));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -113,7 +111,7 @@ export function setupEndpointsAccount(router: Router): void {
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
|
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
|
||||||
//FIXME: Parse form data
|
//FIXME: Parse form data
|
||||||
const result = await UserHelpers.followUser(target, ctx.user, true, false);
|
const result = await UserHelpers.followUser(target, true, false, ctx);
|
||||||
ctx.body = convertRelationshipId(result);
|
ctx.body = convertRelationshipId(result);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -122,7 +120,7 @@ export function setupEndpointsAccount(router: Router): void {
|
||||||
auth(true, ["write:follows"]),
|
auth(true, ["write:follows"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
|
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
|
||||||
const result = await UserHelpers.unfollowUser(target, ctx.user);
|
const result = await UserHelpers.unfollowUser(target, ctx);
|
||||||
ctx.body = convertRelationshipId(result);
|
ctx.body = convertRelationshipId(result);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -131,7 +129,7 @@ export function setupEndpointsAccount(router: Router): void {
|
||||||
auth(true, ["write:blocks"]),
|
auth(true, ["write:blocks"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
|
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
|
||||||
const result = await UserHelpers.blockUser(target, ctx.user);
|
const result = await UserHelpers.blockUser(target, ctx);
|
||||||
ctx.body = convertRelationshipId(result);
|
ctx.body = convertRelationshipId(result);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -140,7 +138,7 @@ export function setupEndpointsAccount(router: Router): void {
|
||||||
auth(true, ["write:blocks"]),
|
auth(true, ["write:blocks"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
|
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
|
||||||
const result = await UserHelpers.unblockUser(target, ctx.user);
|
const result = await UserHelpers.unblockUser(target, ctx);
|
||||||
ctx.body = convertRelationshipId(result)
|
ctx.body = convertRelationshipId(result)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -151,7 +149,7 @@ export function setupEndpointsAccount(router: Router): void {
|
||||||
//FIXME: parse form data
|
//FIXME: parse form data
|
||||||
const args = normalizeUrlQuery(argsToBools(limitToInt(ctx.query, ['duration']), ['notifications']));
|
const args = normalizeUrlQuery(argsToBools(limitToInt(ctx.query, ['duration']), ['notifications']));
|
||||||
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
|
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
|
||||||
const result = await UserHelpers.muteUser(target, ctx.user, args.notifications, args.duration);
|
const result = await UserHelpers.muteUser(target, args.notifications, args.duration, ctx);
|
||||||
ctx.body = convertRelationshipId(result)
|
ctx.body = convertRelationshipId(result)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -160,7 +158,7 @@ export function setupEndpointsAccount(router: Router): void {
|
||||||
auth(true, ["write:mutes"]),
|
auth(true, ["write:mutes"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
|
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
|
||||||
const result = await UserHelpers.unmuteUser(target, ctx.user);
|
const result = await UserHelpers.unmuteUser(target, ctx);
|
||||||
ctx.body = convertRelationshipId(result)
|
ctx.body = convertRelationshipId(result)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -178,8 +176,8 @@ export function setupEndpointsAccount(router: Router): void {
|
||||||
auth(true, ["read:bookmarks"]),
|
auth(true, ["read:bookmarks"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
|
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
|
||||||
const res = await UserHelpers.getUserBookmarks(ctx.user, args.max_id, args.since_id, args.min_id, args.limit);
|
const res = await UserHelpers.getUserBookmarks(args.max_id, args.since_id, args.min_id, args.limit, ctx);
|
||||||
const bookmarks = await NoteConverter.encodeMany(res.data, ctx.user, ctx);
|
const bookmarks = await NoteConverter.encodeMany(res.data, ctx);
|
||||||
ctx.body = bookmarks.map(s => convertStatusIds(s));
|
ctx.body = bookmarks.map(s => convertStatusIds(s));
|
||||||
ctx.pagination = res.pagination;
|
ctx.pagination = res.pagination;
|
||||||
}
|
}
|
||||||
|
@ -188,8 +186,8 @@ export function setupEndpointsAccount(router: Router): void {
|
||||||
auth(true, ["read:favourites"]),
|
auth(true, ["read:favourites"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
|
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
|
||||||
const res = await UserHelpers.getUserFavorites(ctx.user, args.max_id, args.since_id, args.min_id, args.limit);
|
const res = await UserHelpers.getUserFavorites(args.max_id, args.since_id, args.min_id, args.limit, ctx);
|
||||||
const favorites = await NoteConverter.encodeMany(res.data, ctx.user, ctx);
|
const favorites = await NoteConverter.encodeMany(res.data, ctx);
|
||||||
ctx.body = favorites.map(s => convertStatusIds(s));
|
ctx.body = favorites.map(s => convertStatusIds(s));
|
||||||
ctx.pagination = res.pagination;
|
ctx.pagination = res.pagination;
|
||||||
}
|
}
|
||||||
|
@ -198,7 +196,7 @@ export function setupEndpointsAccount(router: Router): void {
|
||||||
auth(true, ["read:mutes"]),
|
auth(true, ["read:mutes"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
|
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
|
||||||
const res = await UserHelpers.getUserMutes(ctx.user, args.max_id, args.since_id, args.min_id, args.limit, ctx);
|
const res = await UserHelpers.getUserMutes(args.max_id, args.since_id, args.min_id, args.limit, ctx);
|
||||||
ctx.body = res.data.map(m => convertAccountId(m));
|
ctx.body = res.data.map(m => convertAccountId(m));
|
||||||
ctx.pagination = res.pagination;
|
ctx.pagination = res.pagination;
|
||||||
}
|
}
|
||||||
|
@ -207,7 +205,7 @@ export function setupEndpointsAccount(router: Router): void {
|
||||||
auth(true, ["read:blocks"]),
|
auth(true, ["read:blocks"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
|
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
|
||||||
const res = await UserHelpers.getUserBlocks(ctx.user, args.max_id, args.since_id, args.min_id, args.limit);
|
const res = await UserHelpers.getUserBlocks(args.max_id, args.since_id, args.min_id, args.limit, ctx);
|
||||||
const blocks = await UserConverter.encodeMany(res.data, ctx);
|
const blocks = await UserConverter.encodeMany(res.data, ctx);
|
||||||
ctx.body = blocks.map(b => convertAccountId(b));
|
ctx.body = blocks.map(b => convertAccountId(b));
|
||||||
ctx.pagination = res.pagination;
|
ctx.pagination = res.pagination;
|
||||||
|
@ -217,7 +215,7 @@ export function setupEndpointsAccount(router: Router): void {
|
||||||
auth(true, ["read:follows"]),
|
auth(true, ["read:follows"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
|
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
|
||||||
const res = await UserHelpers.getUserFollowRequests(ctx.user, args.max_id, args.since_id, args.min_id, args.limit);
|
const res = await UserHelpers.getUserFollowRequests(args.max_id, args.since_id, args.min_id, args.limit, ctx);
|
||||||
const requests = await UserConverter.encodeMany(res.data, ctx);
|
const requests = await UserConverter.encodeMany(res.data, ctx);
|
||||||
ctx.body = requests.map(b => convertAccountId(b));
|
ctx.body = requests.map(b => convertAccountId(b));
|
||||||
ctx.pagination = res.pagination;
|
ctx.pagination = res.pagination;
|
||||||
|
@ -228,7 +226,7 @@ export function setupEndpointsAccount(router: Router): void {
|
||||||
auth(true, ["write:follows"]),
|
auth(true, ["write:follows"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
|
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
|
||||||
const result = await UserHelpers.acceptFollowRequest(target, ctx.user);
|
const result = await UserHelpers.acceptFollowRequest(target, ctx);
|
||||||
ctx.body = convertRelationshipId(result);
|
ctx.body = convertRelationshipId(result);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -237,7 +235,7 @@ export function setupEndpointsAccount(router: Router): void {
|
||||||
auth(true, ["write:follows"]),
|
auth(true, ["write:follows"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
|
const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
|
||||||
const result = await UserHelpers.rejectFollowRequest(target, ctx.user);
|
const result = await UserHelpers.rejectFollowRequest(target, ctx);
|
||||||
ctx.body = convertRelationshipId(result);
|
ctx.body = convertRelationshipId(result);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,7 +14,7 @@ export function setupEndpointsList(router: Router): void {
|
||||||
router.get("/v1/lists",
|
router.get("/v1/lists",
|
||||||
auth(true, ['read:lists']),
|
auth(true, ['read:lists']),
|
||||||
async (ctx, reply) => {
|
async (ctx, reply) => {
|
||||||
ctx.body = await ListHelpers.getLists(ctx.user)
|
ctx.body = await ListHelpers.getLists(ctx)
|
||||||
.then(p => p.map(list => convertListId(list)));
|
.then(p => p.map(list => convertListId(list)));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -24,7 +24,7 @@ export function setupEndpointsList(router: Router): void {
|
||||||
async (ctx, reply) => {
|
async (ctx, reply) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
|
|
||||||
ctx.body = await ListHelpers.getListOr404(ctx.user, id)
|
ctx.body = await ListHelpers.getListOr404(id, ctx)
|
||||||
.then(p => convertListId(p));
|
.then(p => convertListId(p));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -34,7 +34,7 @@ export function setupEndpointsList(router: Router): void {
|
||||||
const body = ctx.request.body as any;
|
const body = ctx.request.body as any;
|
||||||
const title = (body.title ?? '').trim();
|
const title = (body.title ?? '').trim();
|
||||||
|
|
||||||
ctx.body = await ListHelpers.createList(ctx.user, title)
|
ctx.body = await ListHelpers.createList(title, ctx)
|
||||||
.then(p => convertListId(p));
|
.then(p => convertListId(p));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -48,7 +48,7 @@ export function setupEndpointsList(router: Router): void {
|
||||||
|
|
||||||
const body = ctx.request.body as any;
|
const body = ctx.request.body as any;
|
||||||
const title = (body.title ?? '').trim();
|
const title = (body.title ?? '').trim();
|
||||||
ctx.body = await ListHelpers.updateList(ctx.user, list, title)
|
ctx.body = await ListHelpers.updateList(list, title, ctx)
|
||||||
.then(p => convertListId(p));
|
.then(p => convertListId(p));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -60,7 +60,7 @@ export function setupEndpointsList(router: Router): void {
|
||||||
const list = await UserLists.findOneBy({ userId: ctx.user.id, id: id });
|
const list = await UserLists.findOneBy({ userId: ctx.user.id, id: id });
|
||||||
if (!list) throw new MastoApiError(404);
|
if (!list) throw new MastoApiError(404);
|
||||||
|
|
||||||
await ListHelpers.deleteList(ctx.user, list);
|
await ListHelpers.deleteList(list, ctx);
|
||||||
ctx.body = {};
|
ctx.body = {};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -70,7 +70,7 @@ export function setupEndpointsList(router: Router): void {
|
||||||
async (ctx, reply) => {
|
async (ctx, reply) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)));
|
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)));
|
||||||
const res = await ListHelpers.getListUsers(ctx.user, id, args.max_id, args.since_id, args.min_id, args.limit);
|
const res = await ListHelpers.getListUsers(id, args.max_id, args.since_id, args.min_id, args.limit, ctx);
|
||||||
const accounts = await UserConverter.encodeMany(res.data, ctx);
|
const accounts = await UserConverter.encodeMany(res.data, ctx);
|
||||||
|
|
||||||
ctx.body = accounts.map(account => convertAccountId(account));
|
ctx.body = accounts.map(account => convertAccountId(account));
|
||||||
|
@ -90,7 +90,7 @@ export function setupEndpointsList(router: Router): void {
|
||||||
|
|
||||||
const ids = toArray(body['account_ids']).map(p => convertId(p, IdType.IceshrimpId));
|
const ids = toArray(body['account_ids']).map(p => convertId(p, IdType.IceshrimpId));
|
||||||
const targets = await Promise.all(ids.map(p => getUser(p)));
|
const targets = await Promise.all(ids.map(p => getUser(p)));
|
||||||
await ListHelpers.addToList(ctx.user, list, targets);
|
await ListHelpers.addToList(list, targets, ctx);
|
||||||
ctx.body = {}
|
ctx.body = {}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -107,7 +107,7 @@ export function setupEndpointsList(router: Router): void {
|
||||||
|
|
||||||
const ids = toArray(body['account_ids']).map(p => convertId(p, IdType.IceshrimpId));
|
const ids = toArray(body['account_ids']).map(p => convertId(p, IdType.IceshrimpId));
|
||||||
const targets = await Promise.all(ids.map(p => getUser(p)));
|
const targets = await Promise.all(ids.map(p => getUser(p)));
|
||||||
await ListHelpers.removeFromList(ctx.user, list, targets);
|
await ListHelpers.removeFromList(list, targets, ctx);
|
||||||
ctx.body = {}
|
ctx.body = {}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,8 +3,6 @@ import { convertId, IdType } from "@/misc/convert-id.js";
|
||||||
import { convertAttachmentId } from "@/server/api/mastodon/converters.js";
|
import { convertAttachmentId } from "@/server/api/mastodon/converters.js";
|
||||||
import { MediaHelpers } from "@/server/api/mastodon/helpers/media.js";
|
import { MediaHelpers } from "@/server/api/mastodon/helpers/media.js";
|
||||||
import { FileConverter } from "@/server/api/mastodon/converters/file.js";
|
import { FileConverter } from "@/server/api/mastodon/converters/file.js";
|
||||||
import { Files } from "formidable";
|
|
||||||
import { toSingleLast } from "@/prelude/array.js";
|
|
||||||
import { auth } from "@/server/api/mastodon/middleware/auth.js";
|
import { auth } from "@/server/api/mastodon/middleware/auth.js";
|
||||||
|
|
||||||
export function setupEndpointsMedia(router: Router): void {
|
export function setupEndpointsMedia(router: Router): void {
|
||||||
|
@ -12,7 +10,7 @@ export function setupEndpointsMedia(router: Router): void {
|
||||||
auth(true, ['write:media']),
|
auth(true, ['write:media']),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const file = await MediaHelpers.getMediaPackedOr404(ctx.user, id);
|
const file = await MediaHelpers.getMediaPackedOr404(id, ctx);
|
||||||
const attachment = FileConverter.encode(file);
|
const attachment = FileConverter.encode(file);
|
||||||
ctx.body = convertAttachmentId(attachment);
|
ctx.body = convertAttachmentId(attachment);
|
||||||
}
|
}
|
||||||
|
@ -21,8 +19,8 @@ export function setupEndpointsMedia(router: Router): void {
|
||||||
auth(true, ['write:media']),
|
auth(true, ['write:media']),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const file = await MediaHelpers.getMediaOr404(ctx.user, id);
|
const file = await MediaHelpers.getMediaOr404(id, ctx);
|
||||||
const result = await MediaHelpers.updateMedia(ctx.user, file, ctx.request.body)
|
const result = await MediaHelpers.updateMedia(file, ctx)
|
||||||
.then(p => FileConverter.encode(p));
|
.then(p => FileConverter.encode(p));
|
||||||
ctx.body = convertAttachmentId(result);
|
ctx.body = convertAttachmentId(result);
|
||||||
}
|
}
|
||||||
|
@ -30,10 +28,7 @@ export function setupEndpointsMedia(router: Router): void {
|
||||||
router.post(["/v2/media", "/v1/media"],
|
router.post(["/v2/media", "/v1/media"],
|
||||||
auth(true, ['write:media']),
|
auth(true, ['write:media']),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
//FIXME: why do we have to cast this to any first?
|
const result = await MediaHelpers.uploadMedia(ctx)
|
||||||
const files = (ctx.request as any).files as Files | undefined;
|
|
||||||
const file = toSingleLast(files?.file);
|
|
||||||
const result = await MediaHelpers.uploadMedia(ctx.user, file, ctx.request.body)
|
|
||||||
.then(p => FileConverter.encode(p));
|
.then(p => FileConverter.encode(p));
|
||||||
ctx.body = convertAttachmentId(result);
|
ctx.body = convertAttachmentId(result);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ export function setupEndpointsMisc(router: Router): void {
|
||||||
auth(true),
|
auth(true),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const args = argsToBools(ctx.query, ['with_dismissed']);
|
const args = argsToBools(ctx.query, ['with_dismissed']);
|
||||||
ctx.body = await MiscHelpers.getAnnouncements(ctx.user, args['with_dismissed'])
|
ctx.body = await MiscHelpers.getAnnouncements(args['with_dismissed'], ctx)
|
||||||
.then(p => p.map(x => convertAnnouncementId(x)));
|
.then(p => p.map(x => convertAnnouncementId(x)));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -37,7 +37,7 @@ export function setupEndpointsMisc(router: Router): void {
|
||||||
const announcement = await Announcements.findOneBy({ id: id });
|
const announcement = await Announcements.findOneBy({ id: id });
|
||||||
if (!announcement) throw new MastoApiError(404);
|
if (!announcement) throw new MastoApiError(404);
|
||||||
|
|
||||||
await MiscHelpers.dismissAnnouncement(announcement, ctx.user);
|
await MiscHelpers.dismissAnnouncement(announcement, ctx);
|
||||||
ctx.body = {};
|
ctx.body = {};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -68,7 +68,7 @@ export function setupEndpointsMisc(router: Router): void {
|
||||||
router.get("/v1/preferences",
|
router.get("/v1/preferences",
|
||||||
auth(true, ['read:accounts']),
|
auth(true, ['read:accounts']),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
ctx.body = await MiscHelpers.getPreferences(ctx.user);
|
ctx.body = await MiscHelpers.getPreferences(ctx);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ export function setupEndpointsMisc(router: Router): void {
|
||||||
auth(true, ['read']),
|
auth(true, ['read']),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const args = limitToInt(ctx.query);
|
const args = limitToInt(ctx.query);
|
||||||
ctx.body = await MiscHelpers.getFollowSuggestions(ctx.user, args.limit, ctx)
|
ctx.body = await MiscHelpers.getFollowSuggestions(args.limit, ctx)
|
||||||
.then(p => p.map(x => convertSuggestionIds(x)));
|
.then(p => p.map(x => convertSuggestionIds(x)));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -11,8 +11,8 @@ export function setupEndpointsNotifications(router: Router): void {
|
||||||
auth(true, ['read:notifications']),
|
auth(true, ['read:notifications']),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)), ['types[]', 'exclude_types[]']);
|
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)), ['types[]', 'exclude_types[]']);
|
||||||
const res = await NotificationHelpers.getNotifications(ctx.user, args.max_id, args.since_id, args.min_id, args.limit, args['types[]'], args['exclude_types[]'], args.account_id);
|
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.data, ctx.user, ctx);
|
const data = await NotificationConverter.encodeMany(res.data, ctx);
|
||||||
|
|
||||||
ctx.body = data.map(n => convertNotificationIds(n));
|
ctx.body = data.map(n => convertNotificationIds(n));
|
||||||
ctx.pagination = res.pagination;
|
ctx.pagination = res.pagination;
|
||||||
|
@ -22,15 +22,15 @@ export function setupEndpointsNotifications(router: Router): void {
|
||||||
router.get("/v1/notifications/:id",
|
router.get("/v1/notifications/:id",
|
||||||
auth(true, ['read:notifications']),
|
auth(true, ['read:notifications']),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const notification = await NotificationHelpers.getNotificationOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx.user);
|
const notification = await NotificationHelpers.getNotificationOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
|
||||||
ctx.body = convertNotificationIds(await NotificationConverter.encode(notification, ctx.user, ctx));
|
ctx.body = convertNotificationIds(await NotificationConverter.encode(notification, ctx));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post("/v1/notifications/clear",
|
router.post("/v1/notifications/clear",
|
||||||
auth(true, ['write:notifications']),
|
auth(true, ['write:notifications']),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
await NotificationHelpers.clearAllNotifications(ctx.user);
|
await NotificationHelpers.clearAllNotifications(ctx);
|
||||||
ctx.body = {};
|
ctx.body = {};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -38,8 +38,8 @@ export function setupEndpointsNotifications(router: Router): void {
|
||||||
router.post("/v1/notifications/:id/dismiss",
|
router.post("/v1/notifications/:id/dismiss",
|
||||||
auth(true, ['write:notifications']),
|
auth(true, ['write:notifications']),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const notification = await NotificationHelpers.getNotificationOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx.user);
|
const notification = await NotificationHelpers.getNotificationOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx);
|
||||||
await NotificationHelpers.dismissNotification(notification.id, ctx.user);
|
await NotificationHelpers.dismissNotification(notification.id, ctx);
|
||||||
ctx.body = {};
|
ctx.body = {};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -48,7 +48,7 @@ export function setupEndpointsNotifications(router: Router): void {
|
||||||
auth(true, ['write:conversations']),
|
auth(true, ['write:conversations']),
|
||||||
async (ctx, reply) => {
|
async (ctx, reply) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
await NotificationHelpers.markConversationAsRead(id, ctx.user);
|
await NotificationHelpers.markConversationAsRead(id, ctx);
|
||||||
ctx.body = {};
|
ctx.body = {};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,7 +9,7 @@ export function setupEndpointsSearch(router: Router): void {
|
||||||
auth(true, ['read:search']),
|
auth(true, ['read:search']),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const args = normalizeUrlQuery(convertPaginationArgsIds(argsToBools(limitToInt(ctx.query), ['resolve', 'following', 'exclude_unreviewed'])));
|
const args = normalizeUrlQuery(convertPaginationArgsIds(argsToBools(limitToInt(ctx.query), ['resolve', 'following', 'exclude_unreviewed'])));
|
||||||
const result = await SearchHelpers.search(ctx.user, 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);
|
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);
|
ctx.body = convertSearchIds(result);
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
router.post("/v1/statuses",
|
router.post("/v1/statuses",
|
||||||
auth(true, ['write:statuses']),
|
auth(true, ['write:statuses']),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const key = NoteHelpers.getIdempotencyKey(ctx.headers, ctx.user);
|
const key = NoteHelpers.getIdempotencyKey(ctx);
|
||||||
if (key !== null) {
|
if (key !== null) {
|
||||||
const result = await NoteHelpers.getFromIdempotencyCache(key);
|
const result = await NoteHelpers.getFromIdempotencyCache(key);
|
||||||
|
|
||||||
|
@ -31,8 +31,8 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
let request = NoteHelpers.normalizeComposeOptions(ctx.request.body);
|
let request = NoteHelpers.normalizeComposeOptions(ctx.request.body);
|
||||||
ctx.body = await NoteHelpers.createNote(request, ctx.user)
|
ctx.body = await NoteHelpers.createNote(request, ctx)
|
||||||
.then(p => NoteConverter.encode(p, ctx.user, ctx))
|
.then(p => NoteConverter.encode(p, ctx))
|
||||||
.then(p => convertStatusIds(p));
|
.then(p => convertStatusIds(p));
|
||||||
|
|
||||||
if (key !== null) NoteHelpers.postIdempotencyCache.set(key, { status: ctx.body });
|
if (key !== null) NoteHelpers.postIdempotencyCache.set(key, { status: ctx.body });
|
||||||
|
@ -42,10 +42,10 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
auth(true, ['write:statuses']),
|
auth(true, ['write:statuses']),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const noteId = convertId(ctx.params.id, IdType.IceshrimpId);
|
const noteId = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const note = await NoteHelpers.getNoteOr404(noteId, ctx.user);
|
const note = await NoteHelpers.getNoteOr404(noteId, ctx);
|
||||||
let request = NoteHelpers.normalizeEditOptions(ctx.request.body);
|
let request = NoteHelpers.normalizeEditOptions(ctx.request.body);
|
||||||
ctx.body = await NoteHelpers.editNote(request, note, ctx.user)
|
ctx.body = await NoteHelpers.editNote(request, note, ctx)
|
||||||
.then(p => NoteConverter.encode(p, ctx.user, ctx))
|
.then(p => NoteConverter.encode(p, ctx))
|
||||||
.then(p => convertStatusIds(p));
|
.then(p => convertStatusIds(p));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -53,9 +53,9 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
auth(false, ["read:statuses"]),
|
auth(false, ["read:statuses"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const noteId = convertId(ctx.params.id, IdType.IceshrimpId);
|
const noteId = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const note = await NoteHelpers.getNoteOr404(noteId, ctx.user);
|
const note = await NoteHelpers.getNoteOr404(noteId, ctx);
|
||||||
|
|
||||||
const status = await NoteConverter.encode(note, ctx.user, ctx);
|
const status = await NoteConverter.encode(note, ctx);
|
||||||
ctx.body = convertStatusIds(status);
|
ctx.body = convertStatusIds(status);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -63,8 +63,8 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
auth(true, ['write:statuses']),
|
auth(true, ['write:statuses']),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const noteId = convertId(ctx.params.id, IdType.IceshrimpId);
|
const noteId = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const note = await NoteHelpers.getNoteOr404(noteId, ctx.user);
|
const note = await NoteHelpers.getNoteOr404(noteId, ctx);
|
||||||
ctx.body = await NoteHelpers.deleteNote(note, ctx.user, ctx)
|
ctx.body = await NoteHelpers.deleteNote(note, ctx)
|
||||||
.then(p => convertStatusIds(p));
|
.then(p => convertStatusIds(p));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -73,13 +73,14 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
"/v1/statuses/:id/context",
|
"/v1/statuses/:id/context",
|
||||||
auth(false, ["read:statuses"]),
|
auth(false, ["read:statuses"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
|
//FIXME: determine final limits within helper functions instead of here
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const note = await NoteHelpers.getNoteOr404(id, ctx.user);
|
const note = await NoteHelpers.getNoteOr404(id, ctx);
|
||||||
const ancestors = await NoteHelpers.getNoteAncestors(note, ctx.user, ctx.user ? 4096 : 60)
|
const ancestors = await NoteHelpers.getNoteAncestors(note, ctx.user ? 4096 : 60, ctx)
|
||||||
.then(n => NoteConverter.encodeMany(n, ctx.user, ctx))
|
.then(n => NoteConverter.encodeMany(n, ctx))
|
||||||
.then(n => n.map(s => convertStatusIds(s)));
|
.then(n => n.map(s => convertStatusIds(s)));
|
||||||
const descendants = await NoteHelpers.getNoteDescendants(note, ctx.user, ctx.user ? 4096 : 40, ctx.user ? 4096 : 20)
|
const descendants = await NoteHelpers.getNoteDescendants(note, ctx.user ? 4096 : 40, ctx.user ? 4096 : 20, ctx)
|
||||||
.then(n => NoteConverter.encodeMany(n, ctx.user, ctx))
|
.then(n => NoteConverter.encodeMany(n, ctx))
|
||||||
.then(n => n.map(s => convertStatusIds(s)));
|
.then(n => n.map(s => convertStatusIds(s)));
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
|
@ -93,7 +94,7 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
auth(false, ["read:statuses"]),
|
auth(false, ["read:statuses"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const note = await NoteHelpers.getNoteOr404(id, ctx.user);
|
const note = await NoteHelpers.getNoteOr404(id, ctx);
|
||||||
const res = await NoteHelpers.getNoteEditHistory(note, ctx);
|
const res = await NoteHelpers.getNoteEditHistory(note, ctx);
|
||||||
ctx.body = res.map(p => convertStatusEditIds(p));
|
ctx.body = res.map(p => convertStatusEditIds(p));
|
||||||
}
|
}
|
||||||
|
@ -103,7 +104,7 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
auth(true, ["read:statuses"]),
|
auth(true, ["read:statuses"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const note = await NoteHelpers.getNoteOr404(id, ctx.user);
|
const note = await NoteHelpers.getNoteOr404(id, ctx);
|
||||||
const src = NoteHelpers.getNoteSource(note);
|
const src = NoteHelpers.getNoteSource(note);
|
||||||
ctx.body = convertStatusSourceId(src);
|
ctx.body = convertStatusSourceId(src);
|
||||||
}
|
}
|
||||||
|
@ -113,9 +114,9 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
auth(false, ["read:statuses"]),
|
auth(false, ["read:statuses"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const note = await NoteHelpers.getNoteOr404(id, ctx.user);
|
const note = await NoteHelpers.getNoteOr404(id, ctx);
|
||||||
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
|
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
|
||||||
const res = await NoteHelpers.getNoteRebloggedBy(note, ctx.user, args.max_id, args.since_id, args.min_id, args.limit);
|
const res = await NoteHelpers.getNoteRebloggedBy(note, args.max_id, args.since_id, args.min_id, args.limit, ctx);
|
||||||
const users = await UserConverter.encodeMany(res.data, ctx);
|
const users = await UserConverter.encodeMany(res.data, ctx);
|
||||||
ctx.body = users.map(m => convertAccountId(m));
|
ctx.body = users.map(m => convertAccountId(m));
|
||||||
ctx.pagination = res.pagination;
|
ctx.pagination = res.pagination;
|
||||||
|
@ -126,7 +127,7 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
auth(false, ["read:statuses"]),
|
auth(false, ["read:statuses"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const note = await NoteHelpers.getNoteOr404(id, ctx.user);
|
const note = await NoteHelpers.getNoteOr404(id, ctx);
|
||||||
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
|
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
|
||||||
const res = await NoteHelpers.getNoteFavoritedBy(note, args.max_id, args.since_id, args.min_id, args.limit);
|
const res = await NoteHelpers.getNoteFavoritedBy(note, args.max_id, args.since_id, args.min_id, args.limit);
|
||||||
const users = await UserConverter.encodeMany(res.data, ctx);
|
const users = await UserConverter.encodeMany(res.data, ctx);
|
||||||
|
@ -139,11 +140,11 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
auth(true, ["write:favourites"]),
|
auth(true, ["write:favourites"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const note = await NoteHelpers.getNoteOr404(id, ctx.user);
|
const note = await NoteHelpers.getNoteOr404(id, ctx);
|
||||||
const reaction = await NoteHelpers.getDefaultReaction();
|
const reaction = await NoteHelpers.getDefaultReaction();
|
||||||
|
|
||||||
ctx.body = await NoteHelpers.reactToNote(note, ctx.user, reaction)
|
ctx.body = await NoteHelpers.reactToNote(note, reaction, ctx)
|
||||||
.then(p => NoteConverter.encode(p, ctx.user, ctx))
|
.then(p => NoteConverter.encode(p, ctx))
|
||||||
.then(p => convertStatusIds(p));
|
.then(p => convertStatusIds(p));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -152,10 +153,10 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
auth(true, ["write:favourites"]),
|
auth(true, ["write:favourites"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const note = await NoteHelpers.getNoteOr404(id, ctx.user);
|
const note = await NoteHelpers.getNoteOr404(id, ctx);
|
||||||
|
|
||||||
ctx.body = await NoteHelpers.removeReactFromNote(note, ctx.user)
|
ctx.body = await NoteHelpers.removeReactFromNote(note, ctx)
|
||||||
.then(p => NoteConverter.encode(p, ctx.user, ctx))
|
.then(p => NoteConverter.encode(p, ctx))
|
||||||
.then(p => convertStatusIds(p));
|
.then(p => convertStatusIds(p));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -165,10 +166,10 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
auth(true, ["write:statuses"]),
|
auth(true, ["write:statuses"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const note = await NoteHelpers.getNoteOr404(id, ctx.user);
|
const note = await NoteHelpers.getNoteOr404(id, ctx);
|
||||||
|
|
||||||
ctx.body = await NoteHelpers.reblogNote(note, ctx.user)
|
ctx.body = await NoteHelpers.reblogNote(note, ctx)
|
||||||
.then(p => NoteConverter.encode(p, ctx.user, ctx))
|
.then(p => NoteConverter.encode(p, ctx))
|
||||||
.then(p => convertStatusIds(p));
|
.then(p => convertStatusIds(p));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -178,10 +179,10 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
auth(true, ["write:statuses"]),
|
auth(true, ["write:statuses"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const note = await NoteHelpers.getNoteOr404(id, ctx.user);
|
const note = await NoteHelpers.getNoteOr404(id, ctx);
|
||||||
|
|
||||||
ctx.body = await NoteHelpers.unreblogNote(note, ctx.user)
|
ctx.body = await NoteHelpers.unreblogNote(note, ctx)
|
||||||
.then(p => NoteConverter.encode(p, ctx.user, ctx))
|
.then(p => NoteConverter.encode(p, ctx))
|
||||||
.then(p => convertStatusIds(p));
|
.then(p => convertStatusIds(p));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -191,10 +192,10 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
auth(true, ["write:bookmarks"]),
|
auth(true, ["write:bookmarks"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const note = await NoteHelpers.getNoteOr404(id, ctx.user);
|
const note = await NoteHelpers.getNoteOr404(id, ctx);
|
||||||
|
|
||||||
ctx.body = await NoteHelpers.bookmarkNote(note, ctx.user)
|
ctx.body = await NoteHelpers.bookmarkNote(note, ctx)
|
||||||
.then(p => NoteConverter.encode(p, ctx.user, ctx))
|
.then(p => NoteConverter.encode(p, ctx))
|
||||||
.then(p => convertStatusIds(p));
|
.then(p => convertStatusIds(p));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -204,10 +205,10 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
auth(true, ["write:bookmarks"]),
|
auth(true, ["write:bookmarks"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const note = await NoteHelpers.getNoteOr404(id, ctx.user);
|
const note = await NoteHelpers.getNoteOr404(id, ctx);
|
||||||
|
|
||||||
ctx.body = await NoteHelpers.unbookmarkNote(note, ctx.user)
|
ctx.body = await NoteHelpers.unbookmarkNote(note, ctx)
|
||||||
.then(p => NoteConverter.encode(p, ctx.user, ctx))
|
.then(p => NoteConverter.encode(p, ctx))
|
||||||
.then(p => convertStatusIds(p));
|
.then(p => convertStatusIds(p));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -217,10 +218,10 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
auth(true, ["write:accounts"]),
|
auth(true, ["write:accounts"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const note = await NoteHelpers.getNoteOr404(id, ctx.user);
|
const note = await NoteHelpers.getNoteOr404(id, ctx);
|
||||||
|
|
||||||
ctx.body = await NoteHelpers.pinNote(note, ctx.user)
|
ctx.body = await NoteHelpers.pinNote(note, ctx)
|
||||||
.then(p => NoteConverter.encode(p, ctx.user, ctx))
|
.then(p => NoteConverter.encode(p, ctx))
|
||||||
.then(p => convertStatusIds(p));
|
.then(p => convertStatusIds(p));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -230,10 +231,10 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
auth(true, ["write:accounts"]),
|
auth(true, ["write:accounts"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const note = await NoteHelpers.getNoteOr404(id, ctx.user);
|
const note = await NoteHelpers.getNoteOr404(id, ctx);
|
||||||
|
|
||||||
ctx.body = await NoteHelpers.unpinNote(note, ctx.user)
|
ctx.body = await NoteHelpers.unpinNote(note, ctx)
|
||||||
.then(p => NoteConverter.encode(p, ctx.user, ctx))
|
.then(p => NoteConverter.encode(p, ctx))
|
||||||
.then(p => convertStatusIds(p));
|
.then(p => convertStatusIds(p));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -243,10 +244,10 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
auth(true, ["write:favourites"]),
|
auth(true, ["write:favourites"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const note = await NoteHelpers.getNoteOr404(id, ctx.user);
|
const note = await NoteHelpers.getNoteOr404(id, ctx);
|
||||||
|
|
||||||
ctx.body = await NoteHelpers.reactToNote(note, ctx.user, ctx.params.name)
|
ctx.body = await NoteHelpers.reactToNote(note, ctx.params.name, ctx)
|
||||||
.then(p => NoteConverter.encode(p, ctx.user, ctx))
|
.then(p => NoteConverter.encode(p, ctx))
|
||||||
.then(p => convertStatusIds(p));
|
.then(p => convertStatusIds(p));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -256,10 +257,10 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
auth(true, ["write:favourites"]),
|
auth(true, ["write:favourites"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const note = await NoteHelpers.getNoteOr404(id, ctx.user);
|
const note = await NoteHelpers.getNoteOr404(id, ctx);
|
||||||
|
|
||||||
ctx.body = await NoteHelpers.removeReactFromNote(note, ctx.user)
|
ctx.body = await NoteHelpers.removeReactFromNote(note, ctx)
|
||||||
.then(p => NoteConverter.encode(p, ctx.user, ctx))
|
.then(p => NoteConverter.encode(p, ctx))
|
||||||
.then(p => convertStatusIds(p));
|
.then(p => convertStatusIds(p));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -267,8 +268,8 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
auth(false, ["read:statuses"]),
|
auth(false, ["read:statuses"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const note = await NoteHelpers.getNoteOr404(id, ctx.user);
|
const note = await NoteHelpers.getNoteOr404(id, ctx);
|
||||||
const data = await PollHelpers.getPoll(note, ctx.user, ctx);
|
const data = await PollHelpers.getPoll(note, ctx);
|
||||||
ctx.body = convertPollId(data);
|
ctx.body = convertPollId(data);
|
||||||
});
|
});
|
||||||
router.post<{ Params: { id: string } }>(
|
router.post<{ Params: { id: string } }>(
|
||||||
|
@ -276,13 +277,13 @@ export function setupEndpointsStatus(router: Router): void {
|
||||||
auth(true, ["write:statuses"]),
|
auth(true, ["write:statuses"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
const id = convertId(ctx.params.id, IdType.IceshrimpId);
|
||||||
const note = await NoteHelpers.getNoteOr404(id, ctx.user);
|
const note = await NoteHelpers.getNoteOr404(id, ctx);
|
||||||
|
|
||||||
const body: any = ctx.request.body;
|
const body: any = ctx.request.body;
|
||||||
const choices = toArray(body.choices ?? []).map(p => parseInt(p));
|
const choices = toArray(body.choices ?? []).map(p => parseInt(p));
|
||||||
if (choices.length < 1) throw new MastoApiError(400, "Must vote for at least one option");
|
if (choices.length < 1) throw new MastoApiError(400, "Must vote for at least one option");
|
||||||
|
|
||||||
const data = await PollHelpers.voteInPoll(choices, note, ctx.user, ctx);
|
const data = await PollHelpers.voteInPoll(choices, note, ctx);
|
||||||
ctx.body = convertPollId(data);
|
ctx.body = convertPollId(data);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -67,8 +67,8 @@ export function setupEndpointsTimeline(router: Router): void {
|
||||||
auth(true, ['read:statuses']),
|
auth(true, ['read:statuses']),
|
||||||
async (ctx, reply) => {
|
async (ctx, reply) => {
|
||||||
const args = normalizeUrlQuery(convertPaginationArgsIds(argsToBools(limitToInt(ctx.query))));
|
const args = normalizeUrlQuery(convertPaginationArgsIds(argsToBools(limitToInt(ctx.query))));
|
||||||
const res = await TimelineHelpers.getPublicTimeline(ctx.user, args.max_id, args.since_id, args.min_id, args.limit, args.only_media, args.local, args.remote);
|
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.data, ctx.user, ctx);
|
const tl = await NoteConverter.encodeMany(res.data, ctx);
|
||||||
|
|
||||||
ctx.body = tl.map(s => convertStatusIds(s));
|
ctx.body = tl.map(s => convertStatusIds(s));
|
||||||
ctx.pagination = res.pagination;
|
ctx.pagination = res.pagination;
|
||||||
|
@ -80,7 +80,7 @@ export function setupEndpointsTimeline(router: Router): void {
|
||||||
const tag = (ctx.params.hashtag ?? '').trim();
|
const tag = (ctx.params.hashtag ?? '').trim();
|
||||||
const args = normalizeUrlQuery(convertPaginationArgsIds(argsToBools(limitToInt(ctx.query))), ['any[]', 'all[]', 'none[]']);
|
const args = normalizeUrlQuery(convertPaginationArgsIds(argsToBools(limitToInt(ctx.query))), ['any[]', 'all[]', 'none[]']);
|
||||||
const res = await TimelineHelpers.getTagTimeline(ctx.user, 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);
|
const res = await TimelineHelpers.getTagTimeline(ctx.user, 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);
|
||||||
const tl = await NoteConverter.encodeMany(res.data, ctx.user, ctx);
|
const tl = await NoteConverter.encodeMany(res.data, ctx);
|
||||||
|
|
||||||
ctx.body = tl.map(s => convertStatusIds(s));
|
ctx.body = tl.map(s => convertStatusIds(s));
|
||||||
ctx.pagination = res.pagination;
|
ctx.pagination = res.pagination;
|
||||||
|
@ -90,8 +90,8 @@ export function setupEndpointsTimeline(router: Router): void {
|
||||||
auth(true, ['read:statuses']),
|
auth(true, ['read:statuses']),
|
||||||
async (ctx, reply) => {
|
async (ctx, reply) => {
|
||||||
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)));
|
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)));
|
||||||
const res = await TimelineHelpers.getHomeTimeline(ctx.user, args.max_id, args.since_id, args.min_id, args.limit);
|
const res = await TimelineHelpers.getHomeTimeline(args.max_id, args.since_id, args.min_id, args.limit, ctx);
|
||||||
const tl = await NoteConverter.encodeMany(res.data, ctx.user, ctx);
|
const tl = await NoteConverter.encodeMany(res.data, ctx);
|
||||||
|
|
||||||
ctx.body = tl.map(s => convertStatusIds(s));
|
ctx.body = tl.map(s => convertStatusIds(s));
|
||||||
ctx.pagination = res.pagination;
|
ctx.pagination = res.pagination;
|
||||||
|
@ -105,8 +105,8 @@ export function setupEndpointsTimeline(router: Router): void {
|
||||||
if (!list) throw new MastoApiError(404);
|
if (!list) throw new MastoApiError(404);
|
||||||
|
|
||||||
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)));
|
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)));
|
||||||
const res = await TimelineHelpers.getListTimeline(ctx.user, list, args.max_id, args.since_id, args.min_id, args.limit);
|
const res = await TimelineHelpers.getListTimeline(list, args.max_id, args.since_id, args.min_id, args.limit, ctx);
|
||||||
const tl = await NoteConverter.encodeMany(res.data, ctx.user, ctx);
|
const tl = await NoteConverter.encodeMany(res.data, ctx);
|
||||||
|
|
||||||
ctx.body = tl.map(s => convertStatusIds(s));
|
ctx.body = tl.map(s => convertStatusIds(s));
|
||||||
ctx.pagination = res.pagination;
|
ctx.pagination = res.pagination;
|
||||||
|
@ -116,7 +116,7 @@ export function setupEndpointsTimeline(router: Router): void {
|
||||||
auth(true, ['read:statuses']),
|
auth(true, ['read:statuses']),
|
||||||
async (ctx, reply) => {
|
async (ctx, reply) => {
|
||||||
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)));
|
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)));
|
||||||
const res = await TimelineHelpers.getConversations(ctx.user, args.max_id, args.since_id, args.min_id, args.limit, ctx);
|
const res = await TimelineHelpers.getConversations(args.max_id, args.since_id, args.min_id, args.limit, ctx);
|
||||||
|
|
||||||
ctx.body = res.data.map(c => convertConversationIds(c));
|
ctx.body = res.data.map(c => convertConversationIds(c));
|
||||||
ctx.pagination = res.pagination;
|
ctx.pagination = res.pagination;
|
||||||
|
|
|
@ -7,9 +7,12 @@ import { pushUserToUserList } from "@/services/user-list/push.js";
|
||||||
import { genId } from "@/misc/gen-id.js";
|
import { genId } from "@/misc/gen-id.js";
|
||||||
import { publishUserListStream } from "@/services/stream.js";
|
import { publishUserListStream } from "@/services/stream.js";
|
||||||
import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js";
|
import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js";
|
||||||
|
import { MastoContext } from "@/server/api/mastodon/index.js";
|
||||||
|
|
||||||
export class ListHelpers {
|
export class ListHelpers {
|
||||||
public static async getLists(user: ILocalUser): Promise<MastodonEntity.List[]> {
|
public static async getLists(ctx: MastoContext): Promise<MastodonEntity.List[]> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
|
|
||||||
return UserLists.findBy({ userId: user.id }).then(p => p.map(list => {
|
return UserLists.findBy({ userId: user.id }).then(p => p.map(list => {
|
||||||
return {
|
return {
|
||||||
id: list.id,
|
id: list.id,
|
||||||
|
@ -18,7 +21,9 @@ export class ListHelpers {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getList(user: ILocalUser, id: string): Promise<MastodonEntity.List> {
|
public static async getList(id: string, ctx: MastoContext): Promise<MastodonEntity.List> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
|
|
||||||
return UserLists.findOneByOrFail({ userId: user.id, id: id }).then(list => {
|
return UserLists.findOneByOrFail({ userId: user.id, id: id }).then(list => {
|
||||||
return {
|
return {
|
||||||
id: list.id,
|
id: list.id,
|
||||||
|
@ -27,14 +32,15 @@ export class ListHelpers {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getListOr404(user: ILocalUser, id: string): Promise<MastodonEntity.List> {
|
public static async getListOr404(id: string, ctx: MastoContext): Promise<MastodonEntity.List> {
|
||||||
return this.getList(user, id).catch(_ => {
|
return this.getList(id, ctx).catch(_ => {
|
||||||
throw new MastoApiError(404);
|
throw new MastoApiError(404);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getListUsers(user: ILocalUser, id: string, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise<LinkPaginationObject<User[]>> {
|
public static async getListUsers(id: string, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<LinkPaginationObject<User[]>> {
|
||||||
if (limit > 80) limit = 80;
|
if (limit > 80) limit = 80;
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
const list = await UserLists.findOneBy({ userId: user.id, id: id });
|
const list = await UserLists.findOneBy({ userId: user.id, id: id });
|
||||||
if (!list) throw new MastoApiError(404);
|
if (!list) throw new MastoApiError(404);
|
||||||
const query = PaginationHelpers.makePaginationQuery(
|
const query = PaginationHelpers.makePaginationQuery(
|
||||||
|
@ -59,12 +65,14 @@ export class ListHelpers {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async deleteList(user: ILocalUser, list: UserList) {
|
public static async deleteList(list: UserList, ctx: MastoContext) {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
if (user.id != list.userId) throw new Error("List is not owned by user");
|
if (user.id != list.userId) throw new Error("List is not owned by user");
|
||||||
await UserLists.delete(list.id);
|
await UserLists.delete(list.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async addToList(localUser: ILocalUser, list: UserList, usersToAdd: User[]) {
|
public static async addToList(list: UserList, usersToAdd: User[], ctx: MastoContext) {
|
||||||
|
const localUser = ctx.user as ILocalUser;
|
||||||
if (localUser.id != list.userId) throw new Error("List is not owned by user");
|
if (localUser.id != list.userId) throw new Error("List is not owned by user");
|
||||||
for (const user of usersToAdd) {
|
for (const user of usersToAdd) {
|
||||||
if (user.id !== localUser.id) {
|
if (user.id !== localUser.id) {
|
||||||
|
@ -89,7 +97,8 @@ export class ListHelpers {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async removeFromList(localUser: ILocalUser, list: UserList, usersToRemove: User[]) {
|
public static async removeFromList(list: UserList, usersToRemove: User[], ctx: MastoContext) {
|
||||||
|
const localUser = ctx.user as ILocalUser;
|
||||||
if (localUser.id != list.userId) throw new Error("List is not owned by user");
|
if (localUser.id != list.userId) throw new Error("List is not owned by user");
|
||||||
for (const user of usersToRemove) {
|
for (const user of usersToRemove) {
|
||||||
const exist = await UserListJoinings.exist({
|
const exist = await UserListJoinings.exist({
|
||||||
|
@ -105,9 +114,10 @@ export class ListHelpers {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async createList(user: ILocalUser, title: string): Promise<MastodonEntity.List> {
|
public static async createList(title: string, ctx: MastoContext): Promise<MastodonEntity.List> {
|
||||||
if (title.length < 1) throw new MastoApiError(400, "Title must not be empty");
|
if (title.length < 1) throw new MastoApiError(400, "Title must not be empty");
|
||||||
|
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
const list = await UserLists.insert({
|
const list = await UserLists.insert({
|
||||||
id: genId(),
|
id: genId(),
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
|
@ -121,8 +131,10 @@ export class ListHelpers {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async updateList(user: ILocalUser, list: UserList, title: string) {
|
public static async updateList(list: UserList, title: string, ctx: MastoContext) {
|
||||||
if (title.length < 1) throw new MastoApiError(400, "Title must not be empty");
|
if (title.length < 1) throw new MastoApiError(400, "Title must not be empty");
|
||||||
|
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
if (user.id != list.userId) throw new Error("List is not owned by user");
|
if (user.id != list.userId) throw new Error("List is not owned by user");
|
||||||
|
|
||||||
const partial = { name: title };
|
const partial = { name: title };
|
||||||
|
@ -135,7 +147,8 @@ export class ListHelpers {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getListsByMember(user: ILocalUser, member: User): Promise<MastodonEntity.List[]> {
|
public static async getListsByMember(member: User, ctx: MastoContext): Promise<MastodonEntity.List[]> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
const joinQuery = UserListJoinings.createQueryBuilder('member')
|
const joinQuery = UserListJoinings.createQueryBuilder('member')
|
||||||
.select("member.userListId")
|
.select("member.userListId")
|
||||||
.where("member.userId = :memberId");
|
.where("member.userId = :memberId");
|
||||||
|
|
|
@ -3,11 +3,18 @@ import { ILocalUser } from "@/models/entities/user.js";
|
||||||
import { DriveFiles } from "@/models/index.js";
|
import { DriveFiles } from "@/models/index.js";
|
||||||
import { Packed } from "@/misc/schema.js";
|
import { Packed } from "@/misc/schema.js";
|
||||||
import { DriveFile } from "@/models/entities/drive-file.js";
|
import { DriveFile } from "@/models/entities/drive-file.js";
|
||||||
import { File } from "formidable";
|
import { File, Files } from "formidable";
|
||||||
import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js";
|
import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js";
|
||||||
|
import { MastoContext } from "@/server/api/mastodon/index.js";
|
||||||
|
import { toSingleLast } from "@/prelude/array.js";
|
||||||
|
|
||||||
export class MediaHelpers {
|
export class MediaHelpers {
|
||||||
public static async uploadMedia(user: ILocalUser, file: File | undefined, body: any): Promise<Packed<"DriveFile">> {
|
public static async uploadMedia(ctx: MastoContext): Promise<Packed<"DriveFile">> {
|
||||||
|
const files = ctx.request.files as Files | undefined;
|
||||||
|
const file = toSingleLast(files?.file);
|
||||||
|
const user = ctx.user as ILocalUser
|
||||||
|
const body = ctx.request.body as any;
|
||||||
|
|
||||||
if (!file) throw new MastoApiError(400, "Validation failed: File content type is invalid, File is invalid");
|
if (!file) throw new MastoApiError(400, "Validation failed: File content type is invalid, File is invalid");
|
||||||
|
|
||||||
return addFile({
|
return addFile({
|
||||||
|
@ -20,7 +27,9 @@ export class MediaHelpers {
|
||||||
.then(p => DriveFiles.pack(p));
|
.then(p => DriveFiles.pack(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async uploadMediaBasic(user: ILocalUser, file: File): Promise<DriveFile> {
|
public static async uploadMediaBasic(file: File, ctx: MastoContext): Promise<DriveFile> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
|
|
||||||
return addFile({
|
return addFile({
|
||||||
user: user,
|
user: user,
|
||||||
path: file.filepath,
|
path: file.filepath,
|
||||||
|
@ -29,7 +38,10 @@ export class MediaHelpers {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async updateMedia(user: ILocalUser, file: DriveFile, body: any): Promise<Packed<"DriveFile">> {
|
public static async updateMedia(file: DriveFile, ctx: MastoContext): Promise<Packed<"DriveFile">> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
|
const body = ctx.request.body as any;
|
||||||
|
|
||||||
await DriveFiles.update(file.id, {
|
await DriveFiles.update(file.id, {
|
||||||
comment: body?.description ?? undefined
|
comment: body?.description ?? undefined
|
||||||
});
|
});
|
||||||
|
@ -38,24 +50,26 @@ export class MediaHelpers {
|
||||||
.then(p => DriveFiles.pack(p));
|
.then(p => DriveFiles.pack(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getMediaPacked(user: ILocalUser, id: string): Promise<Packed<"DriveFile"> | null> {
|
public static async getMediaPacked(id: string, ctx: MastoContext): Promise<Packed<"DriveFile"> | null> {
|
||||||
return this.getMedia(user, id)
|
const user = ctx.user as ILocalUser;
|
||||||
|
return this.getMedia(id, ctx)
|
||||||
.then(p => p ? DriveFiles.pack(p) : null);
|
.then(p => p ? DriveFiles.pack(p) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getMediaPackedOr404(user: ILocalUser, id: string): Promise<Packed<"DriveFile">> {
|
public static async getMediaPackedOr404(id: string, ctx: MastoContext): Promise<Packed<"DriveFile">> {
|
||||||
return this.getMediaPacked(user, id).then(p => {
|
return this.getMediaPacked(id, ctx).then(p => {
|
||||||
if (p) return p;
|
if (p) return p;
|
||||||
throw new MastoApiError(404);
|
throw new MastoApiError(404);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getMedia(user: ILocalUser, id: string): Promise<DriveFile | null> {
|
public static async getMedia(id: string, ctx: MastoContext): Promise<DriveFile | null> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
return DriveFiles.findOneBy({ id: id, userId: user.id });
|
return DriveFiles.findOneBy({ id: id, userId: user.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getMediaOr404(user: ILocalUser, id: string): Promise<DriveFile> {
|
public static async getMediaOr404(id: string, ctx: MastoContext): Promise<DriveFile> {
|
||||||
return this.getMedia(user, id).then(p => {
|
return this.getMedia(id, ctx).then(p => {
|
||||||
if (p) return p;
|
if (p) return p;
|
||||||
throw new MastoApiError(404);
|
throw new MastoApiError(404);
|
||||||
});
|
});
|
||||||
|
|
|
@ -96,7 +96,9 @@ export class MiscHelpers {
|
||||||
return awaitAll(res);
|
return awaitAll(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getAnnouncements(user: ILocalUser, includeRead: boolean = false): Promise<MastodonEntity.Announcement[]> {
|
public static async getAnnouncements(includeRead: boolean = false, ctx: MastoContext): Promise<MastodonEntity.Announcement[]> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
|
|
||||||
if (includeRead) {
|
if (includeRead) {
|
||||||
const [announcements, reads] = await Promise.all([
|
const [announcements, reads] = await Promise.all([
|
||||||
Announcements.createQueryBuilder("announcement")
|
Announcements.createQueryBuilder("announcement")
|
||||||
|
@ -122,7 +124,8 @@ export class MiscHelpers {
|
||||||
.then(p => p.map(x => AnnouncementConverter.encode(x, false)));
|
.then(p => p.map(x => AnnouncementConverter.encode(x, false)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async dismissAnnouncement(announcement: Announcement, user: ILocalUser): Promise<void> {
|
public static async dismissAnnouncement(announcement: Announcement, ctx: MastoContext): Promise<void> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
const exists = await AnnouncementReads.exist({ where: { userId: user.id, announcementId: announcement.id } });
|
const exists = await AnnouncementReads.exist({ where: { userId: user.id, announcementId: announcement.id } });
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
await AnnouncementReads.insert({
|
await AnnouncementReads.insert({
|
||||||
|
@ -134,7 +137,8 @@ export class MiscHelpers {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getFollowSuggestions(user: ILocalUser, limit: number, ctx: MastoContext): Promise<MastodonEntity.SuggestedAccount[]> {
|
public static async getFollowSuggestions(limit: number, ctx: MastoContext): Promise<MastodonEntity.SuggestedAccount[]> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
const results: Promise<MastodonEntity.SuggestedAccount[]>[] = [];
|
const results: Promise<MastodonEntity.SuggestedAccount[]>[] = [];
|
||||||
|
|
||||||
const pinned = fetchMeta().then(meta => Promise.all(
|
const pinned = fetchMeta().then(meta => Promise.all(
|
||||||
|
@ -220,7 +224,7 @@ export class MiscHelpers {
|
||||||
.skip(offset)
|
.skip(offset)
|
||||||
.take(limit)
|
.take(limit)
|
||||||
.getMany()
|
.getMany()
|
||||||
.then(result => NoteConverter.encodeMany(result, null, ctx));
|
.then(result => NoteConverter.encodeMany(result, ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getTrendingHashtags(limit: number = 10, offset: number = 0): Promise<MastodonEntity.Tag[]> {
|
public static async getTrendingHashtags(limit: number = 10, offset: number = 0): Promise<MastodonEntity.Tag[]> {
|
||||||
|
@ -229,11 +233,12 @@ export class MiscHelpers {
|
||||||
//FIXME: This was already implemented in api/endpoints/hashtags/trend.ts, but the implementation is sketchy at best. Rewrite from scratch.
|
//FIXME: This was already implemented in api/endpoints/hashtags/trend.ts, but the implementation is sketchy at best. Rewrite from scratch.
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getPreferences(user: ILocalUser): Promise<MastodonEntity.Preferences> {
|
public static getPreferences(ctx: MastoContext): Promise<MastodonEntity.Preferences> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
const profile = UserProfiles.findOneByOrFail({ userId: user.id });
|
const profile = UserProfiles.findOneByOrFail({ userId: user.id });
|
||||||
const sensitive = profile.then(p => p.alwaysMarkNsfw);
|
const sensitive = profile.then(p => p.alwaysMarkNsfw);
|
||||||
const language = profile.then(p => p.lang);
|
const language = profile.then(p => p.lang);
|
||||||
const privacy = UserHelpers.getDefaultNoteVisibility(user)
|
const privacy = UserHelpers.getDefaultNoteVisibility(ctx)
|
||||||
.then(p => VisibilityConverter.encode(p));
|
.then(p => VisibilityConverter.encode(p));
|
||||||
|
|
||||||
const res = {
|
const res = {
|
||||||
|
|
|
@ -47,7 +47,8 @@ export class NoteHelpers {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async reactToNote(note: Note, user: ILocalUser, reaction: string): Promise<Note> {
|
public static async reactToNote(note: Note, reaction: string, ctx: MastoContext): Promise<Note> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
await createReaction(user, note, reaction).catch(e => {
|
await createReaction(user, note, reaction).catch(e => {
|
||||||
if (e instanceof IdentifiableError && e.id == '51c42bb4-931a-456b-bff7-e5a8a70dd298') return;
|
if (e instanceof IdentifiableError && e.id == '51c42bb4-931a-456b-bff7-e5a8a70dd298') return;
|
||||||
throw e;
|
throw e;
|
||||||
|
@ -55,12 +56,14 @@ export class NoteHelpers {
|
||||||
return getNote(note.id, user);
|
return getNote(note.id, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async removeReactFromNote(note: Note, user: ILocalUser): Promise<Note> {
|
public static async removeReactFromNote(note: Note, ctx: MastoContext): Promise<Note> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
await deleteReaction(user, note);
|
await deleteReaction(user, note);
|
||||||
return getNote(note.id, user);
|
return getNote(note.id, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async reblogNote(note: Note, user: ILocalUser): Promise<Note> {
|
public static async reblogNote(note: Note, ctx: MastoContext): Promise<Note> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
const existingRenote = await Notes.findOneBy({
|
const existingRenote = await Notes.findOneBy({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
renoteId: note.id,
|
renoteId: note.id,
|
||||||
|
@ -75,7 +78,8 @@ export class NoteHelpers {
|
||||||
return await createNote(user, data);
|
return await createNote(user, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async unreblogNote(note: Note, user: ILocalUser): Promise<Note> {
|
public static async unreblogNote(note: Note, ctx: MastoContext): Promise<Note> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
return Notes.findBy({
|
return Notes.findBy({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
renoteId: note.id,
|
renoteId: note.id,
|
||||||
|
@ -85,7 +89,8 @@ export class NoteHelpers {
|
||||||
.then(_ => getNote(note.id, user));
|
.then(_ => getNote(note.id, user));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async bookmarkNote(note: Note, user: ILocalUser): Promise<Note> {
|
public static async bookmarkNote(note: Note, ctx: MastoContext): Promise<Note> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
const bookmarked = await NoteFavorites.exist({
|
const bookmarked = await NoteFavorites.exist({
|
||||||
where: {
|
where: {
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
|
@ -105,7 +110,8 @@ export class NoteHelpers {
|
||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async unbookmarkNote(note: Note, user: ILocalUser): Promise<Note> {
|
public static async unbookmarkNote(note: Note, ctx: MastoContext): Promise<Note> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
return NoteFavorites.findOneBy({
|
return NoteFavorites.findOneBy({
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
@ -114,7 +120,8 @@ export class NoteHelpers {
|
||||||
.then(_ => note);
|
.then(_ => note);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async pinNote(note: Note, user: ILocalUser): Promise<Note> {
|
public static async pinNote(note: Note, ctx: MastoContext): Promise<Note> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
const pinned = await UserNotePinings.exist({
|
const pinned = await UserNotePinings.exist({
|
||||||
where: {
|
where: {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
@ -129,7 +136,8 @@ export class NoteHelpers {
|
||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async unpinNote(note: Note, user: ILocalUser): Promise<Note> {
|
public static async unpinNote(note: Note, ctx: MastoContext): Promise<Note> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
const pinned = await UserNotePinings.exist({
|
const pinned = await UserNotePinings.exist({
|
||||||
where: {
|
where: {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
@ -144,9 +152,10 @@ export class NoteHelpers {
|
||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async deleteNote(note: Note, user: ILocalUser, ctx: MastoContext): Promise<MastodonEntity.Status> {
|
public static async deleteNote(note: Note, ctx: MastoContext): Promise<MastodonEntity.Status> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
if (user.id !== note.userId) throw new MastoApiError(404);
|
if (user.id !== note.userId) throw new MastoApiError(404);
|
||||||
const status = await NoteConverter.encode(note, user, ctx);
|
const status = await NoteConverter.encode(note, ctx);
|
||||||
await deleteNote(user, note);
|
await deleteNote(user, note);
|
||||||
status.content = undefined;
|
status.content = undefined;
|
||||||
return status;
|
return status;
|
||||||
|
@ -222,8 +231,9 @@ export class NoteHelpers {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getNoteRebloggedBy(note: Note, user: ILocalUser | null, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise<LinkPaginationObject<User[]>> {
|
public static async getNoteRebloggedBy(note: Note, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<LinkPaginationObject<User[]>> {
|
||||||
if (limit > 80) limit = 80;
|
if (limit > 80) limit = 80;
|
||||||
|
const user = ctx.user as ILocalUser | null;
|
||||||
const query = PaginationHelpers.makePaginationQuery(
|
const query = PaginationHelpers.makePaginationQuery(
|
||||||
Notes.createQueryBuilder("note"),
|
Notes.createQueryBuilder("note"),
|
||||||
sinceId,
|
sinceId,
|
||||||
|
@ -249,7 +259,8 @@ export class NoteHelpers {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getNoteDescendants(note: Note | string, user: ILocalUser | null, limit: number = 10, depth: number = 2): Promise<Note[]> {
|
public static async getNoteDescendants(note: Note | string, limit: number = 10, depth: number = 2, ctx: MastoContext): Promise<Note[]> {
|
||||||
|
const user = ctx.user as ILocalUser | null;
|
||||||
const noteId = typeof note === "string" ? note : note.id;
|
const noteId = typeof note === "string" ? note : note.id;
|
||||||
const query = makePaginationQuery(Notes.createQueryBuilder("note"))
|
const query = makePaginationQuery(Notes.createQueryBuilder("note"))
|
||||||
.andWhere(
|
.andWhere(
|
||||||
|
@ -266,7 +277,8 @@ export class NoteHelpers {
|
||||||
return query.getMany().then(p => p.reverse());
|
return query.getMany().then(p => p.reverse());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getNoteAncestors(rootNote: Note, user: ILocalUser | null, limit: number = 10): Promise<Note[]> {
|
public static async getNoteAncestors(rootNote: Note, limit: number = 10, ctx: MastoContext): Promise<Note[]> {
|
||||||
|
const user = ctx.user as ILocalUser | null;
|
||||||
const notes = new Array<Note>;
|
const notes = new Array<Note>;
|
||||||
for (let i = 0; i < limit; i++) {
|
for (let i = 0; i < limit; i++) {
|
||||||
const currentNote = notes.at(-1) ?? rootNote;
|
const currentNote = notes.at(-1) ?? rootNote;
|
||||||
|
@ -282,13 +294,14 @@ export class NoteHelpers {
|
||||||
return notes.reverse();
|
return notes.reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async createNote(request: MastodonEntity.StatusCreationRequest, user: ILocalUser): Promise<Note> {
|
public static async createNote(request: MastodonEntity.StatusCreationRequest, ctx: MastoContext): Promise<Note> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
const files = request.media_ids && request.media_ids.length > 0
|
const files = request.media_ids && request.media_ids.length > 0
|
||||||
? DriveFiles.findByIds(request.media_ids)
|
? DriveFiles.findByIds(request.media_ids)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const reply = request.in_reply_to_id ? await getNote(request.in_reply_to_id, user) : undefined;
|
const reply = request.in_reply_to_id ? await getNote(request.in_reply_to_id, user) : undefined;
|
||||||
const visibility = request.visibility ?? UserHelpers.getDefaultNoteVisibility(user);
|
const visibility = request.visibility ?? UserHelpers.getDefaultNoteVisibility(ctx);
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
|
@ -304,13 +317,14 @@ export class NoteHelpers {
|
||||||
reply: reply,
|
reply: reply,
|
||||||
cw: request.spoiler_text,
|
cw: request.spoiler_text,
|
||||||
visibility: visibility,
|
visibility: visibility,
|
||||||
visibleUsers: Promise.resolve(visibility).then(p => p === 'specified' ? this.extractMentions(request.text ?? '', user) : undefined)
|
visibleUsers: Promise.resolve(visibility).then(p => p === 'specified' ? this.extractMentions(request.text ?? '', ctx) : undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
return createNote(user, await awaitAll(data));
|
return createNote(user, await awaitAll(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async editNote(request: MastodonEntity.StatusEditRequest, note: Note, user: ILocalUser): Promise<Note> {
|
public static async editNote(request: MastodonEntity.StatusEditRequest, note: Note, ctx: MastoContext): Promise<Note> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
const files = request.media_ids && request.media_ids.length > 0
|
const files = request.media_ids && request.media_ids.length > 0
|
||||||
? DriveFiles.findByIds(request.media_ids)
|
? DriveFiles.findByIds(request.media_ids)
|
||||||
: [];
|
: [];
|
||||||
|
@ -331,7 +345,8 @@ export class NoteHelpers {
|
||||||
return editNote(user, note, await awaitAll(data));
|
return editNote(user, note, await awaitAll(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async extractMentions(text: string, user: ILocalUser): Promise<User[]> {
|
public static async extractMentions(text: string, ctx: MastoContext): Promise<User[]> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
return extractMentionedUsers(user, mfm.parse(text)!);
|
return extractMentionedUsers(user, mfm.parse(text)!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,13 +412,16 @@ export class NoteHelpers {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getNoteOr404(id: string, user: ILocalUser | null): Promise<Note> {
|
public static async getNoteOr404(id: string, ctx: MastoContext): Promise<Note> {
|
||||||
|
const user = ctx.user as ILocalUser | null;
|
||||||
return getNote(id, user).catch(_ => {
|
return getNote(id, user).catch(_ => {
|
||||||
throw new MastoApiError(404);
|
throw new MastoApiError(404);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getIdempotencyKey(headers: any, user: ILocalUser): string | null {
|
public static getIdempotencyKey(ctx: MastoContext): string | null {
|
||||||
|
const headers = ctx.headers;
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
if (headers["idempotency-key"] === undefined || headers["idempotency-key"] === null) return null;
|
if (headers["idempotency-key"] === undefined || headers["idempotency-key"] === null) return null;
|
||||||
return `${user.id}-${Array.isArray(headers["idempotency-key"]) ? headers["idempotency-key"].at(-1)! : headers["idempotency-key"]}`;
|
return `${user.id}-${Array.isArray(headers["idempotency-key"]) ? headers["idempotency-key"].at(-1)! : headers["idempotency-key"]}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,13 @@ import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js";
|
||||||
import { Notification } from "@/models/entities/notification.js";
|
import { Notification } from "@/models/entities/notification.js";
|
||||||
import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js";
|
import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js";
|
||||||
import { LinkPaginationObject } from "@/server/api/mastodon/middleware/pagination.js";
|
import { LinkPaginationObject } from "@/server/api/mastodon/middleware/pagination.js";
|
||||||
|
import { MastoContext } from "@/server/api/mastodon/index.js";
|
||||||
|
|
||||||
export class NotificationHelpers {
|
export class NotificationHelpers {
|
||||||
public static async getNotifications(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, types: string[] | undefined, excludeTypes: string[] | undefined, accountId: string | undefined): Promise<LinkPaginationObject<Notification[]>> {
|
public static async getNotifications(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, types: string[] | undefined, excludeTypes: string[] | undefined, accountId: string | undefined, ctx: MastoContext): Promise<LinkPaginationObject<Notification[]>> {
|
||||||
if (limit > 80) limit = 80;
|
if (limit > 80) limit = 80;
|
||||||
|
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
let requestedTypes = types
|
let requestedTypes = types
|
||||||
? this.decodeTypes(types)
|
? this.decodeTypes(types)
|
||||||
: ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest'];
|
: ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest'];
|
||||||
|
@ -35,26 +37,30 @@ export class NotificationHelpers {
|
||||||
return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined);
|
return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getNotification(id: string, user: ILocalUser): Promise<Notification | null> {
|
public static async getNotification(id: string, ctx: MastoContext): Promise<Notification | null> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
return Notifications.findOneBy({ id: id, notifieeId: user.id });
|
return Notifications.findOneBy({ id: id, notifieeId: user.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getNotificationOr404(id: string, user: ILocalUser): Promise<Notification> {
|
public static async getNotificationOr404(id: string, ctx: MastoContext): Promise<Notification> {
|
||||||
return this.getNotification(id, user).then(p => {
|
return this.getNotification(id, ctx).then(p => {
|
||||||
if (p) return p;
|
if (p) return p;
|
||||||
throw new MastoApiError(404);
|
throw new MastoApiError(404);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async dismissNotification(id: string, user: ILocalUser): Promise<void> {
|
public static async dismissNotification(id: string, ctx: MastoContext): Promise<void> {
|
||||||
const result = await Notifications.update({ id: id, notifieeId: user.id }, { isRead: true });
|
const user = ctx.user as ILocalUser;
|
||||||
|
await Notifications.update({ id: id, notifieeId: user.id }, { isRead: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async clearAllNotifications(user: ILocalUser): Promise<void> {
|
public static async clearAllNotifications(ctx: MastoContext): Promise<void> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
await Notifications.update({ notifieeId: user.id }, { isRead: true });
|
await Notifications.update({ notifieeId: user.id }, { isRead: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async markConversationAsRead(id: string, user: ILocalUser): Promise<void> {
|
public static async markConversationAsRead(id: string, ctx: MastoContext): Promise<void> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
const notesQuery = Notes.createQueryBuilder("note")
|
const notesQuery = Notes.createQueryBuilder("note")
|
||||||
.select("note.id")
|
.select("note.id")
|
||||||
.andWhere("COALESCE(note.threadId, note.id) = :conversationId");
|
.andWhere("COALESCE(note.threadId, note.id) = :conversationId");
|
||||||
|
|
|
@ -17,7 +17,8 @@ import { UserHelpers } from "@/server/api/mastodon/helpers/user.js";
|
||||||
import { MastoContext } from "@/server/api/mastodon/index.js";
|
import { MastoContext } from "@/server/api/mastodon/index.js";
|
||||||
|
|
||||||
export class PollHelpers {
|
export class PollHelpers {
|
||||||
public static async getPoll(note: Note, user: ILocalUser | null, ctx: MastoContext): Promise<MastodonEntity.Poll> {
|
public static async getPoll(note: Note, ctx: MastoContext): Promise<MastodonEntity.Poll> {
|
||||||
|
const user = ctx.user as ILocalUser | null;
|
||||||
if (!await Notes.isVisibleForMe(note, user?.id ?? null))
|
if (!await Notes.isVisibleForMe(note, user?.id ?? null))
|
||||||
throw new Error('Cannot encode poll not visible for user');
|
throw new Error('Cannot encode poll not visible for user');
|
||||||
|
|
||||||
|
@ -32,8 +33,9 @@ export class PollHelpers {
|
||||||
return populatePoll(note, user?.id ?? null).then(p => PollConverter.encode(p, note.id, noteEmoji));
|
return populatePoll(note, user?.id ?? null).then(p => PollConverter.encode(p, note.id, noteEmoji));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async voteInPoll(choices: number[], note: Note, user: ILocalUser, ctx: MastoContext): Promise<MastodonEntity.Poll> {
|
public static async voteInPoll(choices: number[], note: Note, ctx: MastoContext): Promise<MastodonEntity.Poll> {
|
||||||
if (!note.hasPoll) throw new MastoApiError(404);
|
if (!note.hasPoll) throw new MastoApiError(404);
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
|
|
||||||
for (const choice of choices) {
|
for (const choice of choices) {
|
||||||
const createdAt = new Date();
|
const createdAt = new Date();
|
||||||
|
@ -123,6 +125,6 @@ export class PollHelpers {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.getPoll(note, user, ctx);
|
return this.getPoll(note, ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,15 +24,16 @@ import config from "@/config/index.js";
|
||||||
import { MastoContext } from "@/server/api/mastodon/index.js";
|
import { MastoContext } from "@/server/api/mastodon/index.js";
|
||||||
|
|
||||||
export class SearchHelpers {
|
export class SearchHelpers {
|
||||||
public static async search(user: ILocalUser, q: string | undefined, type: string | undefined, resolve: boolean = false, following: boolean = false, accountId: string | undefined, excludeUnreviewed: boolean = false, maxId: string | undefined, minId: string | undefined, limit: number = 20, offset: number | undefined, ctx: MastoContext): Promise<MastodonEntity.Search> {
|
public static async search(q: string | undefined, type: string | undefined, resolve: boolean = false, following: boolean = false, accountId: string | undefined, excludeUnreviewed: boolean = false, maxId: string | undefined, minId: string | undefined, limit: number = 20, offset: number | undefined, ctx: MastoContext): Promise<MastodonEntity.Search> {
|
||||||
if (q === undefined || q.trim().length === 0) throw new Error('Search query cannot be empty');
|
if (q === undefined || q.trim().length === 0) throw new Error('Search query cannot be empty');
|
||||||
if (limit > 40) limit = 40;
|
if (limit > 40) limit = 40;
|
||||||
const notes = type === 'statuses' || !type ? this.searchNotes(user, q, resolve, following, accountId, maxId, minId, limit, offset) : [];
|
const user = ctx.user as ILocalUser;
|
||||||
const users = type === 'accounts' || !type ? this.searchUsers(user, q, resolve, following, maxId, minId, limit, offset) : [];
|
const notes = type === 'statuses' || !type ? this.searchNotes(q, resolve, following, accountId, maxId, minId, limit, offset, ctx) : [];
|
||||||
|
const users = type === 'accounts' || !type ? this.searchUsers(q, resolve, following, maxId, minId, limit, offset, ctx) : [];
|
||||||
const tags = type === 'hashtags' || !type ? this.searchTags(q, excludeUnreviewed, limit, offset) : [];
|
const tags = type === 'hashtags' || !type ? this.searchTags(q, excludeUnreviewed, limit, offset) : [];
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
statuses: Promise.resolve(notes).then(p => NoteConverter.encodeMany(p, user, ctx)),
|
statuses: Promise.resolve(notes).then(p => NoteConverter.encodeMany(p, ctx)),
|
||||||
accounts: Promise.resolve(users).then(p => UserConverter.encodeMany(p, ctx)),
|
accounts: Promise.resolve(users).then(p => UserConverter.encodeMany(p, ctx)),
|
||||||
hashtags: Promise.resolve(tags)
|
hashtags: Promise.resolve(tags)
|
||||||
};
|
};
|
||||||
|
@ -40,7 +41,8 @@ export class SearchHelpers {
|
||||||
return awaitAll(result);
|
return awaitAll(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async searchUsers(user: ILocalUser, q: string, resolve: boolean, following: boolean, maxId: string | undefined, minId: string | undefined, limit: number, offset: number | undefined): Promise<User[]> {
|
private static async searchUsers(q: string, resolve: boolean, following: boolean, maxId: string | undefined, minId: string | undefined, limit: number, offset: number | undefined, ctx: MastoContext): Promise<User[]> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
if (resolve) {
|
if (resolve) {
|
||||||
try {
|
try {
|
||||||
if (q.startsWith('https://') || q.startsWith('http://')) {
|
if (q.startsWith('https://') || q.startsWith('http://')) {
|
||||||
|
@ -113,8 +115,9 @@ export class SearchHelpers {
|
||||||
return query.skip(offset ?? 0).take(limit).getMany().then(p => minId ? p.reverse() : p);
|
return query.skip(offset ?? 0).take(limit).getMany().then(p => minId ? p.reverse() : p);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async searchNotes(user: ILocalUser, q: string, resolve: boolean, following: boolean, accountId: string | undefined, maxId: string | undefined, minId: string | undefined, limit: number, offset: number | undefined): Promise<Note[]> {
|
private static async searchNotes(q: string, resolve: boolean, following: boolean, accountId: string | undefined, maxId: string | undefined, minId: string | undefined, limit: number, offset: number | undefined, ctx: MastoContext): Promise<Note[]> {
|
||||||
if (accountId && following) throw new Error("The 'following' and 'accountId' parameters cannot be used simultaneously");
|
if (accountId && following) throw new Error("The 'following' and 'accountId' parameters cannot be used simultaneously");
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
|
|
||||||
if (resolve) {
|
if (resolve) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -22,8 +22,9 @@ import { generatePaginationData, LinkPaginationObject } from "@/server/api/masto
|
||||||
import { MastoContext } from "@/server/api/mastodon/index.js";
|
import { MastoContext } from "@/server/api/mastodon/index.js";
|
||||||
|
|
||||||
export class TimelineHelpers {
|
export class TimelineHelpers {
|
||||||
public static async getHomeTimeline(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20): Promise<LinkPaginationObject<Note[]>> {
|
public static async getHomeTimeline(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise<LinkPaginationObject<Note[]>> {
|
||||||
if (limit > 40) limit = 40;
|
if (limit > 40) limit = 40;
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
|
|
||||||
const followingQuery = Followings.createQueryBuilder("following")
|
const followingQuery = Followings.createQueryBuilder("following")
|
||||||
.select("following.followeeId")
|
.select("following.followeeId")
|
||||||
|
@ -56,8 +57,9 @@ export class TimelineHelpers {
|
||||||
return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined);
|
return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getPublicTimeline(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, onlyMedia: boolean = false, local: boolean = false, remote: boolean = false): Promise<LinkPaginationObject<Note[]>> {
|
public static async getPublicTimeline(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, onlyMedia: boolean = false, local: boolean = false, remote: boolean = false, ctx: MastoContext): Promise<LinkPaginationObject<Note[]>> {
|
||||||
if (limit > 40) limit = 40;
|
if (limit > 40) limit = 40;
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
|
|
||||||
if (local && remote) {
|
if (local && remote) {
|
||||||
throw new Error("local and remote are mutually exclusive options");
|
throw new Error("local and remote are mutually exclusive options");
|
||||||
|
@ -99,8 +101,9 @@ export class TimelineHelpers {
|
||||||
return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined);
|
return PaginationHelpers.execQueryLinkPagination(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<LinkPaginationObject<Note[]>> {
|
public static async getListTimeline(list: UserList, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise<LinkPaginationObject<Note[]>> {
|
||||||
if (limit > 40) limit = 40;
|
if (limit > 40) limit = 40;
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
if (user.id != list.userId) throw new Error("List is not owned by user");
|
if (user.id != list.userId) throw new Error("List is not owned by user");
|
||||||
|
|
||||||
const listQuery = UserListJoinings.createQueryBuilder("member")
|
const listQuery = UserListJoinings.createQueryBuilder("member")
|
||||||
|
@ -123,8 +126,9 @@ export class TimelineHelpers {
|
||||||
return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined);
|
return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getTagTimeline(user: ILocalUser, tag: string, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, any: string[], all: string[], none: string[], onlyMedia: boolean = false, local: boolean = false, remote: boolean = false): Promise<LinkPaginationObject<Note[]>> {
|
public static async getTagTimeline(tag: string, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, any: string[], all: string[], none: string[], onlyMedia: boolean = false, local: boolean = false, remote: boolean = false, ctx: MastoContext): Promise<LinkPaginationObject<Note[]>> {
|
||||||
if (limit > 40) limit = 40;
|
if (limit > 40) limit = 40;
|
||||||
|
const user = ctx.user as ILocalUser | null;
|
||||||
|
|
||||||
if (tag.length < 1) throw new MastoApiError(400, "Tag cannot be empty");
|
if (tag.length < 1) throw new MastoApiError(400, "Tag cannot be empty");
|
||||||
|
|
||||||
|
@ -164,8 +168,9 @@ export class TimelineHelpers {
|
||||||
return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined);
|
return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getConversations(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise<LinkPaginationObject<MastodonEntity.Conversation[]>> {
|
public static async getConversations(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise<LinkPaginationObject<MastodonEntity.Conversation[]>> {
|
||||||
if (limit > 40) limit = 40;
|
if (limit > 40) limit = 40;
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
const sq = Notes.createQueryBuilder("note")
|
const sq = Notes.createQueryBuilder("note")
|
||||||
.select("COALESCE(note.threadId, note.id)", "conversationId")
|
.select("COALESCE(note.threadId, note.id)", "conversationId")
|
||||||
.addSelect("note.id", "latest")
|
.addSelect("note.id", "latest")
|
||||||
|
@ -207,7 +212,7 @@ export class TimelineHelpers {
|
||||||
return {
|
return {
|
||||||
id: c.threadId ?? c.id,
|
id: c.threadId ?? c.id,
|
||||||
accounts: accounts.then(u => u.length > 0 ? u : UserConverter.encodeMany([user], ctx)), // failsafe to prevent apps from crashing case when all participant users have been deleted
|
accounts: accounts.then(u => u.length > 0 ? u : UserConverter.encodeMany([user], ctx)), // failsafe to prevent apps from crashing case when all participant users have been deleted
|
||||||
last_status: NoteConverter.encode(c, user, ctx),
|
last_status: NoteConverter.encode(c, ctx),
|
||||||
unread: unread
|
unread: unread
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -61,8 +61,9 @@ export type updateCredsData = {
|
||||||
type RelationshipType = 'followers' | 'following';
|
type RelationshipType = 'followers' | 'following';
|
||||||
|
|
||||||
export class UserHelpers {
|
export class UserHelpers {
|
||||||
public static async followUser(target: User, localUser: ILocalUser, reblogs: boolean, notify: boolean): Promise<MastodonEntity.Relationship> {
|
public static async followUser(target: User, reblogs: boolean, notify: boolean, ctx: MastoContext): Promise<MastodonEntity.Relationship> {
|
||||||
//FIXME: implement reblogs & notify params
|
//FIXME: implement reblogs & notify params
|
||||||
|
const localUser = ctx.user as ILocalUser;
|
||||||
const following = await Followings.exist({ where: { followerId: localUser.id, followeeId: target.id } });
|
const following = await Followings.exist({ where: { followerId: localUser.id, followeeId: target.id } });
|
||||||
const requested = await FollowRequests.exist({ where: { followerId: localUser.id, followeeId: target.id } });
|
const requested = await FollowRequests.exist({ where: { followerId: localUser.id, followeeId: target.id } });
|
||||||
if (!following && !requested)
|
if (!following && !requested)
|
||||||
|
@ -71,7 +72,8 @@ export class UserHelpers {
|
||||||
return this.getUserRelationshipTo(target.id, localUser.id);
|
return this.getUserRelationshipTo(target.id, localUser.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async unfollowUser(target: User, localUser: ILocalUser): Promise<MastodonEntity.Relationship> {
|
public static async unfollowUser(target: User, ctx: MastoContext): Promise<MastodonEntity.Relationship> {
|
||||||
|
const localUser = ctx.user as ILocalUser;
|
||||||
const following = await Followings.exist({ where: { followerId: localUser.id, followeeId: target.id } });
|
const following = await Followings.exist({ where: { followerId: localUser.id, followeeId: target.id } });
|
||||||
const requested = await FollowRequests.exist({ where: { followerId: localUser.id, followeeId: target.id } });
|
const requested = await FollowRequests.exist({ where: { followerId: localUser.id, followeeId: target.id } });
|
||||||
if (following)
|
if (following)
|
||||||
|
@ -82,7 +84,8 @@ export class UserHelpers {
|
||||||
return this.getUserRelationshipTo(target.id, localUser.id);
|
return this.getUserRelationshipTo(target.id, localUser.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async blockUser(target: User, localUser: ILocalUser): Promise<MastodonEntity.Relationship> {
|
public static async blockUser(target: User, ctx: MastoContext): Promise<MastodonEntity.Relationship> {
|
||||||
|
const localUser = ctx.user as ILocalUser;
|
||||||
const blocked = await Blockings.exist({ where: { blockerId: localUser.id, blockeeId: target.id } });
|
const blocked = await Blockings.exist({ where: { blockerId: localUser.id, blockeeId: target.id } });
|
||||||
if (!blocked)
|
if (!blocked)
|
||||||
await createBlocking(localUser, target);
|
await createBlocking(localUser, target);
|
||||||
|
@ -90,7 +93,8 @@ export class UserHelpers {
|
||||||
return this.getUserRelationshipTo(target.id, localUser.id);
|
return this.getUserRelationshipTo(target.id, localUser.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async unblockUser(target: User, localUser: ILocalUser): Promise<MastodonEntity.Relationship> {
|
public static async unblockUser(target: User, ctx: MastoContext): Promise<MastodonEntity.Relationship> {
|
||||||
|
const localUser = ctx.user as ILocalUser;
|
||||||
const blocked = await Blockings.exist({ where: { blockerId: localUser.id, blockeeId: target.id } });
|
const blocked = await Blockings.exist({ where: { blockerId: localUser.id, blockeeId: target.id } });
|
||||||
if (blocked)
|
if (blocked)
|
||||||
await deleteBlocking(localUser, target);
|
await deleteBlocking(localUser, target);
|
||||||
|
@ -98,8 +102,9 @@ export class UserHelpers {
|
||||||
return this.getUserRelationshipTo(target.id, localUser.id);
|
return this.getUserRelationshipTo(target.id, localUser.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async muteUser(target: User, localUser: ILocalUser, notifications: boolean = true, duration: number = 0): Promise<MastodonEntity.Relationship> {
|
public static async muteUser(target: User, notifications: boolean = true, duration: number = 0, ctx: MastoContext): Promise<MastodonEntity.Relationship> {
|
||||||
//FIXME: respect notifications parameter
|
//FIXME: respect notifications parameter
|
||||||
|
const localUser = ctx.user as ILocalUser;
|
||||||
const muted = await Mutings.exist({ where: { muterId: localUser.id, muteeId: target.id } });
|
const muted = await Mutings.exist({ where: { muterId: localUser.id, muteeId: target.id } });
|
||||||
if (!muted) {
|
if (!muted) {
|
||||||
await Mutings.insert({
|
await Mutings.insert({
|
||||||
|
@ -121,7 +126,8 @@ export class UserHelpers {
|
||||||
return this.getUserRelationshipTo(target.id, localUser.id);
|
return this.getUserRelationshipTo(target.id, localUser.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async unmuteUser(target: User, localUser: ILocalUser): Promise<MastodonEntity.Relationship> {
|
public static async unmuteUser(target: User, ctx: MastoContext): Promise<MastodonEntity.Relationship> {
|
||||||
|
const localUser = ctx.user as ILocalUser;
|
||||||
const muting = await Mutings.findOneBy({ muterId: localUser.id, muteeId: target.id });
|
const muting = await Mutings.findOneBy({ muterId: localUser.id, muteeId: target.id });
|
||||||
if (muting) {
|
if (muting) {
|
||||||
await Mutings.delete({
|
await Mutings.delete({
|
||||||
|
@ -134,21 +140,27 @@ export class UserHelpers {
|
||||||
return this.getUserRelationshipTo(target.id, localUser.id);
|
return this.getUserRelationshipTo(target.id, localUser.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async acceptFollowRequest(target: User, localUser: ILocalUser): Promise<MastodonEntity.Relationship> {
|
public static async acceptFollowRequest(target: User, ctx: MastoContext): Promise<MastodonEntity.Relationship> {
|
||||||
|
const localUser = ctx.user as ILocalUser;
|
||||||
const pending = await FollowRequests.exist({ where: { followerId: target.id, followeeId: localUser.id } });
|
const pending = await FollowRequests.exist({ where: { followerId: target.id, followeeId: localUser.id } });
|
||||||
if (pending)
|
if (pending)
|
||||||
await acceptFollowRequest(localUser, target);
|
await acceptFollowRequest(localUser, target);
|
||||||
return this.getUserRelationshipTo(target.id, localUser.id);
|
return this.getUserRelationshipTo(target.id, localUser.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async rejectFollowRequest(target: User, localUser: ILocalUser): Promise<MastodonEntity.Relationship> {
|
public static async rejectFollowRequest(target: User, ctx: MastoContext): Promise<MastodonEntity.Relationship> {
|
||||||
|
const localUser = ctx.user as ILocalUser;
|
||||||
const pending = await FollowRequests.exist({ where: { followerId: target.id, followeeId: localUser.id } });
|
const pending = await FollowRequests.exist({ where: { followerId: target.id, followeeId: localUser.id } });
|
||||||
if (pending)
|
if (pending)
|
||||||
await rejectFollowRequest(localUser, target);
|
await rejectFollowRequest(localUser, target);
|
||||||
return this.getUserRelationshipTo(target.id, localUser.id);
|
return this.getUserRelationshipTo(target.id, localUser.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async updateCredentials(user: ILocalUser, formData: updateCredsData, files: Files | undefined, ctx: MastoContext): Promise<MastodonEntity.Account> {
|
public static async updateCredentials(ctx: MastoContext): Promise<MastodonEntity.Account> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
|
const files = (ctx.request as any).files as Files | undefined;
|
||||||
|
const formData = (ctx.request as any).body as updateCredsData;
|
||||||
|
|
||||||
const updates: Partial<User> = {};
|
const updates: Partial<User> = {};
|
||||||
const profileUpdates: Partial<UserProfile> = {};
|
const profileUpdates: Partial<UserProfile> = {};
|
||||||
|
|
||||||
|
@ -156,12 +168,12 @@ export class UserHelpers {
|
||||||
const header = toSingleLast(files?.header);
|
const header = toSingleLast(files?.header);
|
||||||
|
|
||||||
if (avatar) {
|
if (avatar) {
|
||||||
const file = await MediaHelpers.uploadMediaBasic(user, avatar);
|
const file = await MediaHelpers.uploadMediaBasic(avatar, ctx);
|
||||||
updates.avatarId = file.id;
|
updates.avatarId = file.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header) {
|
if (header) {
|
||||||
const file = await MediaHelpers.uploadMediaBasic(user, header);
|
const file = await MediaHelpers.uploadMediaBasic(header, ctx);
|
||||||
updates.bannerId = file.id;
|
updates.bannerId = file.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,13 +196,14 @@ export class UserHelpers {
|
||||||
if (Object.keys(updates).length > 0) await Users.update(user.id, updates);
|
if (Object.keys(updates).length > 0) await Users.update(user.id, updates);
|
||||||
if (Object.keys(profileUpdates).length > 0) await UserProfiles.update({ userId: user.id }, profileUpdates);
|
if (Object.keys(profileUpdates).length > 0) await UserProfiles.update({ userId: user.id }, profileUpdates);
|
||||||
|
|
||||||
return this.verifyCredentials(user, ctx);
|
return this.verifyCredentials(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async verifyCredentials(user: ILocalUser, ctx: MastoContext): Promise<MastodonEntity.Account> {
|
public static async verifyCredentials(ctx: MastoContext): Promise<MastodonEntity.Account> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
const acct = UserConverter.encode(user, ctx);
|
const acct = UserConverter.encode(user, ctx);
|
||||||
const profile = UserProfiles.findOneByOrFail({ userId: user.id });
|
const profile = UserProfiles.findOneByOrFail({ userId: user.id });
|
||||||
const privacy = this.getDefaultNoteVisibility(user);
|
const privacy = this.getDefaultNoteVisibility(ctx);
|
||||||
const fields = profile.then(profile => profile.fields.map(field => {
|
const fields = profile.then(profile => profile.fields.map(field => {
|
||||||
return {
|
return {
|
||||||
name: field.name,
|
name: field.name,
|
||||||
|
@ -225,9 +238,10 @@ export class UserHelpers {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getUserMutes(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<LinkPaginationObject<MastodonEntity.MutedAccount[]>> {
|
public static async getUserMutes(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<LinkPaginationObject<MastodonEntity.MutedAccount[]>> {
|
||||||
if (limit > 80) limit = 80;
|
if (limit > 80) limit = 80;
|
||||||
|
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
const query = PaginationHelpers.makePaginationQuery(
|
const query = PaginationHelpers.makePaginationQuery(
|
||||||
Mutings.createQueryBuilder("muting"),
|
Mutings.createQueryBuilder("muting"),
|
||||||
sinceId,
|
sinceId,
|
||||||
|
@ -260,9 +274,10 @@ export class UserHelpers {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getUserBlocks(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise<LinkPaginationObject<User[]>> {
|
public static async getUserBlocks(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<LinkPaginationObject<User[]>> {
|
||||||
if (limit > 80) limit = 80;
|
if (limit > 80) limit = 80;
|
||||||
|
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
const query = PaginationHelpers.makePaginationQuery(
|
const query = PaginationHelpers.makePaginationQuery(
|
||||||
Blockings.createQueryBuilder("blocking"),
|
Blockings.createQueryBuilder("blocking"),
|
||||||
sinceId,
|
sinceId,
|
||||||
|
@ -286,9 +301,10 @@ export class UserHelpers {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getUserFollowRequests(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise<LinkPaginationObject<User[]>> {
|
public static async getUserFollowRequests(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<LinkPaginationObject<User[]>> {
|
||||||
if (limit > 80) limit = 80;
|
if (limit > 80) limit = 80;
|
||||||
|
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
const query = PaginationHelpers.makePaginationQuery(
|
const query = PaginationHelpers.makePaginationQuery(
|
||||||
FollowRequests.createQueryBuilder("request"),
|
FollowRequests.createQueryBuilder("request"),
|
||||||
sinceId,
|
sinceId,
|
||||||
|
@ -312,8 +328,9 @@ export class UserHelpers {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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<LinkPaginationObject<Note[]>> {
|
public static async getUserStatuses(user: User, 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, ctx: MastoContext): Promise<LinkPaginationObject<Note[]>> {
|
||||||
if (limit > 40) limit = 40;
|
if (limit > 40) limit = 40;
|
||||||
|
const localUser = ctx.user as ILocalUser | null;
|
||||||
|
|
||||||
if (tagged !== undefined) {
|
if (tagged !== undefined) {
|
||||||
//FIXME respect tagged
|
//FIXME respect tagged
|
||||||
|
@ -373,9 +390,10 @@ export class UserHelpers {
|
||||||
return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined);
|
return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getUserBookmarks(localUser: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20): Promise<LinkPaginationObject<Note[]>> {
|
public static async getUserBookmarks(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise<LinkPaginationObject<Note[]>> {
|
||||||
if (limit > 40) limit = 40;
|
if (limit > 40) limit = 40;
|
||||||
|
|
||||||
|
const localUser = ctx.user as ILocalUser;
|
||||||
const query = PaginationHelpers.makePaginationQuery(
|
const query = PaginationHelpers.makePaginationQuery(
|
||||||
NoteFavorites.createQueryBuilder("favorite"),
|
NoteFavorites.createQueryBuilder("favorite"),
|
||||||
sinceId,
|
sinceId,
|
||||||
|
@ -396,9 +414,10 @@ export class UserHelpers {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getUserFavorites(localUser: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20): Promise<LinkPaginationObject<Note[]>> {
|
public static async getUserFavorites(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise<LinkPaginationObject<Note[]>> {
|
||||||
if (limit > 40) limit = 40;
|
if (limit > 40) limit = 40;
|
||||||
|
|
||||||
|
const localUser = ctx.user as ILocalUser;
|
||||||
const query = PaginationHelpers.makePaginationQuery(
|
const query = PaginationHelpers.makePaginationQuery(
|
||||||
NoteReactions.createQueryBuilder("reaction"),
|
NoteReactions.createQueryBuilder("reaction"),
|
||||||
sinceId,
|
sinceId,
|
||||||
|
@ -419,9 +438,10 @@ export class UserHelpers {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async getUserRelationships(type: RelationshipType, user: User, localUser: ILocalUser | null, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise<LinkPaginationObject<User[]>> {
|
private static async getUserRelationships(type: RelationshipType, user: User, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<LinkPaginationObject<User[]>> {
|
||||||
if (limit > 80) limit = 80;
|
if (limit > 80) limit = 80;
|
||||||
|
|
||||||
|
const localUser = ctx.user as ILocalUser | null;
|
||||||
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
|
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
|
||||||
if (profile.ffVisibility === "private") {
|
if (profile.ffVisibility === "private") {
|
||||||
if (!localUser || user.id !== localUser.id) return { data: [] };
|
if (!localUser || user.id !== localUser.id) return { data: [] };
|
||||||
|
@ -463,12 +483,12 @@ export class UserHelpers {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getUserFollowers(user: User, localUser: ILocalUser | null, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise<LinkPaginationObject<User[]>> {
|
public static async getUserFollowers(user: User, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<LinkPaginationObject<User[]>> {
|
||||||
return this.getUserRelationships('followers', user, localUser, maxId, sinceId, minId, limit);
|
return this.getUserRelationships('followers', user, maxId, sinceId, minId, limit, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getUserFollowing(user: User, localUser: ILocalUser | null, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise<LinkPaginationObject<User[]>> {
|
public static async getUserFollowing(user: User, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<LinkPaginationObject<User[]>> {
|
||||||
return this.getUserRelationships('following', user, localUser, maxId, sinceId, minId, limit);
|
return this.getUserRelationships('following', user, maxId, sinceId, minId, limit, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getUserRelationhipToMany(targetIds: string[], localUserId: string): Promise<MastodonEntity.Relationship[]> {
|
public static async getUserRelationhipToMany(targetIds: string[], localUserId: string): Promise<MastodonEntity.Relationship[]> {
|
||||||
|
@ -528,7 +548,8 @@ export class UserHelpers {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getDefaultNoteVisibility(user: ILocalUser): Promise<IceshrimpVisibility> {
|
public static async getDefaultNoteVisibility(ctx: MastoContext): Promise<IceshrimpVisibility> {
|
||||||
|
const user = ctx.user as ILocalUser;
|
||||||
return RegistryItems.findOneBy({
|
return RegistryItems.findOneBy({
|
||||||
domain: IsNull(),
|
domain: IsNull(),
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
|
Loading…
Reference in a new issue