[mastodon-client] PUT /statuses/:id

This commit is contained in:
Laura Hausmann 2023-09-30 18:28:41 +02:00
parent 0719d9abbc
commit b0487e1e63
No known key found for this signature in database
GPG Key ID: D044E84C5BE01605
3 changed files with 89 additions and 44 deletions

View File

@ -1,10 +1,9 @@
import Router from "@koa/router"; import Router from "@koa/router";
import { getClient } from "../index.js"; import { getClient } from "../index.js";
import { emojiRegexAtStartToEnd } from "@/misc/emoji-regex.js";
import querystring from "node:querystring"; import querystring from "node:querystring";
import qs from "qs"; import qs from "qs";
import { convertId, IdType } from "../../index.js"; import { convertId, IdType } from "../../index.js";
import { convertAccount, convertAttachment, convertPoll, convertStatus, } from "../converters.js"; import { convertAccount, convertPoll, convertStatus, } from "../converters.js";
import { NoteConverter } from "@/server/api/mastodon/converters/note.js"; import { NoteConverter } from "@/server/api/mastodon/converters/note.js";
import { getNote } from "@/server/api/common/getters.js"; import { getNote } from "@/server/api/common/getters.js";
import authenticate from "@/server/api/authenticate.js"; import authenticate from "@/server/api/authenticate.js";
@ -14,7 +13,6 @@ import { convertPaginationArgsIds, limitToInt, normalizeUrlQuery } from "@/serve
import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js"; import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js";
import { UserConverter } from "@/server/api/mastodon/converters/user.js"; import { UserConverter } from "@/server/api/mastodon/converters/user.js";
import { Cache } from "@/misc/cache.js"; import { Cache } from "@/misc/cache.js";
import { redisClient } from "@/db/redis.js";
import AsyncLock from "async-lock"; import AsyncLock from "async-lock";
import { ILocalUser } from "@/models/entities/user.js"; import { ILocalUser } from "@/models/entities/user.js";
@ -55,49 +53,31 @@ export function setupEndpointsStatus(router: Router): void {
} }
}); });
router.put("/v1/statuses/:id", async (ctx) => { router.put("/v1/statuses/:id", async (ctx) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try { try {
ctx.params.id = convertId(ctx.params.id, IdType.IceshrimpId); const auth = await authenticate(ctx.headers.authorization, null);
let body: any = ctx.request.body; const user = auth[0] ?? null;
if (
(!body.poll && body["poll[options][]"]) ||
(!body.media_ids && body["media_ids[]"])
) {
body = normalizeQuery(body);
}
if (!body.media_ids) body.media_ids = undefined;
if (body.media_ids && !body.media_ids.length) body.media_ids = undefined;
if (body.media_ids) {
body.media_ids = (body.media_ids as string[]).map((p) =>
convertId(p, IdType.IceshrimpId),
);
}
const {sensitive} = body;
body.sensitive =
typeof sensitive === "string" ? sensitive === "true" : sensitive;
if (body.poll) { if (!user) {
if ( ctx.status = 401;
body.poll.expires_in != null && return;
typeof body.poll.expires_in === "string"
)
body.poll.expires_in = parseInt(body.poll.expires_in);
if (
body.poll.multiple != null &&
typeof body.poll.multiple === "string"
)
body.poll.multiple = body.poll.multiple == "true";
if (
body.poll.hide_totals != null &&
typeof body.poll.hide_totals === "string"
)
body.poll.hide_totals = body.poll.hide_totals == "true";
} }
const data = await client.editStatus(ctx.params.id, body); const noteId = convertId(ctx.params.id, IdType.IceshrimpId);
ctx.body = convertStatus(data.data); const note = await getNote(noteId, user ?? null).then(n => n).catch(() => null);
if (!note) {
if (!note) {
ctx.status = 404;
ctx.body = {
error: "Note not found"
};
return;
}
}
let request = NoteHelpers.normalizeEditOptions(ctx.request.body);
ctx.body = await NoteHelpers.editNote(request, note, user)
.then(p => NoteConverter.encode(p, user))
.then(p => convertStatus(p));
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = ctx.status == 404 ? 404 : 401; ctx.status = ctx.status == 404 ? 404 : 401;
@ -142,7 +122,7 @@ export function setupEndpointsStatus(router: Router): void {
ctx.status = 404; ctx.status = 404;
ctx.body = { ctx.body = {
error: "Note not found" error: "Note not found"
} };
return; return;
} }
@ -150,7 +130,7 @@ export function setupEndpointsStatus(router: Router): void {
ctx.status = 403; ctx.status = 403;
ctx.body = { ctx.body = {
error: "Cannot delete someone else's note" error: "Cannot delete someone else's note"
} };
return; return;
} }

View File

@ -58,4 +58,17 @@ namespace MastodonEntity {
language?: string, language?: string,
scheduled_at?: Date scheduled_at?: Date
} }
export type StatusEditRequest = {
text?: string,
media_ids?: string[],
poll?: {
options: string[],
expires_in: number,
multiple: boolean
},
sensitive?: boolean,
spoiler_text?: string,
language?: string
}
} }

View File

@ -18,6 +18,7 @@ import { getNote } from "@/server/api/common/getters.js";
import createReaction from "@/services/note/reaction/create.js"; import createReaction from "@/services/note/reaction/create.js";
import deleteReaction from "@/services/note/reaction/delete.js"; import deleteReaction from "@/services/note/reaction/delete.js";
import createNote, { extractMentionedUsers } from "@/services/note/create.js"; import createNote, { extractMentionedUsers } from "@/services/note/create.js";
import editNote from "@/services/note/edit.js";
import deleteNote from "@/services/note/delete.js"; import deleteNote from "@/services/note/delete.js";
import { genId } from "@/misc/gen-id.js"; import { genId } from "@/misc/gen-id.js";
import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js"; import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js";
@ -249,6 +250,27 @@ export class NoteHelpers {
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> {
const files = request.media_ids && request.media_ids.length > 0
? DriveFiles.findByIds(request.media_ids)
: [];
const data = {
files: files,
poll: request.poll
? {
choices: request.poll.options,
multiple: request.poll.multiple,
expiresAt: request.poll.expires_in && request.poll.expires_in > 0 ? new Date(new Date().getTime() + (request.poll.expires_in * 1000)) : null,
}
: undefined,
text: request.text,
cw: request.spoiler_text
}
return editNote(user, note, await awaitAll(data));
}
public static async extractMentions(text: string, user: ILocalUser): Promise<User[]> { public static async extractMentions(text: string, user: ILocalUser): Promise<User[]> {
return extractMentionedUsers(user, mfm.parse(text)!); return extractMentionedUsers(user, mfm.parse(text)!);
} }
@ -289,6 +311,36 @@ export class NoteHelpers {
return result; return result;
} }
public static normalizeEditOptions(body: any): MastodonEntity.StatusEditRequest {
const result: MastodonEntity.StatusEditRequest = {};
body = qs.parse(querystring.stringify(body));
if (body.status !== null)
result.text = body.status;
if (body.spoiler_text !== null)
result.spoiler_text = body.spoiler_text;
if (body.language !== null)
result.language = body.language;
if (body.media_ids)
result.media_ids = body.media_ids && body.media_ids.length > 0
? this.normalizeToArray(body.media_ids)
.map(p => convertId(p, IdType.IceshrimpId))
: undefined;
if (body.poll) {
result.poll = {
expires_in: parseInt(body.poll.expires_in, 10),
options: body.poll.options,
multiple: !!body.poll.multiple,
}
}
result.sensitive = !!body.sensitive;
return result;
}
private static normalizeToArray<T>(subject: T | T[]) { private static normalizeToArray<T>(subject: T | T[]) {
return Array.isArray(subject) ? subject : [subject]; return Array.isArray(subject) ? subject : [subject];
} }