Merge pull request '[PR]: Next batch of masto-client improvements' (#10440) from e2net/calckey:masto-client-improvements into develop

Reviewed-on: https://codeberg.org/calckey/calckey/pulls/10440
This commit is contained in:
Kainoa Kanter 2023-07-08 23:00:09 +00:00
commit 80b60af9f4
6 changed files with 62 additions and 127 deletions

View file

@ -33,7 +33,7 @@ export const paramDef = {
} as const; } as const;
export default define(meta, paramDef, async (ps, user) => { export default define(meta, paramDef, async (ps, user) => {
if (ps.key !== "reactions") return; if (ps.key !== "reactions" && ps.key !== "defaultNoteVisibility") return;
const query = RegistryItems.createQueryBuilder("item") const query = RegistryItems.createQueryBuilder("item")
.where("item.domain IS NULL") .where("item.domain IS NULL")
.andWhere("item.userId = :userId", { userId: user.id }) .andWhere("item.userId = :userId", { userId: user.id })

View file

@ -112,7 +112,7 @@ mastoFileRouter.post("/v2/media", upload.single("file"), async (ctx) => {
ctx.status = 401; ctx.status = 401;
return; return;
} }
const data = await client.uploadMedia(multipartData); const data = await client.uploadMedia(multipartData, ctx.request.body);
ctx.body = convertAttachment(data.data as Entity.Attachment); ctx.body = convertAttachment(data.data as Entity.Attachment);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);

View file

@ -48,7 +48,7 @@ export function apiAccountMastodon(router: Router): void {
acct.source = { acct.source = {
note: acct.note, note: acct.note,
fields: acct.fields, fields: acct.fields,
privacy: "public", privacy: await client.getDefaultPostPrivacy(),
sensitive: false, sensitive: false,
language: "", language: "",
}; };

View file

@ -123,27 +123,7 @@ export function apiStatusMastodon(router: Router): void {
id, id,
convertTimelinesArgsId(limitToInt(ctx.query as any)), convertTimelinesArgsId(limitToInt(ctx.query as any)),
); );
const status = await client.getStatus(id);
let reqInstance = axios.create({
headers: {
Authorization: ctx.headers.authorization,
},
});
const reactionsAxios = await reqInstance.get(
`${BASE_URL}/api/notes/reactions?noteId=${id}`,
);
const reactions: IReaction[] = reactionsAxios.data;
const text = reactions
.map((r) => `${r.type.replace("@.", "")} ${r.user.username}`)
.join("<br />");
data.data.descendants.unshift(
statusModel(
status.data.id,
status.data.account.id,
status.data.emojis,
text,
),
);
data.data.ancestors = data.data.ancestors.map((status) => data.data.ancestors = data.data.ancestors.map((status) =>
convertStatus(status), convertStatus(status),
); );
@ -456,65 +436,3 @@ async function getFirstReaction(
return react; return react;
} }
} }
export function statusModel(
id: string | null,
acctId: string | null,
emojis: MastodonEntity.Emoji[],
content: string,
) {
const now = new Date().toISOString();
return {
id: "9atm5frjhb",
uri: "/static-assets/transparent.png", // ""
url: "/static-assets/transparent.png", // "",
account: {
id: "9arzuvv0sw",
username: "Reactions",
acct: "Reactions",
display_name: "Reactions to this post",
locked: false,
created_at: now,
followers_count: 0,
following_count: 0,
statuses_count: 0,
note: "",
url: "/static-assets/transparent.png",
avatar: "/static-assets/badges/info.png",
avatar_static: "/static-assets/badges/info.png",
header: "/static-assets/transparent.png", // ""
header_static: "/static-assets/transparent.png", // ""
emojis: [],
fields: [],
moved: null,
bot: false,
},
in_reply_to_id: id,
in_reply_to_account_id: acctId,
reblog: null,
content: `<p>${content}</p>`,
plain_content: null,
created_at: now,
emojis: emojis,
replies_count: 0,
reblogs_count: 0,
favourites_count: 0,
favourited: false,
reblogged: false,
muted: false,
sensitive: false,
spoiler_text: "",
visibility: "public" as const,
media_attachments: [],
mentions: [],
tags: [],
card: null,
poll: null,
application: null,
language: null,
pinned: false,
emoji_reactions: [],
bookmarked: false,
quote: null,
};
}

View file

@ -460,7 +460,7 @@ export default class Misskey implements MegalodonInterface {
if (options) { if (options) {
if (options.limit) { if (options.limit) {
params = Object.assign(params, { params = Object.assign(params, {
limit: options.limit limit: options.limit <= 100 ? options.limit : 100
}) })
} }
else { else {
@ -474,11 +474,11 @@ export default class Misskey implements MegalodonInterface {
limit: 40 limit: 40
}) })
} }
return this.client.post<Array<MisskeyAPI.Entity.Follower>>('/api/users/followers', params).then(res => { return this.client.post<Array<MisskeyAPI.Entity.Follower>>('/api/users/followers', params).then(async res => {
return Object.assign(res, { return Object.assign(res, {
data: res.data.map(f => this.converter.follower(f)) data: (await Promise.all(res.data.map(async f => (this.getAccount(f.followerId)).then(p => p.data))))
}) })
}) })
} }
/** /**
@ -498,15 +498,15 @@ export default class Misskey implements MegalodonInterface {
if (options) { if (options) {
if (options.limit) { if (options.limit) {
params = Object.assign(params, { params = Object.assign(params, {
limit: options.limit limit: options.limit <= 100 ? options.limit : 100
}) })
} }
} }
return this.client.post<Array<MisskeyAPI.Entity.Following>>('/api/users/following', params).then(res => { return this.client.post<Array<MisskeyAPI.Entity.Following>>('/api/users/following', params).then(async res => {
return Object.assign(res, { return Object.assign(res, {
data: res.data.map(f => this.converter.following(f)) data: (await Promise.all(res.data.map(async f => (this.getAccount(f.followeeId)).then(p => p.data))))
}) })
}) })
} }
public async getAccountLists(_id: string): Promise<Response<Array<Entity.List>>> { public async getAccountLists(_id: string): Promise<Response<Array<Entity.List>>> {
@ -1079,23 +1079,11 @@ export default class Misskey implements MegalodonInterface {
// accounts/preferences // accounts/preferences
// ====================================== // ======================================
public async getPreferences(): Promise<Response<Entity.Preferences>> { public async getPreferences(): Promise<Response<Entity.Preferences>> {
return this.client.post<MisskeyAPI.Entity.UserDetailMe>('/api/i').then(res => { return this.client.post<MisskeyAPI.Entity.UserDetailMe>('/api/i').then(async res => {
/* return Object.assign(res, {
return this.client.post<MisskeyAPI.Entity.GetAll>('/api/i/registry/get-all', { data: this.converter.userPreferences(res.data, await this.getDefaultPostPrivacy())
scope: ['client', 'base'], })
}).then(ga => { })
return Object.assign(res, {
data: this.converter.userPreferences(res.data, ga.data)
})
})
*/
// TODO:
// FIXME: get this from api
return Object.assign(res, {
data: this.converter.userPreferences(res.data, {defaultNoteVisibility: "followers", tutorial: -1})
})
})
} }
// ====================================== // ======================================
@ -1529,6 +1517,23 @@ export default class Misskey implements MegalodonInterface {
.then(res => res.data[0] ?? '⭐'); .then(res => res.data[0] ?? '⭐');
} }
private async getDefaultPostPrivacy(): Promise<'public' | 'unlisted' | 'private' | 'direct'> {
// NOTE: get-unsecure is calckey's extension.
// Misskey doesn't have this endpoint and regular `/i/registry/get` won't work
// unless you have a 'nativeToken', which is reserved for the frontend webapp.
return this.client
.post<string>('/api/i/registry/get-unsecure', {
key: 'defaultNoteVisibility',
scope: ['client', 'base'],
})
.then(res => {
if (!res.data || (res.data != 'public' && res.data != 'home' && res.data != 'followers' && res.data != 'specified'))
return 'public';
return this.converter.visibility(res.data);
});
}
public async unfavouriteStatus(id: string): Promise<Response<Entity.Status>> { public async unfavouriteStatus(id: string): Promise<Response<Entity.Status>> {
// NOTE: Misskey allows only one reaction per status, so we don't need to care what that emoji was. // NOTE: Misskey allows only one reaction per status, so we don't need to care what that emoji was.
return this.deleteEmojiReaction(id, ''); return this.deleteEmojiReaction(id, '');
@ -1638,20 +1643,26 @@ export default class Misskey implements MegalodonInterface {
/** /**
* POST /api/drive/files/create * POST /api/drive/files/create
*/ */
public async uploadMedia(file: any, _options?: { description?: string; focus?: string }): Promise<Response<Entity.Attachment>> { public async uploadMedia(file: any, options?: { description?: string; focus?: string }): Promise<Response<Entity.Attachment>> {
const formData = new FormData() const formData = new FormData()
formData.append('file', fs.createReadStream(file.path), { formData.append('file', fs.createReadStream(file.path), {
contentType: file.mimetype, contentType: file.mimetype
filename: file.originalname, })
})
if (file.originalname != null && file.originalname !== 'file')
formData.append('name', file.originalname);
if (options?.description != null)
formData.append('comment', options.description);
let headers: { [key: string]: string } = {} let headers: { [key: string]: string } = {}
if (typeof formData.getHeaders === 'function') { if (typeof formData.getHeaders === 'function') {
headers = formData.getHeaders() headers = formData.getHeaders()
} }
return this.client return this.client
.post<MisskeyAPI.Entity.File>('/api/drive/files/create', formData, headers) .post<MisskeyAPI.Entity.File>('/api/drive/files/create', formData, headers)
.then(res => ({ ...res, data: this.converter.file(res.data) })) .then(res => ({ ...res, data: this.converter.file(res.data) }))
} }
public async getMedia(id: string): Promise<Response<Entity.Attachment>> { public async getMedia(id: string): Promise<Response<Entity.Attachment>> {
const res = await this.client.post<MisskeyAPI.Entity.File>('/api/drive/files/show', { fileId: id }) const res = await this.client.post<MisskeyAPI.Entity.File>('/api/drive/files/show', { fileId: id })
@ -1679,6 +1690,12 @@ export default class Misskey implements MegalodonInterface {
isSensitive: options.is_sensitive isSensitive: options.is_sensitive
}) })
} }
if (options.description !== undefined) {
params = Object.assign(params, {
comment: options.description
})
}
} }
return this.client return this.client
.post<MisskeyAPI.Entity.File>('/api/drive/files/update', params) .post<MisskeyAPI.Entity.File>('/api/drive/files/update', params)

View file

@ -134,8 +134,8 @@ namespace MisskeyAPI {
url: acctUrl, url: acctUrl,
avatar: u.avatarUrl, avatar: u.avatarUrl,
avatar_static: u.avatarUrl, avatar_static: u.avatarUrl,
header: this.plcUrl, // FIXME header: this.plcUrl,
header_static: this.plcUrl, // FIXME header_static: this.plcUrl,
emojis: u.emojis.map(e => this.emoji(e)), emojis: u.emojis.map(e => this.emoji(e)),
moved: null, moved: null,
fields: [], fields: [],
@ -174,13 +174,13 @@ namespace MisskeyAPI {
} }
} }
userPreferences = (u: MisskeyAPI.Entity.UserDetailMe, g: MisskeyAPI.Entity.GetAll): MegalodonEntity.Preferences => { userPreferences = (u: MisskeyAPI.Entity.UserDetailMe, v: 'public' | 'unlisted' | 'private' | 'direct'): MegalodonEntity.Preferences => {
return { return {
"reading:expand:media": "default", "reading:expand:media": "default",
"reading:expand:spoilers": false, "reading:expand:spoilers": false,
"posting:default:language": u.lang, "posting:default:language": u.lang,
"posting:default:sensitive": u.alwaysMarkNsfw, "posting:default:sensitive": u.alwaysMarkNsfw,
"posting:default:visibility": this.visibility(g.defaultNoteVisibility) "posting:default:visibility": v
} }
} }
@ -308,7 +308,7 @@ namespace MisskeyAPI {
emojis: n.emojis.map(e => this.emoji(e)), emojis: n.emojis.map(e => this.emoji(e)),
replies_count: n.repliesCount, replies_count: n.repliesCount,
reblogs_count: n.renoteCount, reblogs_count: n.renoteCount,
favourites_count: this.getTotalReactions(n.reactions), // FIXME: instead get # of default reaction emoji reactions favourites_count: this.getTotalReactions(n.reactions),
reblogged: false, reblogged: false,
favourited: !!n.myReaction, favourited: !!n.myReaction,
muted: false, muted: false,