mirror of
https://activitypub.software/TransFem-org/Sharkey
synced 2024-12-22 16:30:13 +00:00
upd: add history endpoint, make sure all areas use new convertAccount
This commit is contained in:
parent
8fd669ff7d
commit
c53323d237
6 changed files with 104 additions and 28 deletions
|
@ -8,7 +8,7 @@ import { DI } from '@/di-symbols.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
import { convertAccount, convertAnnouncement, convertFilter, convertAttachment, convertFeaturedTag, convertList, MastoConverters } from './converters.js';
|
import { convertAnnouncement, convertFilter, convertAttachment, convertFeaturedTag, convertList, MastoConverters } from './converters.js';
|
||||||
import { getInstance } from './endpoints/meta.js';
|
import { getInstance } from './endpoints/meta.js';
|
||||||
import { ApiAuthMastodon, ApiAccountMastodon, ApiFilterMastodon, ApiNotifyMastodon, ApiSearchMastodon, ApiTimelineMastodon, ApiStatusMastodon } from './endpoints.js';
|
import { ApiAuthMastodon, ApiAccountMastodon, ApiFilterMastodon, ApiNotifyMastodon, ApiSearchMastodon, ApiTimelineMastodon, ApiStatusMastodon } from './endpoints.js';
|
||||||
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
||||||
|
@ -102,8 +102,8 @@ export class MastodonApiServerService {
|
||||||
},
|
},
|
||||||
order: { id: 'ASC' },
|
order: { id: 'ASC' },
|
||||||
});
|
});
|
||||||
const contact = admin == null ? null : convertAccount((await client.getAccount(admin.id)).data);
|
const contact = admin == null ? null : await this.mastoConverter.convertAccount((await client.getAccount(admin.id)).data);
|
||||||
reply.send(await getInstance(data.data, contact, this.config, await this.metaService.fetch()));
|
reply.send(await getInstance(data.data, contact as Entity.Account, this.config, await this.metaService.fetch()));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e); */
|
/* console.error(e); */
|
||||||
reply.code(401).send(e.response.data);
|
reply.code(401).send(e.response.data);
|
||||||
|
@ -252,7 +252,7 @@ export class MastodonApiServerService {
|
||||||
// displayed without being logged in
|
// displayed without being logged in
|
||||||
try {
|
try {
|
||||||
const data = await client.updateCredentials(_request.body!);
|
const data = await client.updateCredentials(_request.body!);
|
||||||
reply.send(convertAccount(data.data));
|
reply.send(await this.mastoConverter.convertAccount(data.data));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e); */
|
/* console.error(e); */
|
||||||
reply.code(401).send(e.response.data);
|
reply.code(401).send(e.response.data);
|
||||||
|
@ -268,7 +268,7 @@ export class MastodonApiServerService {
|
||||||
const data = await client.search((_request.query as any).acct, { type: 'accounts' });
|
const data = await client.search((_request.query as any).acct, { type: 'accounts' });
|
||||||
const profile = await this.userProfilesRepository.findOneBy({ userId: data.data.accounts[0].id });
|
const profile = await this.userProfilesRepository.findOneBy({ userId: data.data.accounts[0].id });
|
||||||
data.data.accounts[0].fields = profile?.fields.map(f => ({ ...f, verified_at: null })) || [];
|
data.data.accounts[0].fields = profile?.fields.map(f => ({ ...f, verified_at: null })) || [];
|
||||||
reply.send(convertAccount(data.data.accounts[0]));
|
reply.send(await this.mastoConverter.convertAccount(data.data.accounts[0]));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e); */
|
/* console.error(e); */
|
||||||
reply.code(401).send(e.response.data);
|
reply.code(401).send(e.response.data);
|
||||||
|
@ -544,7 +544,7 @@ export class MastodonApiServerService {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const data = await client.getFollowRequests( ((_request.query as any) || { limit: 20 }).limit );
|
const data = await client.getFollowRequests( ((_request.query as any) || { limit: 20 }).limit );
|
||||||
reply.send(data.data.map((account) => convertAccount(account as Entity.Account)));
|
reply.send(await Promise.all(data.data.map(async (account) => await this.mastoConverter.convertAccount(account as Entity.Account))));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e);
|
/* console.error(e);
|
||||||
console.error(e.response.data); */
|
console.error(e.response.data); */
|
||||||
|
@ -587,7 +587,7 @@ export class MastodonApiServerService {
|
||||||
const accessTokens = _request.headers.authorization;
|
const accessTokens = _request.headers.authorization;
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const search = new ApiSearchMastodon(_request, client, BASE_URL);
|
const search = new ApiSearchMastodon(_request, client, BASE_URL, this.mastoConverter);
|
||||||
reply.send(await search.SearchV1());
|
reply.send(await search.SearchV1());
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e);
|
/* console.error(e);
|
||||||
|
@ -601,7 +601,7 @@ export class MastodonApiServerService {
|
||||||
const accessTokens = _request.headers.authorization;
|
const accessTokens = _request.headers.authorization;
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const search = new ApiSearchMastodon(_request, client, BASE_URL);
|
const search = new ApiSearchMastodon(_request, client, BASE_URL, this.mastoConverter);
|
||||||
reply.send(await search.SearchV2());
|
reply.send(await search.SearchV2());
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e);
|
/* console.error(e);
|
||||||
|
@ -615,7 +615,7 @@ export class MastodonApiServerService {
|
||||||
const accessTokens = _request.headers.authorization;
|
const accessTokens = _request.headers.authorization;
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const search = new ApiSearchMastodon(_request, client, BASE_URL);
|
const search = new ApiSearchMastodon(_request, client, BASE_URL, this.mastoConverter);
|
||||||
reply.send(await search.getStatusTrends());
|
reply.send(await search.getStatusTrends());
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e);
|
/* console.error(e);
|
||||||
|
@ -629,7 +629,7 @@ export class MastodonApiServerService {
|
||||||
const accessTokens = _request.headers.authorization;
|
const accessTokens = _request.headers.authorization;
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const search = new ApiSearchMastodon(_request, client, BASE_URL);
|
const search = new ApiSearchMastodon(_request, client, BASE_URL, this.mastoConverter);
|
||||||
reply.send(await search.getSuggestions());
|
reply.send(await search.getSuggestions());
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e);
|
/* console.error(e);
|
||||||
|
|
|
@ -7,11 +7,11 @@ import type { Config } from '@/config.js';
|
||||||
import type { IMentionedRemoteUsers } from '@/models/Note.js';
|
import type { IMentionedRemoteUsers } from '@/models/Note.js';
|
||||||
import type { MiUser } from '@/models/User.js';
|
import type { MiUser } from '@/models/User.js';
|
||||||
import type { NoteEditRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
import type { NoteEditRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
|
||||||
import { awaitAll } from '@/misc/prelude/await-all.js';
|
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||||
import { GetterService } from '../GetterService.js';
|
import { GetterService } from '../GetterService.js';
|
||||||
import { ReactionService } from '@/core/ReactionService.js';
|
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
|
||||||
export enum IdConvertType {
|
export enum IdConvertType {
|
||||||
MastodonId,
|
MastodonId,
|
||||||
|
@ -39,10 +39,14 @@ export class MastoConverters {
|
||||||
@Inject(DI.userProfilesRepository)
|
@Inject(DI.userProfilesRepository)
|
||||||
private userProfilesRepository: UserProfilesRepository,
|
private userProfilesRepository: UserProfilesRepository,
|
||||||
|
|
||||||
|
@Inject(DI.noteEditRepository)
|
||||||
|
private noteEditRepository: NoteEditRepository,
|
||||||
|
|
||||||
private mfmService: MfmService,
|
private mfmService: MfmService,
|
||||||
private getterService: GetterService,
|
private getterService: GetterService,
|
||||||
private customEmojiService: CustomEmojiService,
|
private customEmojiService: CustomEmojiService,
|
||||||
private reactionService: ReactionService,
|
private idService: IdService,
|
||||||
|
private driveFileEntityService: DriveFileEntityService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +68,39 @@ export class MastoConverters {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public fileType(s: string): 'unknown' | 'image' | 'gifv' | 'video' | 'audio' {
|
||||||
|
if (s === 'image/gif') {
|
||||||
|
return 'gifv';
|
||||||
|
}
|
||||||
|
if (s.includes('image')) {
|
||||||
|
return 'image';
|
||||||
|
}
|
||||||
|
if (s.includes('video')) {
|
||||||
|
return 'video';
|
||||||
|
}
|
||||||
|
if (s.includes('audio')) {
|
||||||
|
return 'audio';
|
||||||
|
}
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
public encodeFile(f: any): Entity.Attachment {
|
||||||
|
return {
|
||||||
|
id: f.id,
|
||||||
|
type: this.fileType(f.type),
|
||||||
|
url: f.url,
|
||||||
|
remote_url: f.url,
|
||||||
|
preview_url: f.thumbnailUrl,
|
||||||
|
text_url: f.url,
|
||||||
|
meta: {
|
||||||
|
width: f.properties.width,
|
||||||
|
height: f.properties.height
|
||||||
|
},
|
||||||
|
description: f.comment ? f.comment : null,
|
||||||
|
blurhash: f.blurhash ? f.blurhash : null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public async getUser(id: string): Promise<MiUser> {
|
public async getUser(id: string): Promise<MiUser> {
|
||||||
return this.getterService.getUser(id).then(p => {
|
return this.getterService.getUser(id).then(p => {
|
||||||
return p;
|
return p;
|
||||||
|
@ -78,7 +115,7 @@ export class MastoConverters {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async convertAccount(account: Entity.Account) {
|
public async convertAccount(account: Entity.Account | MiUser) {
|
||||||
const user = await this.getUser(account.id);
|
const user = await this.getUser(account.id);
|
||||||
const profile = await this.userProfilesRepository.findOneBy({ userId: user.id });
|
const profile = await this.userProfilesRepository.findOneBy({ userId: user.id });
|
||||||
const emojis = await this.customEmojiService.populateEmojis(user.emojis, user.host ? user.host : this.config.host);
|
const emojis = await this.customEmojiService.populateEmojis(user.emojis, user.host ? user.host : this.config.host);
|
||||||
|
@ -93,19 +130,26 @@ export class MastoConverters {
|
||||||
category: undefined,
|
category: undefined,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
const fqn = `${user.username}@${user.host ?? this.config.hostname}`;
|
||||||
|
let acct = user.username;
|
||||||
|
let acctUrl = `https://${user.host || this.config.host}/@${user.username}`;
|
||||||
|
if (user.host) {
|
||||||
|
acct = `${user.username}@${user.host}`;
|
||||||
|
acctUrl = `https://${user.host}/@${user.username}`;
|
||||||
|
}
|
||||||
return awaitAll({
|
return awaitAll({
|
||||||
id: account.id,
|
id: account.id,
|
||||||
username: account.username,
|
username: user.username,
|
||||||
acct: account.acct,
|
acct: acct,
|
||||||
fqn: account.fqn,
|
fqn: fqn,
|
||||||
display_name: account.display_name || account.username,
|
display_name: user.name ?? user.username,
|
||||||
locked: user.isLocked,
|
locked: user.isLocked,
|
||||||
created_at: account.created_at,
|
created_at: this.idService.parse(user.id).date.toISOString(),
|
||||||
followers_count: user.followersCount,
|
followers_count: user.followersCount,
|
||||||
following_count: user.followingCount,
|
following_count: user.followingCount,
|
||||||
statuses_count: user.notesCount,
|
statuses_count: user.notesCount,
|
||||||
note: profile?.description ?? account.note,
|
note: profile?.description ?? '',
|
||||||
url: account.url,
|
url: user.uri ?? acctUrl,
|
||||||
avatar: user.avatarUrl ? user.avatarUrl : 'https://dev.joinsharkey.org/static-assets/avatar.png',
|
avatar: user.avatarUrl ? user.avatarUrl : 'https://dev.joinsharkey.org/static-assets/avatar.png',
|
||||||
avatar_static: user.avatarUrl ? user.avatarUrl : 'https://dev.joinsharkey.org/static-assets/avatar.png',
|
avatar_static: user.avatarUrl ? user.avatarUrl : 'https://dev.joinsharkey.org/static-assets/avatar.png',
|
||||||
header: user.bannerUrl ? user.bannerUrl : 'https://dev.joinsharkey.org/static-assets/transparent.png',
|
header: user.bannerUrl ? user.bannerUrl : 'https://dev.joinsharkey.org/static-assets/transparent.png',
|
||||||
|
@ -118,6 +162,36 @@ export class MastoConverters {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getEdits(id: string) {
|
||||||
|
const note = await this.getterService.getNote(id);
|
||||||
|
if (!note) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const noteUser = await this.getUser(note.userId).then(async (p) => await this.convertAccount(p));
|
||||||
|
const edits = await this.noteEditRepository.find({ where: { noteId: note.id }, order: { id: 'ASC' } });
|
||||||
|
const history: Promise<any>[] = [];
|
||||||
|
|
||||||
|
let lastDate = this.idService.parse(note.id).date;
|
||||||
|
for (const edit of edits) {
|
||||||
|
const files = this.driveFileEntityService.packManyByIds(edit.fileIds);
|
||||||
|
const item = {
|
||||||
|
account: noteUser,
|
||||||
|
content: this.mfmService.toMastoHtml(mfm.parse(edit.newText ?? ''), JSON.parse(note.mentionedRemoteUsers)).then(p => p ?? ''),
|
||||||
|
created_at: lastDate.toISOString(),
|
||||||
|
emojis: [],
|
||||||
|
sensitive: files.then(files => files.length > 0 ? files.some((f) => f.isSensitive) : false),
|
||||||
|
spoiler_text: edit.cw ?? '',
|
||||||
|
poll: null,
|
||||||
|
media_attachments: files.then(files => files.length > 0 ? files.map((f) => this.encodeFile(f)) : [])
|
||||||
|
};
|
||||||
|
lastDate = edit.updatedAt;
|
||||||
|
history.push(awaitAll(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
return await Promise.all(history);
|
||||||
|
}
|
||||||
|
|
||||||
public async convertStatus(status: Entity.Status) {
|
public async convertStatus(status: Entity.Status) {
|
||||||
const convertedAccount = this.convertAccount(status.account);
|
const convertedAccount = this.convertAccount(status.account);
|
||||||
const note = await this.getterService.getNote(status.id);
|
const note = await this.getterService.getNote(status.id);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Converter } from 'megalodon';
|
import { Converter } from 'megalodon';
|
||||||
import { convertAccount, convertStatus } from '../converters.js';
|
import { MastoConverters, convertAccount, convertStatus } from '../converters.js';
|
||||||
import { limitToInt } from './timeline.js';
|
import { limitToInt } from './timeline.js';
|
||||||
import type { MegalodonInterface } from 'megalodon';
|
import type { MegalodonInterface } from 'megalodon';
|
||||||
import type { FastifyRequest } from 'fastify';
|
import type { FastifyRequest } from 'fastify';
|
||||||
|
@ -63,7 +63,7 @@ export class ApiSearchMastodon {
|
||||||
private client: MegalodonInterface;
|
private client: MegalodonInterface;
|
||||||
private BASE_URL: string;
|
private BASE_URL: string;
|
||||||
|
|
||||||
constructor(request: FastifyRequest, client: MegalodonInterface, BASE_URL: string) {
|
constructor(request: FastifyRequest, client: MegalodonInterface, BASE_URL: string, private mastoConverter: MastoConverters) {
|
||||||
this.request = request;
|
this.request = request;
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.BASE_URL = BASE_URL;
|
this.BASE_URL = BASE_URL;
|
||||||
|
@ -89,8 +89,8 @@ export class ApiSearchMastodon {
|
||||||
const stat = !type || type === 'statuses' ? await this.client.search(query.q, { type: 'statuses', ...query }) : null;
|
const stat = !type || type === 'statuses' ? await this.client.search(query.q, { type: 'statuses', ...query }) : null;
|
||||||
const tags = !type || type === 'hashtags' ? await this.client.search(query.q, { type: 'hashtags', ...query }) : null;
|
const tags = !type || type === 'hashtags' ? await this.client.search(query.q, { type: 'hashtags', ...query }) : null;
|
||||||
const data = {
|
const data = {
|
||||||
accounts: acct?.data.accounts.map((account) => convertAccount(account)) ?? [],
|
accounts: await Promise.all(acct?.data.accounts.map(async (account) => await this.mastoConverter.convertAccount(account)) ?? []),
|
||||||
statuses: stat?.data.statuses.map((status) => convertStatus(status)) ?? [],
|
statuses: await Promise.all(stat?.data.statuses.map(async (status) => await this.mastoConverter.convertStatus(status)) ?? []),
|
||||||
hashtags: tags?.data.hashtags ?? [],
|
hashtags: tags?.data.hashtags ?? [],
|
||||||
};
|
};
|
||||||
return data;
|
return data;
|
||||||
|
|
|
@ -74,7 +74,8 @@ export class ApiStatusMastodon {
|
||||||
public async getHistory() {
|
public async getHistory() {
|
||||||
this.fastify.get<{ Params: { id: string } }>('/v1/statuses/:id/history', async (_request, reply) => {
|
this.fastify.get<{ Params: { id: string } }>('/v1/statuses/:id/history', async (_request, reply) => {
|
||||||
try {
|
try {
|
||||||
reply.send([]);
|
const edits = await this.mastoconverter.getEdits(_request.params.id);
|
||||||
|
reply.send(edits);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
reply.code(401).send(e.response.data);
|
reply.code(401).send(e.response.data);
|
||||||
|
|
|
@ -2,7 +2,8 @@ namespace Entity {
|
||||||
export type List = {
|
export type List = {
|
||||||
id: string
|
id: string
|
||||||
title: string
|
title: string
|
||||||
replies_policy: RepliesPolicy | null
|
replies_policy?: RepliesPolicy | null
|
||||||
|
exclusive?: RepliesPolicy | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RepliesPolicy = 'followed' | 'list' | 'none'
|
export type RepliesPolicy = 'followed' | 'list' | 'none'
|
||||||
|
|
|
@ -391,7 +391,7 @@ namespace MisskeyAPI {
|
||||||
export const list = (l: Entity.List): MegalodonEntity.List => ({
|
export const list = (l: Entity.List): MegalodonEntity.List => ({
|
||||||
id: l.id,
|
id: l.id,
|
||||||
title: l.name,
|
title: l.name,
|
||||||
replies_policy: null
|
exclusive: null
|
||||||
})
|
})
|
||||||
|
|
||||||
export const encodeNotificationType = (
|
export const encodeNotificationType = (
|
||||||
|
|
Loading…
Reference in a new issue