mirror of
https://codeberg.org/yeentown/barkey
synced 2024-11-26 00:35:15 +00:00
upd: add MFM to HTML support and Mentions parsing to mastodon api (#33)
* upd: attempt to turn MFM to html on mastodon * revert: recent change until better implementation later * chore: remove unused packages * Update docker.yml * upd: add MFM to HTML for timelines and status view * chore: lint * upd: megalodon resolve urls * upd: add spliting * test: local user mention * test: change local user url in mention * upd: change check * test: megalodon changes * upd: edit resolving of local users This is starting to drive me nuts * upd: remove the @ symbol in query * fix: make renderPerson return host instead of null for local * upd: change url for local user * upd: change limit * upd: add url to output * upd: add mastodon boolean * test: test different format * fix: test of different format * test: change up resolving * fix: forgot to provide url * upd: change lookup function a bit * test: substring * test: regex * upd: remove substr * test: new regexs * dirty test * test: one last attempt for today * upd: fix build error * upd: take input from iceshrimp dev * upd: parse remote statuses * upd: fix pleroma users misformatted urls * upd: add uri to normal user * fix: forgot to push updated types * fix: resolving broke * fix: html not converting correctly * fix: return default img if no banner * upd: swap out img used for no header, set fallback avatar * fix: html escaped & and ' symbols * upd: fix ' converting into 39; and get profile fields * upd: resolve fields on lookup --------- Co-authored-by: Amelia Yukii <123300075+Insert5StarName@users.noreply.github.com>
This commit is contained in:
parent
e5d9eb3082
commit
54578f6965
13 changed files with 237 additions and 41 deletions
6
.github/workflows/docker.yml
vendored
6
.github/workflows/docker.yml
vendored
|
@ -1,6 +1,12 @@
|
||||||
name: Publish Docker image
|
name: Publish Docker image
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- stable
|
||||||
|
paths:
|
||||||
|
- packages/**
|
||||||
|
- locales/**
|
||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
BIN
packages/backend/assets/transparent.png
Normal file
BIN
packages/backend/assets/transparent.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 68 B |
|
@ -339,6 +339,13 @@ export class MfmService {
|
||||||
mention: (node) => {
|
mention: (node) => {
|
||||||
const a = doc.createElement('a');
|
const a = doc.createElement('a');
|
||||||
const { username, host, acct } = node.props;
|
const { username, host, acct } = node.props;
|
||||||
|
/* if (mastodon) {
|
||||||
|
const splitacct = acct.split("@");
|
||||||
|
a.setAttribute('href', splitacct[2] !== this.config.host && splitacct[2] !== undefined ? `https://${splitacct[2]}/@${splitacct[1]}` : `https://${this.config.host}/${acct}`);
|
||||||
|
a.className = 'u-url mention';
|
||||||
|
a.textContent = acct;
|
||||||
|
return a;
|
||||||
|
} */
|
||||||
const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host);
|
const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host);
|
||||||
a.setAttribute('href', remoteUserInfo ? (remoteUserInfo.url ? remoteUserInfo.url : remoteUserInfo.uri) : `${this.config.url}/${acct}`);
|
a.setAttribute('href', remoteUserInfo ? (remoteUserInfo.url ? remoteUserInfo.url : remoteUserInfo.uri) : `${this.config.url}/${acct}`);
|
||||||
a.className = 'u-url mention';
|
a.className = 'u-url mention';
|
||||||
|
|
|
@ -27,7 +27,7 @@ export const meta = {
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
limit: {
|
limit: {
|
||||||
duration: ms('1hour'),
|
duration: ms('1minute'),
|
||||||
max: 30,
|
max: 30,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import megalodon, { Entity, MegalodonInterface } from 'megalodon';
|
||||||
import querystring from 'querystring';
|
import querystring from 'querystring';
|
||||||
import { IsNull } from 'typeorm';
|
import { IsNull } from 'typeorm';
|
||||||
import multer from 'fastify-multer';
|
import multer from 'fastify-multer';
|
||||||
import type { UsersRepository } from '@/models/_.js';
|
import type { NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
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';
|
||||||
|
@ -12,6 +12,7 @@ import { convertId, IdConvertType as IdType, convertAccount, convertAnnouncement
|
||||||
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';
|
||||||
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
|
||||||
export function getClient(BASE_URL: string, authorization: string | undefined): MegalodonInterface {
|
export function getClient(BASE_URL: string, authorization: string | undefined): MegalodonInterface {
|
||||||
const accessTokenArr = authorization?.split(' ') ?? [null];
|
const accessTokenArr = authorization?.split(' ') ?? [null];
|
||||||
|
@ -26,9 +27,14 @@ export class MastodonApiServerService {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
@Inject(DI.notesRepository)
|
||||||
|
private notesRepository: NotesRepository,
|
||||||
|
@Inject(DI.userProfilesRepository)
|
||||||
|
private userProfilesRepository: UserProfilesRepository,
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
private metaService: MetaService,
|
private metaService: MetaService,
|
||||||
|
private userEntityService: UserEntityService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -256,8 +262,10 @@ export class MastodonApiServerService {
|
||||||
const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt
|
const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt
|
||||||
// displayed without being logged in
|
// displayed without being logged in
|
||||||
try {
|
try {
|
||||||
const account = new ApiAccountMastodon(_request, client, BASE_URL);
|
const data = await client.search((_request.query as any).acct, { type: 'accounts' });
|
||||||
reply.send(await account.lookup());
|
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})) || [];
|
||||||
|
reply.send(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);
|
||||||
|
@ -294,6 +302,8 @@ export class MastodonApiServerService {
|
||||||
try {
|
try {
|
||||||
const sharkId = convertId(_request.params.id, IdType.SharkeyId);
|
const sharkId = convertId(_request.params.id, IdType.SharkeyId);
|
||||||
const data = await client.getAccount(sharkId);
|
const data = await client.getAccount(sharkId);
|
||||||
|
const profile = await this.userProfilesRepository.findOneBy({userId: sharkId});
|
||||||
|
data.data.fields = profile?.fields.map(f => ({...f, verified_at: null})) || [];
|
||||||
reply.send(convertAccount(data.data));
|
reply.send(convertAccount(data.data));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e);
|
/* console.error(e);
|
||||||
|
@ -744,7 +754,7 @@ export class MastodonApiServerService {
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region Timelines
|
//#region Timelines
|
||||||
const TLEndpoint = new ApiTimelineMastodon(fastify);
|
const TLEndpoint = new ApiTimelineMastodon(fastify, this.config, this.usersRepository, this.notesRepository, this.userEntityService);
|
||||||
|
|
||||||
// GET Endpoints
|
// GET Endpoints
|
||||||
TLEndpoint.getTL();
|
TLEndpoint.getTL();
|
||||||
|
@ -769,7 +779,7 @@ export class MastodonApiServerService {
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region Status
|
//#region Status
|
||||||
const NoteEndpoint = new ApiStatusMastodon(fastify);
|
const NoteEndpoint = new ApiStatusMastodon(fastify, this.config, this.usersRepository, this.notesRepository, this.userEntityService);
|
||||||
|
|
||||||
// GET Endpoints
|
// GET Endpoints
|
||||||
NoteEndpoint.getStatus();
|
NoteEndpoint.getStatus();
|
||||||
|
|
|
@ -1,4 +1,14 @@
|
||||||
|
import type { Config } from '@/config.js';
|
||||||
|
import { MfmService } from '@/core/MfmService.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { Inject } from '@nestjs/common';
|
||||||
import { Entity } from 'megalodon';
|
import { Entity } from 'megalodon';
|
||||||
|
import { parse } from 'mfm-js';
|
||||||
|
import { GetterService } from '../GetterService.js';
|
||||||
|
import type { IMentionedRemoteUsers } from '@/models/Note.js';
|
||||||
|
import type { MiUser } from '@/models/User.js';
|
||||||
|
import type { NotesRepository, UsersRepository } from '@/models/_.js';
|
||||||
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
|
||||||
const CHAR_COLLECTION = '0123456789abcdefghijklmnopqrstuvwxyz';
|
const CHAR_COLLECTION = '0123456789abcdefghijklmnopqrstuvwxyz';
|
||||||
|
|
||||||
|
@ -7,6 +17,91 @@ export enum IdConvertType {
|
||||||
SharkeyId,
|
SharkeyId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const escapeMFM = (text: string): string => text
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """)
|
||||||
|
.replace(/'/g, "'")
|
||||||
|
.replace(/`/g, "`")
|
||||||
|
.replace(/\r?\n/g, "<br>");
|
||||||
|
|
||||||
|
export class MastoConverters {
|
||||||
|
private MfmService: MfmService;
|
||||||
|
private GetterService: GetterService;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.config)
|
||||||
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.usersRepository)
|
||||||
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
@Inject(DI.notesRepository)
|
||||||
|
private notesRepository: NotesRepository,
|
||||||
|
|
||||||
|
private userEntityService: UserEntityService
|
||||||
|
) {
|
||||||
|
this.MfmService = new MfmService(this.config);
|
||||||
|
this.GetterService = new GetterService(this.usersRepository, this.notesRepository, this.userEntityService);
|
||||||
|
}
|
||||||
|
|
||||||
|
private encode(u: MiUser, m: IMentionedRemoteUsers): MastodonEntity.Mention {
|
||||||
|
let acct = u.username;
|
||||||
|
let acctUrl = `https://${u.host || this.config.host}/@${u.username}`;
|
||||||
|
let url: string | null = null;
|
||||||
|
if (u.host) {
|
||||||
|
const info = m.find(r => r.username === u.username && r.host === u.host);
|
||||||
|
acct = `${u.username}@${u.host}`;
|
||||||
|
acctUrl = `https://${u.host}/@${u.username}`;
|
||||||
|
if (info) url = info.url ?? info.uri;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: u.id,
|
||||||
|
username: u.username,
|
||||||
|
acct: acct,
|
||||||
|
url: url ?? acctUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getUser(id: string): Promise<MiUser> {
|
||||||
|
return this.GetterService.getUser(id).then(p => {
|
||||||
|
return p;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public async convertStatus(status: Entity.Status) {
|
||||||
|
status.account = convertAccount(status.account);
|
||||||
|
const note = await this.GetterService.getNote(status.id);
|
||||||
|
status.id = convertId(status.id, IdConvertType.MastodonId);
|
||||||
|
if (status.in_reply_to_account_id) status.in_reply_to_account_id = convertId(
|
||||||
|
status.in_reply_to_account_id,
|
||||||
|
IdConvertType.MastodonId,
|
||||||
|
);
|
||||||
|
if (status.in_reply_to_id) status.in_reply_to_id = convertId(status.in_reply_to_id, IdConvertType.MastodonId);
|
||||||
|
status.media_attachments = status.media_attachments.map((attachment) =>
|
||||||
|
convertAttachment(attachment),
|
||||||
|
);
|
||||||
|
// This will eventually be improved with a rewrite of this file
|
||||||
|
const mentions = Promise.all(note.mentions.map(p =>
|
||||||
|
this.getUser(p)
|
||||||
|
.then(u => this.encode(u, JSON.parse(note.mentionedRemoteUsers)))
|
||||||
|
.catch(() => null)))
|
||||||
|
.then(p => p.filter(m => m)) as Promise<MastodonEntity.Mention[]>;
|
||||||
|
status.mentions = await mentions;
|
||||||
|
status.mentions = status.mentions.map((mention) => ({
|
||||||
|
...mention,
|
||||||
|
id: convertId(mention.id, IdConvertType.MastodonId),
|
||||||
|
}));
|
||||||
|
const convertedMFM = this.MfmService.toHtml(parse(status.content), JSON.parse(note.mentionedRemoteUsers));
|
||||||
|
status.content = status.content ? convertedMFM?.replace(/&/g , "&").replaceAll(`<span>&</span><a href="${this.config.url}/tags/39;" rel="tag">#39;</a>` , "<span>\'</span>")! : status.content;
|
||||||
|
if (status.poll) status.poll = convertPoll(status.poll);
|
||||||
|
if (status.reblog) status.reblog = convertStatus(status.reblog);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function convertId(in_id: string, id_convert_type: IdConvertType): string {
|
export function convertId(in_id: string, id_convert_type: IdConvertType): string {
|
||||||
switch (id_convert_type) {
|
switch (id_convert_type) {
|
||||||
case IdConvertType.MastodonId: {
|
case IdConvertType.MastodonId: {
|
||||||
|
|
|
@ -63,7 +63,7 @@ export class ApiAccountMastodon {
|
||||||
const data = await this.client.search((this.request.query as any).acct, { type: 'accounts' });
|
const data = await this.client.search((this.request.query as any).acct, { type: 'accounts' });
|
||||||
return convertAccount(data.data.accounts[0]);
|
return convertAccount(data.data.accounts[0]);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e);
|
/* console.error(e)
|
||||||
console.error(e.response.data); */
|
console.error(e.response.data); */
|
||||||
return e.response;
|
return e.response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import querystring from 'querystring';
|
import querystring from 'querystring';
|
||||||
import { emojiRegexAtStartToEnd } from '@/misc/emoji-regex.js';
|
import { emojiRegexAtStartToEnd } from '@/misc/emoji-regex.js';
|
||||||
import { convertId, IdConvertType as IdType, convertAccount, convertAttachment, convertPoll, convertStatus, convertStatusSource } from '../converters.js';
|
import { convertId, IdConvertType as IdType, convertAccount, convertAttachment, convertPoll, convertStatusSource, MastoConverters } from '../converters.js';
|
||||||
import { getClient } from '../MastodonApiServerService.js';
|
import { getClient } from '../MastodonApiServerService.js';
|
||||||
import { convertTimelinesArgsId, limitToInt } from './timeline.js';
|
import { convertTimelinesArgsId, limitToInt } from './timeline.js';
|
||||||
import type { Entity } from 'megalodon';
|
import type { Entity } from 'megalodon';
|
||||||
import type { FastifyInstance } from 'fastify';
|
import type { FastifyInstance } from 'fastify';
|
||||||
|
import type { Config } from '@/config.js';
|
||||||
|
import { NotesRepository, UsersRepository } from '@/models/_.js';
|
||||||
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
|
||||||
function normalizeQuery(data: any) {
|
function normalizeQuery(data: any) {
|
||||||
const str = querystring.stringify(data);
|
const str = querystring.stringify(data);
|
||||||
|
@ -13,9 +16,11 @@ function normalizeQuery(data: any) {
|
||||||
|
|
||||||
export class ApiStatusMastodon {
|
export class ApiStatusMastodon {
|
||||||
private fastify: FastifyInstance;
|
private fastify: FastifyInstance;
|
||||||
|
private mastoconverter: MastoConverters;
|
||||||
|
|
||||||
constructor(fastify: FastifyInstance) {
|
constructor(fastify: FastifyInstance, config: Config, usersrepo: UsersRepository, notesrepo: NotesRepository, userentity: UserEntityService) {
|
||||||
this.fastify = fastify;
|
this.fastify = fastify;
|
||||||
|
this.mastoconverter = new MastoConverters(config, usersrepo, notesrepo, userentity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getStatus() {
|
public async getStatus() {
|
||||||
|
@ -25,7 +30,7 @@ export class ApiStatusMastodon {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const data = await client.getStatus(convertId(_request.params.id, IdType.SharkeyId));
|
const data = await client.getStatus(convertId(_request.params.id, IdType.SharkeyId));
|
||||||
reply.send(convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(data.data));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
reply.code(_request.is404 ? 404 : 401).send(e.response.data);
|
reply.code(_request.is404 ? 404 : 401).send(e.response.data);
|
||||||
|
@ -59,8 +64,8 @@ export class ApiStatusMastodon {
|
||||||
convertId(_request.params.id, IdType.SharkeyId),
|
convertId(_request.params.id, IdType.SharkeyId),
|
||||||
convertTimelinesArgsId(limitToInt(query)),
|
convertTimelinesArgsId(limitToInt(query)),
|
||||||
);
|
);
|
||||||
data.data.ancestors = data.data.ancestors.map((status: Entity.Status) => convertStatus(status));
|
data.data.ancestors = await Promise.all(data.data.ancestors.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status)));
|
||||||
data.data.descendants = data.data.descendants.map((status: Entity.Status) => convertStatus(status));
|
data.data.descendants = await Promise.all(data.data.descendants.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status)));
|
||||||
reply.send(data.data);
|
reply.send(data.data);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -219,7 +224,7 @@ export class ApiStatusMastodon {
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await client.postStatus(text, body);
|
const data = await client.postStatus(text, body);
|
||||||
reply.send(convertStatus(data.data as Entity.Status));
|
reply.send(await this.mastoconverter.convertStatus(data.data as Entity.Status));
|
||||||
} 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);
|
||||||
|
@ -240,7 +245,7 @@ export class ApiStatusMastodon {
|
||||||
body.media_ids = (body.media_ids as string[]).map((p) => convertId(p, IdType.SharkeyId));
|
body.media_ids = (body.media_ids as string[]).map((p) => convertId(p, IdType.SharkeyId));
|
||||||
}
|
}
|
||||||
const data = await client.editStatus(convertId(_request.params.id, IdType.SharkeyId), body);
|
const data = await client.editStatus(convertId(_request.params.id, IdType.SharkeyId), body);
|
||||||
reply.send(convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(data.data));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
reply.code(_request.is404 ? 404 : 401).send(e.response.data);
|
reply.code(_request.is404 ? 404 : 401).send(e.response.data);
|
||||||
|
@ -258,7 +263,7 @@ export class ApiStatusMastodon {
|
||||||
convertId(_request.params.id, IdType.SharkeyId),
|
convertId(_request.params.id, IdType.SharkeyId),
|
||||||
'❤',
|
'❤',
|
||||||
)) as any;
|
)) as any;
|
||||||
reply.send(convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(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);
|
||||||
|
@ -276,7 +281,7 @@ export class ApiStatusMastodon {
|
||||||
convertId(_request.params.id, IdType.SharkeyId),
|
convertId(_request.params.id, IdType.SharkeyId),
|
||||||
'❤',
|
'❤',
|
||||||
);
|
);
|
||||||
reply.send(convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(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);
|
||||||
|
@ -291,7 +296,7 @@ export class ApiStatusMastodon {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const data = await client.reblogStatus(convertId(_request.params.id, IdType.SharkeyId));
|
const data = await client.reblogStatus(convertId(_request.params.id, IdType.SharkeyId));
|
||||||
reply.send(convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(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);
|
||||||
|
@ -306,7 +311,7 @@ export class ApiStatusMastodon {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const data = await client.unreblogStatus(convertId(_request.params.id, IdType.SharkeyId));
|
const data = await client.unreblogStatus(convertId(_request.params.id, IdType.SharkeyId));
|
||||||
reply.send(convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(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);
|
||||||
|
@ -321,7 +326,7 @@ export class ApiStatusMastodon {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const data = await client.bookmarkStatus(convertId(_request.params.id, IdType.SharkeyId));
|
const data = await client.bookmarkStatus(convertId(_request.params.id, IdType.SharkeyId));
|
||||||
reply.send(convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(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);
|
||||||
|
@ -336,7 +341,7 @@ export class ApiStatusMastodon {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const data = await client.unbookmarkStatus(convertId(_request.params.id, IdType.SharkeyId));
|
const data = await client.unbookmarkStatus(convertId(_request.params.id, IdType.SharkeyId));
|
||||||
reply.send(convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(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);
|
||||||
|
@ -351,7 +356,7 @@ export class ApiStatusMastodon {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const data = await client.pinStatus(convertId(_request.params.id, IdType.SharkeyId));
|
const data = await client.pinStatus(convertId(_request.params.id, IdType.SharkeyId));
|
||||||
reply.send(convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(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);
|
||||||
|
@ -366,7 +371,7 @@ export class ApiStatusMastodon {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const data = await client.unpinStatus(convertId(_request.params.id, IdType.SharkeyId));
|
const data = await client.unpinStatus(convertId(_request.params.id, IdType.SharkeyId));
|
||||||
reply.send(convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(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);
|
||||||
|
@ -381,7 +386,7 @@ export class ApiStatusMastodon {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const data = await client.createEmojiReaction(convertId(_request.params.id, IdType.SharkeyId), _request.params.name);
|
const data = await client.createEmojiReaction(convertId(_request.params.id, IdType.SharkeyId), _request.params.name);
|
||||||
reply.send(convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(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);
|
||||||
|
@ -396,7 +401,7 @@ export class ApiStatusMastodon {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const data = await client.deleteEmojiReaction(convertId(_request.params.id, IdType.SharkeyId), _request.params.name);
|
const data = await client.deleteEmojiReaction(convertId(_request.params.id, IdType.SharkeyId), _request.params.name);
|
||||||
reply.send(convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(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);
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import { ParsedUrlQuery } from 'querystring';
|
import { ParsedUrlQuery } from 'querystring';
|
||||||
import { convertId, IdConvertType as IdType, convertAccount, convertConversation, convertList, convertStatus } from '../converters.js';
|
import { convertId, IdConvertType as IdType, convertAccount, convertConversation, convertList, MastoConverters } from '../converters.js';
|
||||||
import { getClient } from '../MastodonApiServerService.js';
|
import { getClient } from '../MastodonApiServerService.js';
|
||||||
import type { Entity } from 'megalodon';
|
import type { Entity } from 'megalodon';
|
||||||
import type { FastifyInstance } from 'fastify';
|
import type { FastifyInstance } from 'fastify';
|
||||||
|
import type { Config } from '@/config.js';
|
||||||
|
import { NotesRepository, UsersRepository } from '@/models/_.js';
|
||||||
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
|
||||||
export function limitToInt(q: ParsedUrlQuery) {
|
export function limitToInt(q: ParsedUrlQuery) {
|
||||||
const object: any = q;
|
const object: any = q;
|
||||||
|
@ -38,9 +41,11 @@ export function convertTimelinesArgsId(q: ParsedUrlQuery) {
|
||||||
|
|
||||||
export class ApiTimelineMastodon {
|
export class ApiTimelineMastodon {
|
||||||
private fastify: FastifyInstance;
|
private fastify: FastifyInstance;
|
||||||
|
private mastoconverter: MastoConverters;
|
||||||
|
|
||||||
constructor(fastify: FastifyInstance) {
|
constructor(fastify: FastifyInstance, config: Config, usersRepository: UsersRepository, notesRepository: NotesRepository, userEntityService: UserEntityService) {
|
||||||
this.fastify = fastify;
|
this.fastify = fastify;
|
||||||
|
this.mastoconverter = new MastoConverters(config, usersRepository, notesRepository, userEntityService);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getTL() {
|
public async getTL() {
|
||||||
|
@ -53,7 +58,7 @@ export class ApiTimelineMastodon {
|
||||||
const data = query.local === 'true'
|
const data = query.local === 'true'
|
||||||
? await client.getLocalTimeline(convertTimelinesArgsId(argsToBools(limitToInt(query))))
|
? await client.getLocalTimeline(convertTimelinesArgsId(argsToBools(limitToInt(query))))
|
||||||
: await client.getPublicTimeline(convertTimelinesArgsId(argsToBools(limitToInt(query))));
|
: await client.getPublicTimeline(convertTimelinesArgsId(argsToBools(limitToInt(query))));
|
||||||
reply.send(data.data.map((status: Entity.Status) => convertStatus(status)));
|
reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status))));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
console.error(e.response.data);
|
console.error(e.response.data);
|
||||||
|
@ -70,7 +75,7 @@ export class ApiTimelineMastodon {
|
||||||
try {
|
try {
|
||||||
const query: any = _request.query;
|
const query: any = _request.query;
|
||||||
const data = await client.getHomeTimeline(convertTimelinesArgsId(limitToInt(query)));
|
const data = await client.getHomeTimeline(convertTimelinesArgsId(limitToInt(query)));
|
||||||
reply.send(data.data.map((status: Entity.Status) => convertStatus(status)));
|
reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status))));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
console.error(e.response.data);
|
console.error(e.response.data);
|
||||||
|
@ -88,7 +93,7 @@ export class ApiTimelineMastodon {
|
||||||
const query: any = _request.query;
|
const query: any = _request.query;
|
||||||
const params: any = _request.params;
|
const params: any = _request.params;
|
||||||
const data = await client.getTagTimeline(params.hashtag, convertTimelinesArgsId(limitToInt(query)));
|
const data = await client.getTagTimeline(params.hashtag, convertTimelinesArgsId(limitToInt(query)));
|
||||||
reply.send(data.data.map((status: Entity.Status) => convertStatus(status)));
|
reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status))));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
console.error(e.response.data);
|
console.error(e.response.data);
|
||||||
|
@ -106,7 +111,7 @@ export class ApiTimelineMastodon {
|
||||||
const query: any = _request.query;
|
const query: any = _request.query;
|
||||||
const params: any = _request.params;
|
const params: any = _request.params;
|
||||||
const data = await client.getListTimeline(convertId(params.id, IdType.SharkeyId), convertTimelinesArgsId(limitToInt(query)));
|
const data = await client.getListTimeline(convertId(params.id, IdType.SharkeyId), convertTimelinesArgsId(limitToInt(query)));
|
||||||
reply.send(data.data.map((status: Entity.Status) => convertStatus(status)));
|
reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status))));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
console.error(e.response.data);
|
console.error(e.response.data);
|
||||||
|
|
|
@ -2122,6 +2122,40 @@ export default class Misskey implements MegalodonInterface {
|
||||||
): Promise<Response<Entity.Results>> {
|
): Promise<Response<Entity.Results>> {
|
||||||
switch (options.type) {
|
switch (options.type) {
|
||||||
case 'accounts': {
|
case 'accounts': {
|
||||||
|
if (q.startsWith("http://") || q.startsWith("https://")) {
|
||||||
|
return this.client
|
||||||
|
.post("/api/ap/show", { uri: q })
|
||||||
|
.then(async (res) => {
|
||||||
|
if (res.status != 200 || res.data.type != "User") {
|
||||||
|
res.status = 200;
|
||||||
|
res.statusText = "OK";
|
||||||
|
res.data = {
|
||||||
|
accounts: [],
|
||||||
|
statuses: [],
|
||||||
|
hashtags: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
const account = await MisskeyAPI.Converter.userDetail(
|
||||||
|
res.data.object as MisskeyAPI.Entity.UserDetail,
|
||||||
|
this.baseUrl,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...res,
|
||||||
|
data: {
|
||||||
|
accounts:
|
||||||
|
options?.max_id && options?.max_id >= account.id
|
||||||
|
? []
|
||||||
|
: [account],
|
||||||
|
statuses: [],
|
||||||
|
hashtags: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
let params = {
|
let params = {
|
||||||
query: q
|
query: q
|
||||||
}
|
}
|
||||||
|
@ -2151,7 +2185,7 @@ export default class Misskey implements MegalodonInterface {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const match = q.match(/^@?(?<user>[a-zA-Z0-9_]+)(?:@(?<host>[a-zA-Z0-9-.]+\.[a-zA-Z0-9-]+)|)$/);
|
const match = params.query.match(/^@?(?<user>[a-zA-Z0-9_]+)(?:@(?<host>[a-zA-Z0-9-.]+\.[a-zA-Z0-9-]+)|)$/);
|
||||||
if (match) {
|
if (match) {
|
||||||
const lookupQuery = {
|
const lookupQuery = {
|
||||||
username: match.groups?.user,
|
username: match.groups?.user,
|
||||||
|
@ -2195,6 +2229,38 @@ export default class Misskey implements MegalodonInterface {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
case 'statuses': {
|
case 'statuses': {
|
||||||
|
if (q.startsWith("http://") || q.startsWith("https://")) {
|
||||||
|
return this.client
|
||||||
|
.post("/api/ap/show", { uri: q })
|
||||||
|
.then(async (res) => {
|
||||||
|
if (res.status != 200 || res.data.type != "Note") {
|
||||||
|
res.status = 200;
|
||||||
|
res.statusText = "OK";
|
||||||
|
res.data = {
|
||||||
|
accounts: [],
|
||||||
|
statuses: [],
|
||||||
|
hashtags: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
const post = await MisskeyAPI.Converter.note(
|
||||||
|
res.data.object as MisskeyAPI.Entity.Note,
|
||||||
|
this.baseUrl
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...res,
|
||||||
|
data: {
|
||||||
|
accounts: [],
|
||||||
|
statuses:
|
||||||
|
options?.max_id && options.max_id >= post.id ? [] : [post],
|
||||||
|
hashtags: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
let params = {
|
let params = {
|
||||||
query: q
|
query: q
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,11 +93,11 @@ namespace MisskeyAPI {
|
||||||
following_count: u.followingCount ? u.followingCount : 0,
|
following_count: u.followingCount ? u.followingCount : 0,
|
||||||
statuses_count: u.notesCount ? u.notesCount : 0,
|
statuses_count: u.notesCount ? u.notesCount : 0,
|
||||||
note: u.description ? u.description : '',
|
note: u.description ? u.description : '',
|
||||||
url: acctUrl,
|
url: u.uri ?? acctUrl,
|
||||||
avatar: u.avatarUrl,
|
avatar: u.avatarUrl ? u.avatarUrl : 'https://dev.joinsharkey.org/static-assets/avatar.png',
|
||||||
avatar_static: u.avatarUrl,
|
avatar_static: u.avatarUrl ? u.avatarUrl : 'https://dev.joinsharkey.org/static-assets/avatar.png',
|
||||||
header: u.bannerUrl ? u.bannerUrl : '',
|
header: u.bannerUrl ? u.bannerUrl : 'https://dev.joinsharkey.org/static-assets/transparent.png',
|
||||||
header_static: u.bannerUrl ? u.bannerUrl : '',
|
header_static: u.bannerUrl ? u.bannerUrl : 'https://dev.joinsharkey.org/static-assets/transparent.png',
|
||||||
emojis: mapEmojis(u.emojis),
|
emojis: mapEmojis(u.emojis),
|
||||||
moved: null,
|
moved: null,
|
||||||
fields: [],
|
fields: [],
|
||||||
|
@ -128,11 +128,11 @@ namespace MisskeyAPI {
|
||||||
following_count: u.followingCount,
|
following_count: u.followingCount,
|
||||||
statuses_count: u.notesCount,
|
statuses_count: u.notesCount,
|
||||||
note: u.description ? u.description.replace(/\n|\\n/g, "<br>") : '',
|
note: u.description ? u.description.replace(/\n|\\n/g, "<br>") : '',
|
||||||
url: acctUrl,
|
url: u.uri ?? acctUrl,
|
||||||
avatar: u.avatarUrl,
|
avatar: u.avatarUrl ? u.avatarUrl : 'https://dev.joinsharkey.org/static-assets/avatar.png',
|
||||||
avatar_static: u.avatarUrl,
|
avatar_static: u.avatarUrl ? u.avatarUrl : 'https://dev.joinsharkey.org/static-assets/avatar.png',
|
||||||
header: u.bannerUrl,
|
header: u.bannerUrl ? u.bannerUrl : 'https://dev.joinsharkey.org/static-assets/transparent.png',
|
||||||
header_static: u.bannerUrl,
|
header_static: u.bannerUrl ? u.bannerUrl : 'https://dev.joinsharkey.org/static-assets/transparent.png',
|
||||||
emojis: mapEmojis(u.emojis),
|
emojis: mapEmojis(u.emojis),
|
||||||
moved: null,
|
moved: null,
|
||||||
fields: [],
|
fields: [],
|
||||||
|
|
|
@ -12,6 +12,7 @@ namespace MisskeyEntity {
|
||||||
notesCount?: number
|
notesCount?: number
|
||||||
host: string | null
|
host: string | null
|
||||||
avatarUrl: string
|
avatarUrl: string
|
||||||
|
uri?: string
|
||||||
bannerUrl?: string | null
|
bannerUrl?: string | null
|
||||||
avatarColor: string
|
avatarColor: string
|
||||||
emojis: Array<Emoji> | { [key: string]: string }
|
emojis: Array<Emoji> | { [key: string]: string }
|
||||||
|
|
|
@ -16,6 +16,7 @@ namespace MisskeyEntity {
|
||||||
emojis: Array<Emoji> | { [key: string]: string }
|
emojis: Array<Emoji> | { [key: string]: string }
|
||||||
createdAt: string
|
createdAt: string
|
||||||
bannerUrl: string
|
bannerUrl: string
|
||||||
|
uri: string
|
||||||
bannerColor: string
|
bannerColor: string
|
||||||
isLocked: boolean
|
isLocked: boolean
|
||||||
isSilenced: boolean
|
isSilenced: boolean
|
||||||
|
|
Loading…
Reference in a new issue