upd: change deps, fix a few bugs, update converter
Fixes User and Notes count bug (transfem-org/Sharkey#113) Fixes build issues due to types (transfem-org/Sharkey#111) Return accounts and notes like Iceshrimp Use MFM class from Iceshrimp to fix HTML output for mastodon
This commit is contained in:
parent
b0a7fd6ddb
commit
82c10de265
14 changed files with 421 additions and 242 deletions
|
@ -388,4 +388,212 @@ export class MfmService {
|
||||||
|
|
||||||
return `<p>${doc.body.innerHTML}</p>`;
|
return `<p>${doc.body.innerHTML}</p>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async toMastoHtml(nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMentionedRemoteUsers = [], inline = false, quoteUri: string | null = null) {
|
||||||
|
if (nodes == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { window } = new Window();
|
||||||
|
|
||||||
|
const doc = window.document;
|
||||||
|
|
||||||
|
async function appendChildren(children: mfm.MfmNode[], targetElement: any): Promise<void> {
|
||||||
|
if (children) {
|
||||||
|
for (const child of await Promise.all(children.map(async (x) => await (handlers as any)[x.type](x)))) targetElement.appendChild(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlers: {
|
||||||
|
[K in mfm.MfmNode['type']]: (node: mfm.NodeType<K>) => any;
|
||||||
|
} = {
|
||||||
|
async bold(node) {
|
||||||
|
const el = doc.createElement('span');
|
||||||
|
el.textContent = '**';
|
||||||
|
await appendChildren(node.children, el);
|
||||||
|
el.textContent += '**';
|
||||||
|
return el;
|
||||||
|
},
|
||||||
|
|
||||||
|
async small(node) {
|
||||||
|
const el = doc.createElement('small');
|
||||||
|
await appendChildren(node.children, el);
|
||||||
|
return el;
|
||||||
|
},
|
||||||
|
|
||||||
|
async strike(node) {
|
||||||
|
const el = doc.createElement('span');
|
||||||
|
el.textContent = '~~';
|
||||||
|
await appendChildren(node.children, el);
|
||||||
|
el.textContent += '~~';
|
||||||
|
return el;
|
||||||
|
},
|
||||||
|
|
||||||
|
async italic(node) {
|
||||||
|
const el = doc.createElement('span');
|
||||||
|
el.textContent = '*';
|
||||||
|
await appendChildren(node.children, el);
|
||||||
|
el.textContent += '*';
|
||||||
|
return el;
|
||||||
|
},
|
||||||
|
|
||||||
|
async fn(node) {
|
||||||
|
const el = doc.createElement('span');
|
||||||
|
el.textContent = '*';
|
||||||
|
await appendChildren(node.children, el);
|
||||||
|
el.textContent += '*';
|
||||||
|
return el;
|
||||||
|
},
|
||||||
|
|
||||||
|
blockCode(node) {
|
||||||
|
const pre = doc.createElement('pre');
|
||||||
|
const inner = doc.createElement('code');
|
||||||
|
|
||||||
|
const nodes = node.props.code
|
||||||
|
.split(/\r\n|\r|\n/)
|
||||||
|
.map((x) => doc.createTextNode(x));
|
||||||
|
|
||||||
|
for (const x of intersperse<FIXME | 'br'>('br', nodes)) {
|
||||||
|
inner.appendChild(x === 'br' ? doc.createElement('br') : x);
|
||||||
|
}
|
||||||
|
|
||||||
|
pre.appendChild(inner);
|
||||||
|
return pre;
|
||||||
|
},
|
||||||
|
|
||||||
|
async center(node) {
|
||||||
|
const el = doc.createElement('div');
|
||||||
|
await appendChildren(node.children, el);
|
||||||
|
return el;
|
||||||
|
},
|
||||||
|
|
||||||
|
emojiCode(node) {
|
||||||
|
return doc.createTextNode(`\u200B:${node.props.name}:\u200B`);
|
||||||
|
},
|
||||||
|
|
||||||
|
unicodeEmoji(node) {
|
||||||
|
return doc.createTextNode(node.props.emoji);
|
||||||
|
},
|
||||||
|
|
||||||
|
hashtag: (node) => {
|
||||||
|
const a = doc.createElement('a');
|
||||||
|
a.setAttribute('href', `${this.config.url}/tags/${node.props.hashtag}`);
|
||||||
|
a.textContent = `#${node.props.hashtag}`;
|
||||||
|
a.setAttribute('rel', 'tag');
|
||||||
|
a.setAttribute('class', 'hashtag');
|
||||||
|
return a;
|
||||||
|
},
|
||||||
|
|
||||||
|
inlineCode(node) {
|
||||||
|
const el = doc.createElement('code');
|
||||||
|
el.textContent = node.props.code;
|
||||||
|
return el;
|
||||||
|
},
|
||||||
|
|
||||||
|
mathInline(node) {
|
||||||
|
const el = doc.createElement('code');
|
||||||
|
el.textContent = node.props.formula;
|
||||||
|
return el;
|
||||||
|
},
|
||||||
|
|
||||||
|
mathBlock(node) {
|
||||||
|
const el = doc.createElement('code');
|
||||||
|
el.textContent = node.props.formula;
|
||||||
|
return el;
|
||||||
|
},
|
||||||
|
|
||||||
|
async link(node) {
|
||||||
|
const a = doc.createElement('a');
|
||||||
|
a.setAttribute('rel', 'nofollow noopener noreferrer');
|
||||||
|
a.setAttribute('target', '_blank');
|
||||||
|
a.setAttribute('href', node.props.url);
|
||||||
|
await appendChildren(node.children, a);
|
||||||
|
return a;
|
||||||
|
},
|
||||||
|
|
||||||
|
async mention(node) {
|
||||||
|
const { username, host, acct } = node.props;
|
||||||
|
const resolved = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host);
|
||||||
|
|
||||||
|
const el = doc.createElement('span');
|
||||||
|
if (!resolved) {
|
||||||
|
el.textContent = acct;
|
||||||
|
} else {
|
||||||
|
el.setAttribute('class', 'h-card');
|
||||||
|
el.setAttribute('translate', 'no');
|
||||||
|
const a = doc.createElement('a');
|
||||||
|
a.setAttribute('href', resolved.url ? resolved.url : resolved.uri);
|
||||||
|
a.className = 'u-url mention';
|
||||||
|
const span = doc.createElement('span');
|
||||||
|
span.textContent = resolved.username || username;
|
||||||
|
a.textContent = '@';
|
||||||
|
a.appendChild(span);
|
||||||
|
el.appendChild(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
return el;
|
||||||
|
},
|
||||||
|
|
||||||
|
async quote(node) {
|
||||||
|
const el = doc.createElement('blockquote');
|
||||||
|
await appendChildren(node.children, el);
|
||||||
|
return el;
|
||||||
|
},
|
||||||
|
|
||||||
|
text(node) {
|
||||||
|
const el = doc.createElement('span');
|
||||||
|
const nodes = node.props.text
|
||||||
|
.split(/\r\n|\r|\n/)
|
||||||
|
.map((x) => doc.createTextNode(x));
|
||||||
|
|
||||||
|
for (const x of intersperse<FIXME | 'br'>('br', nodes)) {
|
||||||
|
el.appendChild(x === 'br' ? doc.createElement('br') : x);
|
||||||
|
}
|
||||||
|
|
||||||
|
return el;
|
||||||
|
},
|
||||||
|
|
||||||
|
url(node) {
|
||||||
|
const a = doc.createElement('a');
|
||||||
|
a.setAttribute('rel', 'nofollow noopener noreferrer');
|
||||||
|
a.setAttribute('target', '_blank');
|
||||||
|
a.setAttribute('href', node.props.url);
|
||||||
|
a.textContent = node.props.url.replace(/^https?:\/\//, '');
|
||||||
|
return a;
|
||||||
|
},
|
||||||
|
|
||||||
|
search: (node) => {
|
||||||
|
const a = doc.createElement('a');
|
||||||
|
a.setAttribute('href', `https"google.com/${node.props.query}`);
|
||||||
|
a.textContent = node.props.content;
|
||||||
|
return a;
|
||||||
|
},
|
||||||
|
|
||||||
|
async plain(node) {
|
||||||
|
const el = doc.createElement('span');
|
||||||
|
await appendChildren(node.children, el);
|
||||||
|
return el;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await appendChildren(nodes, doc.body);
|
||||||
|
|
||||||
|
if (quoteUri !== null) {
|
||||||
|
const a = doc.createElement('a');
|
||||||
|
a.setAttribute('href', quoteUri);
|
||||||
|
a.textContent = quoteUri.replace(/^https?:\/\//, '');
|
||||||
|
|
||||||
|
const quote = doc.createElement('span');
|
||||||
|
quote.setAttribute('class', 'quote-inline');
|
||||||
|
quote.appendChild(doc.createElement('br'));
|
||||||
|
quote.appendChild(doc.createElement('br'));
|
||||||
|
quote.innerHTML += 'RE: ';
|
||||||
|
quote.appendChild(a);
|
||||||
|
|
||||||
|
doc.body.appendChild(quote);
|
||||||
|
}
|
||||||
|
|
||||||
|
return inline ? doc.body.innerHTML : `<p>${doc.body.innerHTML}</p>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { convertId, IdConvertType as IdType, convertAccount, convertAnnouncement, convertFilter, convertAttachment, convertFeaturedTag, convertList } from './converters.js';
|
import { convertAccount, convertAnnouncement, convertFilter, convertAttachment, convertFeaturedTag, convertList } 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';
|
||||||
|
@ -128,7 +128,7 @@ export class MastodonApiServerService {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const data = await client.dismissInstanceAnnouncement(
|
const data = await client.dismissInstanceAnnouncement(
|
||||||
convertId(_request.body['id'], IdType.SharkeyId),
|
_request.body['id'],
|
||||||
);
|
);
|
||||||
reply.send(data.data);
|
reply.send(data.data);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
@ -236,7 +236,7 @@ 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 account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService);
|
||||||
reply.send(await account.verifyCredentials());
|
reply.send(await account.verifyCredentials());
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e); */
|
/* console.error(e); */
|
||||||
|
@ -286,7 +286,7 @@ export class MastodonApiServerService {
|
||||||
ids = [ids];
|
ids = [ids];
|
||||||
}
|
}
|
||||||
users = ids;
|
users = ids;
|
||||||
const account = new ApiAccountMastodon(_request, client, BASE_URL);
|
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService);
|
||||||
reply.send(await account.getRelationships(users));
|
reply.send(await account.getRelationships(users));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e); */
|
/* console.error(e); */
|
||||||
|
@ -302,7 +302,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 sharkId = convertId(_request.params.id, IdType.SharkeyId);
|
const sharkId = _request.params.id;
|
||||||
const data = await client.getAccount(sharkId);
|
const data = await client.getAccount(sharkId);
|
||||||
const profile = await this.userProfilesRepository.findOneBy({ userId: sharkId });
|
const profile = await this.userProfilesRepository.findOneBy({ userId: sharkId });
|
||||||
data.data.fields = profile?.fields.map(f => ({ ...f, verified_at: null })) || [];
|
data.data.fields = profile?.fields.map(f => ({ ...f, verified_at: null })) || [];
|
||||||
|
@ -319,7 +319,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 account = new ApiAccountMastodon(_request, client, BASE_URL);
|
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService);
|
||||||
reply.send(await account.getStatuses());
|
reply.send(await account.getStatuses());
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e);
|
/* console.error(e);
|
||||||
|
@ -347,7 +347,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 account = new ApiAccountMastodon(_request, client, BASE_URL);
|
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService);
|
||||||
reply.send(await account.getFollowers());
|
reply.send(await account.getFollowers());
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e);
|
/* console.error(e);
|
||||||
|
@ -361,7 +361,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 account = new ApiAccountMastodon(_request, client, BASE_URL);
|
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService);
|
||||||
reply.send(await account.getFollowing());
|
reply.send(await account.getFollowing());
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e);
|
/* console.error(e);
|
||||||
|
@ -375,7 +375,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 data = await client.getAccountLists(convertId(_request.params.id, IdType.SharkeyId));
|
const data = await client.getAccountLists(_request.params.id);
|
||||||
reply.send(data.data.map((list) => convertList(list)));
|
reply.send(data.data.map((list) => convertList(list)));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e);
|
/* console.error(e);
|
||||||
|
@ -389,7 +389,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 account = new ApiAccountMastodon(_request, client, BASE_URL);
|
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService);
|
||||||
reply.send(await account.addFollow());
|
reply.send(await account.addFollow());
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e);
|
/* console.error(e);
|
||||||
|
@ -403,7 +403,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 account = new ApiAccountMastodon(_request, client, BASE_URL);
|
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService);
|
||||||
reply.send(await account.rmFollow());
|
reply.send(await account.rmFollow());
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e);
|
/* console.error(e);
|
||||||
|
@ -417,7 +417,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 account = new ApiAccountMastodon(_request, client, BASE_URL);
|
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService);
|
||||||
reply.send(await account.addBlock());
|
reply.send(await account.addBlock());
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e);
|
/* console.error(e);
|
||||||
|
@ -431,7 +431,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 account = new ApiAccountMastodon(_request, client, BASE_URL);
|
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService);
|
||||||
reply.send(await account.rmBlock());
|
reply.send(await account.rmBlock());
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e);
|
/* console.error(e);
|
||||||
|
@ -445,7 +445,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 account = new ApiAccountMastodon(_request, client, BASE_URL);
|
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService);
|
||||||
reply.send(await account.addMute());
|
reply.send(await account.addMute());
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e);
|
/* console.error(e);
|
||||||
|
@ -459,7 +459,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 account = new ApiAccountMastodon(_request, client, BASE_URL);
|
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService);
|
||||||
reply.send(await account.rmMute());
|
reply.send(await account.rmMute());
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e);
|
/* console.error(e);
|
||||||
|
@ -487,7 +487,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 account = new ApiAccountMastodon(_request, client, BASE_URL);
|
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService);
|
||||||
reply.send(await account.getBookmarks());
|
reply.send(await account.getBookmarks());
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e);
|
/* console.error(e);
|
||||||
|
@ -501,7 +501,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 account = new ApiAccountMastodon(_request, client, BASE_URL);
|
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService);
|
||||||
reply.send(await account.getFavourites());
|
reply.send(await account.getFavourites());
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e);
|
/* console.error(e);
|
||||||
|
@ -515,7 +515,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 account = new ApiAccountMastodon(_request, client, BASE_URL);
|
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService);
|
||||||
reply.send(await account.getMutes());
|
reply.send(await account.getMutes());
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e);
|
/* console.error(e);
|
||||||
|
@ -529,7 +529,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 account = new ApiAccountMastodon(_request, client, BASE_URL);
|
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService);
|
||||||
reply.send(await account.getBlocks());
|
reply.send(await account.getBlocks());
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e);
|
/* console.error(e);
|
||||||
|
@ -557,7 +557,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 account = new ApiAccountMastodon(_request, client, BASE_URL);
|
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService);
|
||||||
reply.send(await account.acceptFollow());
|
reply.send(await account.acceptFollow());
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e);
|
/* console.error(e);
|
||||||
|
@ -571,7 +571,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 account = new ApiAccountMastodon(_request, client, BASE_URL);
|
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService);
|
||||||
reply.send(await account.rejectFollow());
|
reply.send(await account.rejectFollow());
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e);
|
/* console.error(e);
|
||||||
|
@ -813,7 +813,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 data = await client.updateMedia(convertId(_request.params.id, IdType.SharkeyId), _request.body!);
|
const data = await client.updateMedia(_request.params.id, _request.body!);
|
||||||
reply.send(convertAttachment(data.data));
|
reply.send(convertAttachment(data.data));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
/* console.error(e); */
|
/* console.error(e); */
|
||||||
|
|
|
@ -3,14 +3,13 @@ import { MfmService } from '@/core/MfmService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { Entity } from 'megalodon';
|
import { Entity } from 'megalodon';
|
||||||
import { parse } from 'mfm-js';
|
import mfm from 'mfm-js';
|
||||||
import { GetterService } from '../GetterService.js';
|
import { GetterService } from '../GetterService.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, UsersRepository } from '@/models/_.js';
|
import type { NoteEditRepository, NotesRepository, UsersRepository } from '@/models/_.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||||
const CHAR_COLLECTION = '0123456789abcdefghijklmnopqrstuvwxyz';
|
|
||||||
|
|
||||||
export enum IdConvertType {
|
export enum IdConvertType {
|
||||||
MastodonId,
|
MastodonId,
|
||||||
|
@ -49,7 +48,7 @@ export class MastoConverters {
|
||||||
this.GetterService = new GetterService(this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService);
|
this.GetterService = new GetterService(this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService);
|
||||||
}
|
}
|
||||||
|
|
||||||
private encode(u: MiUser, m: IMentionedRemoteUsers): MastodonEntity.Mention {
|
private encode(u: MiUser, m: IMentionedRemoteUsers): Entity.Mention {
|
||||||
let acct = u.username;
|
let acct = u.username;
|
||||||
let acctUrl = `https://${u.host || this.config.host}/@${u.username}`;
|
let acctUrl = `https://${u.host || this.config.host}/@${u.username}`;
|
||||||
let url: string | null = null;
|
let url: string | null = null;
|
||||||
|
@ -73,89 +72,97 @@ export class MastoConverters {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async convertAccount(account: Entity.Account) {
|
||||||
|
return awaitAll({
|
||||||
|
id: account.id,
|
||||||
|
username: account.username,
|
||||||
|
acct: account.acct,
|
||||||
|
fqn: account.fqn,
|
||||||
|
display_name: account.display_name || account.username,
|
||||||
|
locked: account.locked,
|
||||||
|
created_at: account.created_at,
|
||||||
|
followers_count: account.followers_count,
|
||||||
|
following_count: account.following_count,
|
||||||
|
statuses_count: account.statuses_count,
|
||||||
|
note: account.note,
|
||||||
|
url: account.url,
|
||||||
|
avatar: account.avatar,
|
||||||
|
avatar_static: account.avatar,
|
||||||
|
header: account.header,
|
||||||
|
header_static: account.header,
|
||||||
|
emojis: account.emojis,
|
||||||
|
moved: null, //FIXME
|
||||||
|
fields: [],
|
||||||
|
bot: false,
|
||||||
|
discoverable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public async convertStatus(status: Entity.Status) {
|
public async convertStatus(status: Entity.Status) {
|
||||||
status.account = 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);
|
||||||
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 =>
|
const mentions = Promise.all(note.mentions.map(p =>
|
||||||
this.getUser(p)
|
this.getUser(p)
|
||||||
.then(u => this.encode(u, JSON.parse(note.mentionedRemoteUsers)))
|
.then(u => this.encode(u, JSON.parse(note.mentionedRemoteUsers)))
|
||||||
.catch(() => null)))
|
.catch(() => null)))
|
||||||
.then(p => p.filter(m => m)) as Promise<MastodonEntity.Mention[]>;
|
.then(p => p.filter(m => m)) as Promise<Entity.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>") as string : status.content;
|
|
||||||
if (status.poll) status.poll = convertPoll(status.poll);
|
|
||||||
if (status.reblog) status.reblog = convertStatus(status.reblog);
|
|
||||||
|
|
||||||
return status;
|
const content = note.text !== null
|
||||||
}
|
? this.MfmService.toMastoHtml(mfm.parse(note.text!), JSON.parse(note.mentionedRemoteUsers), false, null)
|
||||||
}
|
.then(p => p ?? escapeMFM(note.text!))
|
||||||
|
: '';
|
||||||
|
|
||||||
export function convertId(in_id: string, id_convert_type: IdConvertType): string {
|
const tags = note.tags.map(tag => {
|
||||||
switch (id_convert_type) {
|
return {
|
||||||
case IdConvertType.MastodonId: {
|
name: tag,
|
||||||
let out = BigInt(0);
|
url: `${this.config.url}/tags/${tag}`,
|
||||||
const lowerCaseId = in_id.toLowerCase();
|
} as Entity.Tag;
|
||||||
for (let i = 0; i < lowerCaseId.length; i++) {
|
});
|
||||||
const charValue = numFromChar(lowerCaseId.charAt(i));
|
|
||||||
out += BigInt(charValue) * BigInt(36) ** BigInt(i);
|
|
||||||
}
|
|
||||||
return out.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
case IdConvertType.SharkeyId: {
|
// noinspection ES6MissingAwait
|
||||||
let input = BigInt(in_id);
|
return await awaitAll({
|
||||||
let outStr = '';
|
id: note.id,
|
||||||
while (input > BigInt(0)) {
|
uri: note.uri ?? `https://${this.config.host}/notes/${note.id}`,
|
||||||
const remainder = Number(input % BigInt(36));
|
url: note.url ?? note.uri ?? `https://${this.config.host}/notes/${note.id}`,
|
||||||
outStr = charFromNum(remainder) + outStr;
|
account: convertedAccount,
|
||||||
input /= BigInt(36);
|
in_reply_to_id: note.replyId,
|
||||||
}
|
in_reply_to_account_id: note.replyUserId,
|
||||||
const ReversedoutStr = outStr.split('').reduce((acc, char) => char + acc, '');
|
reblog: status.reblog,
|
||||||
return ReversedoutStr;
|
content: content,
|
||||||
}
|
content_type: 'text/x.misskeymarkdown',
|
||||||
|
text: note.text,
|
||||||
default:
|
created_at: status.created_at,
|
||||||
throw new Error('Invalid ID conversion type');
|
emojis: status.emojis,
|
||||||
}
|
replies_count: note.repliesCount,
|
||||||
}
|
reblogs_count: note.renoteCount,
|
||||||
|
favourites_count: status.favourites_count,
|
||||||
function numFromChar(character: string): number {
|
reblogged: false,
|
||||||
for (let i = 0; i < CHAR_COLLECTION.length; i++) {
|
favourited: status.favourited,
|
||||||
if (CHAR_COLLECTION.charAt(i) === character) {
|
muted: status.muted,
|
||||||
return i;
|
sensitive: status.sensitive,
|
||||||
}
|
spoiler_text: note.cw ? note.cw : '',
|
||||||
}
|
visibility: status.visibility,
|
||||||
|
media_attachments: status.media_attachments,
|
||||||
throw new Error('Invalid character in parsed base36 id');
|
mentions: mentions,
|
||||||
}
|
tags: tags,
|
||||||
|
card: null, //FIXME
|
||||||
function charFromNum(number: number): string {
|
poll: status.poll ?? null,
|
||||||
if (number >= 0 && number < CHAR_COLLECTION.length) {
|
application: null, //FIXME
|
||||||
return CHAR_COLLECTION.charAt(number);
|
language: null, //FIXME
|
||||||
} else {
|
pinned: null,
|
||||||
throw new Error('Invalid number for base-36 encoding');
|
reactions: status.emoji_reactions,
|
||||||
|
emoji_reactions: status.emoji_reactions,
|
||||||
|
bookmarked: false,
|
||||||
|
quote: false,
|
||||||
|
edited_at: note.updatedAt?.toISOString(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function simpleConvert(data: any) {
|
function simpleConvert(data: any) {
|
||||||
// copy the object to bypass weird pass by reference bugs
|
// copy the object to bypass weird pass by reference bugs
|
||||||
const result = Object.assign({}, data);
|
const result = Object.assign({}, data);
|
||||||
result.id = convertId(data.id, IdConvertType.MastodonId);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,7 +187,6 @@ export function convertFeaturedTag(tag: Entity.FeaturedTag) {
|
||||||
|
|
||||||
export function convertNotification(notification: Entity.Notification) {
|
export function convertNotification(notification: Entity.Notification) {
|
||||||
notification.account = convertAccount(notification.account);
|
notification.account = convertAccount(notification.account);
|
||||||
notification.id = convertId(notification.id, IdConvertType.MastodonId);
|
|
||||||
if (notification.status) notification.status = convertStatus(notification.status);
|
if (notification.status) notification.status = convertStatus(notification.status);
|
||||||
return notification;
|
return notification;
|
||||||
}
|
}
|
||||||
|
@ -200,19 +206,9 @@ export function convertRelationship(relationship: Entity.Relationship) {
|
||||||
|
|
||||||
export function convertStatus(status: Entity.Status) {
|
export function convertStatus(status: Entity.Status) {
|
||||||
status.account = convertAccount(status.account);
|
status.account = convertAccount(status.account);
|
||||||
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) =>
|
status.media_attachments = status.media_attachments.map((attachment) =>
|
||||||
convertAttachment(attachment),
|
convertAttachment(attachment),
|
||||||
);
|
);
|
||||||
status.mentions = status.mentions.map((mention) => ({
|
|
||||||
...mention,
|
|
||||||
id: convertId(mention.id, IdConvertType.MastodonId),
|
|
||||||
}));
|
|
||||||
if (status.poll) status.poll = convertPoll(status.poll);
|
if (status.poll) status.poll = convertPoll(status.poll);
|
||||||
if (status.reblog) status.reblog = convertStatus(status.reblog);
|
if (status.reblog) status.reblog = convertStatus(status.reblog);
|
||||||
|
|
||||||
|
@ -224,7 +220,6 @@ export function convertStatusSource(status: Entity.StatusSource) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertConversation(conversation: Entity.Conversation) {
|
export function convertConversation(conversation: Entity.Conversation) {
|
||||||
conversation.id = convertId(conversation.id, IdConvertType.MastodonId);
|
|
||||||
conversation.accounts = conversation.accounts.map(convertAccount);
|
conversation.accounts = conversation.accounts.map(convertAccount);
|
||||||
if (conversation.last_status) {
|
if (conversation.last_status) {
|
||||||
conversation.last_status = convertStatus(conversation.last_status);
|
conversation.last_status = convertStatus(conversation.last_status);
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
import { convertId, IdConvertType as IdType, convertAccount, convertRelationship, convertStatus } from '../converters.js';
|
import { MastoConverters, convertRelationship } from '../converters.js';
|
||||||
import { argsToBools, convertTimelinesArgsId, limitToInt } from './timeline.js';
|
import { argsToBools, limitToInt } from './timeline.js';
|
||||||
import type { MegalodonInterface } from 'megalodon';
|
import type { MegalodonInterface } from 'megalodon';
|
||||||
import type { FastifyRequest } from 'fastify';
|
import type { FastifyRequest } from 'fastify';
|
||||||
|
import { NoteEditRepository, NotesRepository, UsersRepository } from '@/models/_.js';
|
||||||
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
import type { Config } from '@/config.js';
|
||||||
|
|
||||||
const relationshipModel = {
|
const relationshipModel = {
|
||||||
id: '',
|
id: '',
|
||||||
|
@ -24,18 +27,25 @@ export class ApiAccountMastodon {
|
||||||
private request: FastifyRequest;
|
private request: FastifyRequest;
|
||||||
private client: MegalodonInterface;
|
private client: MegalodonInterface;
|
||||||
private BASE_URL: string;
|
private BASE_URL: string;
|
||||||
|
private mastoconverter: MastoConverters;
|
||||||
|
|
||||||
constructor(request: FastifyRequest, client: MegalodonInterface, BASE_URL: string) {
|
constructor(request: FastifyRequest, client: MegalodonInterface, BASE_URL: string,
|
||||||
|
config: Config,
|
||||||
|
usersrepo: UsersRepository,
|
||||||
|
notesrepo: NotesRepository,
|
||||||
|
noteeditrepo: NoteEditRepository,
|
||||||
|
userentity: UserEntityService,
|
||||||
|
) {
|
||||||
this.request = request;
|
this.request = request;
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.BASE_URL = BASE_URL;
|
this.BASE_URL = BASE_URL;
|
||||||
|
this.mastoconverter = new MastoConverters(config, usersrepo, notesrepo, noteeditrepo, userentity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async verifyCredentials() {
|
public async verifyCredentials() {
|
||||||
try {
|
try {
|
||||||
const data = await this.client.verifyAccountCredentials();
|
const data = await this.client.verifyAccountCredentials();
|
||||||
const acct = data.data;
|
const acct = data.data;
|
||||||
acct.id = convertId(acct.id, IdType.MastodonId);
|
|
||||||
acct.display_name = acct.display_name || acct.username;
|
acct.display_name = acct.display_name || acct.username;
|
||||||
acct.url = `${this.BASE_URL}/@${acct.url}`;
|
acct.url = `${this.BASE_URL}/@${acct.url}`;
|
||||||
acct.note = acct.note || '';
|
acct.note = acct.note || '';
|
||||||
|
@ -61,7 +71,7 @@ export class ApiAccountMastodon {
|
||||||
public async lookup() {
|
public async lookup() {
|
||||||
try {
|
try {
|
||||||
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 this.mastoconverter.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); */
|
||||||
|
@ -79,7 +89,7 @@ export class ApiAccountMastodon {
|
||||||
|
|
||||||
const reqIds = [];
|
const reqIds = [];
|
||||||
for (let i = 0; i < users.length; i++) {
|
for (let i = 0; i < users.length; i++) {
|
||||||
reqIds.push(convertId(users[i], IdType.SharkeyId));
|
reqIds.push(users[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await this.client.getRelationships(reqIds);
|
const data = await this.client.getRelationships(reqIds);
|
||||||
|
@ -93,11 +103,8 @@ export class ApiAccountMastodon {
|
||||||
|
|
||||||
public async getStatuses() {
|
public async getStatuses() {
|
||||||
try {
|
try {
|
||||||
const data = await this.client.getAccountStatuses(
|
const data = await this.client.getAccountStatuses((this.request.params as any).id, argsToBools(limitToInt(this.request.query as any)));
|
||||||
convertId((this.request.params as any).id, IdType.SharkeyId),
|
return data.data.map((status) => this.mastoconverter.convertStatus(status));
|
||||||
convertTimelinesArgsId(argsToBools(limitToInt(this.request.query as any)))
|
|
||||||
);
|
|
||||||
return data.data.map((status) => convertStatus(status));
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
console.error(e.response.data);
|
console.error(e.response.data);
|
||||||
|
@ -108,10 +115,10 @@ export class ApiAccountMastodon {
|
||||||
public async getFollowers() {
|
public async getFollowers() {
|
||||||
try {
|
try {
|
||||||
const data = await this.client.getAccountFollowers(
|
const data = await this.client.getAccountFollowers(
|
||||||
convertId((this.request.params as any).id, IdType.SharkeyId),
|
(this.request.params as any).id,
|
||||||
convertTimelinesArgsId(limitToInt(this.request.query as any)),
|
limitToInt(this.request.query as any),
|
||||||
);
|
);
|
||||||
return data.data.map((account) => convertAccount(account));
|
return data.data.map((account) => this.mastoconverter.convertAccount(account));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
console.error(e.response.data);
|
console.error(e.response.data);
|
||||||
|
@ -122,10 +129,10 @@ export class ApiAccountMastodon {
|
||||||
public async getFollowing() {
|
public async getFollowing() {
|
||||||
try {
|
try {
|
||||||
const data = await this.client.getAccountFollowing(
|
const data = await this.client.getAccountFollowing(
|
||||||
convertId((this.request.params as any).id, IdType.SharkeyId),
|
(this.request.params as any).id,
|
||||||
convertTimelinesArgsId(limitToInt(this.request.query as any)),
|
limitToInt(this.request.query as any),
|
||||||
);
|
);
|
||||||
return data.data.map((account) => convertAccount(account));
|
return data.data.map((account) => this.mastoconverter.convertAccount(account));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
console.error(e.response.data);
|
console.error(e.response.data);
|
||||||
|
@ -135,7 +142,7 @@ export class ApiAccountMastodon {
|
||||||
|
|
||||||
public async addFollow() {
|
public async addFollow() {
|
||||||
try {
|
try {
|
||||||
const data = await this.client.followAccount( convertId((this.request.params as any).id, IdType.SharkeyId) );
|
const data = await this.client.followAccount( (this.request.params as any).id );
|
||||||
const acct = convertRelationship(data.data);
|
const acct = convertRelationship(data.data);
|
||||||
acct.following = true;
|
acct.following = true;
|
||||||
return acct;
|
return acct;
|
||||||
|
@ -148,7 +155,7 @@ export class ApiAccountMastodon {
|
||||||
|
|
||||||
public async rmFollow() {
|
public async rmFollow() {
|
||||||
try {
|
try {
|
||||||
const data = await this.client.unfollowAccount( convertId((this.request.params as any).id, IdType.SharkeyId) );
|
const data = await this.client.unfollowAccount( (this.request.params as any).id );
|
||||||
const acct = convertRelationship(data.data);
|
const acct = convertRelationship(data.data);
|
||||||
acct.following = false;
|
acct.following = false;
|
||||||
return acct;
|
return acct;
|
||||||
|
@ -161,7 +168,7 @@ export class ApiAccountMastodon {
|
||||||
|
|
||||||
public async addBlock() {
|
public async addBlock() {
|
||||||
try {
|
try {
|
||||||
const data = await this.client.blockAccount( convertId((this.request.params as any).id, IdType.SharkeyId) );
|
const data = await this.client.blockAccount( (this.request.params as any).id );
|
||||||
return convertRelationship(data.data);
|
return convertRelationship(data.data);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -172,7 +179,7 @@ export class ApiAccountMastodon {
|
||||||
|
|
||||||
public async rmBlock() {
|
public async rmBlock() {
|
||||||
try {
|
try {
|
||||||
const data = await this.client.unblockAccount( convertId((this.request.params as any).id, IdType.SharkeyId) );
|
const data = await this.client.unblockAccount( (this.request.params as any).id );
|
||||||
return convertRelationship(data.data);
|
return convertRelationship(data.data);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -184,7 +191,7 @@ export class ApiAccountMastodon {
|
||||||
public async addMute() {
|
public async addMute() {
|
||||||
try {
|
try {
|
||||||
const data = await this.client.muteAccount(
|
const data = await this.client.muteAccount(
|
||||||
convertId((this.request.params as any).id, IdType.SharkeyId),
|
(this.request.params as any).id,
|
||||||
this.request.body as any,
|
this.request.body as any,
|
||||||
);
|
);
|
||||||
return convertRelationship(data.data);
|
return convertRelationship(data.data);
|
||||||
|
@ -197,7 +204,7 @@ export class ApiAccountMastodon {
|
||||||
|
|
||||||
public async rmMute() {
|
public async rmMute() {
|
||||||
try {
|
try {
|
||||||
const data = await this.client.unmuteAccount( convertId((this.request.params as any).id, IdType.SharkeyId) );
|
const data = await this.client.unmuteAccount( (this.request.params as any).id );
|
||||||
return convertRelationship(data.data);
|
return convertRelationship(data.data);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -208,8 +215,8 @@ export class ApiAccountMastodon {
|
||||||
|
|
||||||
public async getBookmarks() {
|
public async getBookmarks() {
|
||||||
try {
|
try {
|
||||||
const data = await this.client.getBookmarks( convertTimelinesArgsId(limitToInt(this.request.query as any)) );
|
const data = await this.client.getBookmarks( limitToInt(this.request.query as any) );
|
||||||
return data.data.map((status) => convertStatus(status));
|
return data.data.map((status) => 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);
|
||||||
|
@ -219,8 +226,8 @@ export class ApiAccountMastodon {
|
||||||
|
|
||||||
public async getFavourites() {
|
public async getFavourites() {
|
||||||
try {
|
try {
|
||||||
const data = await this.client.getFavourites( convertTimelinesArgsId(limitToInt(this.request.query as any)) );
|
const data = await this.client.getFavourites( limitToInt(this.request.query as any) );
|
||||||
return data.data.map((status) => convertStatus(status));
|
return data.data.map((status) => 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);
|
||||||
|
@ -230,8 +237,8 @@ export class ApiAccountMastodon {
|
||||||
|
|
||||||
public async getMutes() {
|
public async getMutes() {
|
||||||
try {
|
try {
|
||||||
const data = await this.client.getMutes( convertTimelinesArgsId(limitToInt(this.request.query as any)) );
|
const data = await this.client.getMutes( limitToInt(this.request.query as any) );
|
||||||
return data.data.map((account) => convertAccount(account));
|
return data.data.map((account) => this.mastoconverter.convertAccount(account));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
console.error(e.response.data);
|
console.error(e.response.data);
|
||||||
|
@ -241,8 +248,8 @@ export class ApiAccountMastodon {
|
||||||
|
|
||||||
public async getBlocks() {
|
public async getBlocks() {
|
||||||
try {
|
try {
|
||||||
const data = await this.client.getBlocks( convertTimelinesArgsId(limitToInt(this.request.query as any)) );
|
const data = await this.client.getBlocks( limitToInt(this.request.query as any) );
|
||||||
return data.data.map((account) => convertAccount(account));
|
return data.data.map((account) => this.mastoconverter.convertAccount(account));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
console.error(e.response.data);
|
console.error(e.response.data);
|
||||||
|
@ -252,7 +259,7 @@ export class ApiAccountMastodon {
|
||||||
|
|
||||||
public async acceptFollow() {
|
public async acceptFollow() {
|
||||||
try {
|
try {
|
||||||
const data = await this.client.acceptFollowRequest( convertId((this.request.params as any).id, IdType.SharkeyId) );
|
const data = await this.client.acceptFollowRequest( (this.request.params as any).id );
|
||||||
return convertRelationship(data.data);
|
return convertRelationship(data.data);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -263,7 +270,7 @@ export class ApiAccountMastodon {
|
||||||
|
|
||||||
public async rejectFollow() {
|
public async rejectFollow() {
|
||||||
try {
|
try {
|
||||||
const data = await this.client.rejectFollowRequest( convertId((this.request.params as any).id, IdType.SharkeyId) );
|
const data = await this.client.rejectFollowRequest( (this.request.params as any).id );
|
||||||
return convertRelationship(data.data);
|
return convertRelationship(data.data);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { IdConvertType as IdType, convertId, convertFilter } from '../converters.js';
|
import { convertFilter } from '../converters.js';
|
||||||
import type { MegalodonInterface } from 'megalodon';
|
import type { MegalodonInterface } from 'megalodon';
|
||||||
import type { FastifyRequest } from 'fastify';
|
import type { FastifyRequest } from 'fastify';
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ export class ApiFilterMastodon {
|
||||||
|
|
||||||
public async getFilter() {
|
public async getFilter() {
|
||||||
try {
|
try {
|
||||||
const data = await this.client.getFilter( convertId((this.request.params as any).id, IdType.SharkeyId) );
|
const data = await this.client.getFilter( (this.request.params as any).id );
|
||||||
return convertFilter(data.data);
|
return convertFilter(data.data);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -45,7 +45,7 @@ export class ApiFilterMastodon {
|
||||||
public async updateFilter() {
|
public async updateFilter() {
|
||||||
try {
|
try {
|
||||||
const body: any = this.request.body;
|
const body: any = this.request.body;
|
||||||
const data = await this.client.updateFilter(convertId((this.request.params as any).id, IdType.SharkeyId), body.pharse, body.context);
|
const data = await this.client.updateFilter((this.request.params as any).id, body.pharse, body.context);
|
||||||
return convertFilter(data.data);
|
return convertFilter(data.data);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -55,7 +55,7 @@ export class ApiFilterMastodon {
|
||||||
|
|
||||||
public async rmFilter() {
|
public async rmFilter() {
|
||||||
try {
|
try {
|
||||||
const data = await this.client.deleteFilter( convertId((this.request.params as any).id, IdType.SharkeyId) );
|
const data = await this.client.deleteFilter( (this.request.params as any).id );
|
||||||
return data.data;
|
return data.data;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { IdConvertType as IdType, convertId, convertNotification } from '../converters.js';
|
import { convertNotification } from '../converters.js';
|
||||||
import { convertTimelinesArgsId } from './timeline.js';
|
|
||||||
import type { MegalodonInterface, Entity } from 'megalodon';
|
import type { MegalodonInterface, Entity } from 'megalodon';
|
||||||
import type { FastifyRequest } from 'fastify';
|
import type { FastifyRequest } from 'fastify';
|
||||||
|
|
||||||
|
@ -19,7 +18,7 @@ export class ApiNotifyMastodon {
|
||||||
|
|
||||||
public async getNotifications() {
|
public async getNotifications() {
|
||||||
try {
|
try {
|
||||||
const data = await this.client.getNotifications( convertTimelinesArgsId(toLimitToInt(this.request.query)) );
|
const data = await this.client.getNotifications( toLimitToInt(this.request.query) );
|
||||||
const notifs = data.data;
|
const notifs = data.data;
|
||||||
const processed = notifs.map((n: Entity.Notification) => {
|
const processed = notifs.map((n: Entity.Notification) => {
|
||||||
const convertedn = convertNotification(n);
|
const convertedn = convertNotification(n);
|
||||||
|
@ -39,7 +38,7 @@ export class ApiNotifyMastodon {
|
||||||
|
|
||||||
public async getNotification() {
|
public async getNotification() {
|
||||||
try {
|
try {
|
||||||
const data = await this.client.getNotification( convertId((this.request.params as any).id, IdType.SharkeyId) );
|
const data = await this.client.getNotification( (this.request.params as any).id );
|
||||||
const notif = convertNotification(data.data);
|
const notif = convertNotification(data.data);
|
||||||
if (notif.type !== 'follow' && notif.type !== 'follow_request' && notif.type === 'reaction') notif.type = 'favourite';
|
if (notif.type !== 'follow' && notif.type !== 'follow_request' && notif.type === 'reaction') notif.type = 'favourite';
|
||||||
return notif;
|
return notif;
|
||||||
|
@ -51,7 +50,7 @@ export class ApiNotifyMastodon {
|
||||||
|
|
||||||
public async rmNotification() {
|
public async rmNotification() {
|
||||||
try {
|
try {
|
||||||
const data = await this.client.dismissNotification( convertId((this.request.params as any).id, IdType.SharkeyId) );
|
const data = await this.client.dismissNotification( (this.request.params as any).id );
|
||||||
return data.data;
|
return data.data;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Converter } from 'megalodon';
|
import { Converter } from 'megalodon';
|
||||||
import { convertAccount, convertStatus } from '../converters.js';
|
import { convertAccount, convertStatus } from '../converters.js';
|
||||||
import { convertTimelinesArgsId, 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';
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ export class ApiSearchMastodon {
|
||||||
|
|
||||||
public async SearchV1() {
|
public async SearchV1() {
|
||||||
try {
|
try {
|
||||||
const query: any = convertTimelinesArgsId(limitToInt(this.request.query as any));
|
const query: any = limitToInt(this.request.query as any);
|
||||||
const type = query.type || '';
|
const type = query.type || '';
|
||||||
const data = await this.client.search(query.q, { type: type, ...query });
|
const data = await this.client.search(query.q, { type: type, ...query });
|
||||||
return data.data;
|
return data.data;
|
||||||
|
@ -83,7 +83,7 @@ export class ApiSearchMastodon {
|
||||||
|
|
||||||
public async SearchV2() {
|
public async SearchV2() {
|
||||||
try {
|
try {
|
||||||
const query: any = convertTimelinesArgsId(limitToInt(this.request.query as any));
|
const query: any = limitToInt(this.request.query as any);
|
||||||
const type = query.type;
|
const type = query.type;
|
||||||
const acct = !type || type === 'accounts' ? await this.client.search(query.q, { type: 'accounts', ...query }) : null;
|
const acct = !type || type === 'accounts' ? await this.client.search(query.q, { type: 'accounts', ...query }) : null;
|
||||||
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;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
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, convertStatusSource, MastoConverters } from '../converters.js';
|
import { convertAttachment, convertPoll, convertStatusSource, MastoConverters } from '../converters.js';
|
||||||
import { getClient } from '../MastodonApiServerService.js';
|
import { getClient } from '../MastodonApiServerService.js';
|
||||||
import { convertTimelinesArgsId, limitToInt } from './timeline.js';
|
import { 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 type { Config } from '@/config.js';
|
||||||
|
@ -29,7 +29,7 @@ export class ApiStatusMastodon {
|
||||||
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 data = await client.getStatus(convertId(_request.params.id, IdType.SharkeyId));
|
const data = await client.getStatus(_request.params.id);
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(data.data));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -44,8 +44,8 @@ export class ApiStatusMastodon {
|
||||||
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 data = await client.getStatusSource(convertId(_request.params.id, IdType.SharkeyId));
|
const data = await client.getStatusSource(_request.params.id);
|
||||||
reply.send(convertStatusSource(data.data));
|
reply.send(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);
|
||||||
|
@ -60,10 +60,7 @@ export class ApiStatusMastodon {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
const query: any = _request.query;
|
const query: any = _request.query;
|
||||||
try {
|
try {
|
||||||
const data = await client.getStatusContext(
|
const data = await client.getStatusContext(_request.params.id, limitToInt(query));
|
||||||
convertId(_request.params.id, IdType.SharkeyId),
|
|
||||||
convertTimelinesArgsId(limitToInt(query)),
|
|
||||||
);
|
|
||||||
data.data.ancestors = await Promise.all(data.data.ancestors.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status)));
|
data.data.ancestors = await Promise.all(data.data.ancestors.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status)));
|
||||||
data.data.descendants = await Promise.all(data.data.descendants.map(async (status: Entity.Status) => await this.mastoconverter.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);
|
||||||
|
@ -91,8 +88,8 @@ export class ApiStatusMastodon {
|
||||||
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 data = await client.getStatusRebloggedBy(convertId(_request.params.id, IdType.SharkeyId));
|
const data = await client.getStatusRebloggedBy(_request.params.id);
|
||||||
reply.send(data.data.map((account: Entity.Account) => convertAccount(account)));
|
reply.send(data.data.map((account: Entity.Account) => this.mastoconverter.convertAccount(account)));
|
||||||
} 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);
|
||||||
|
@ -106,8 +103,8 @@ export class ApiStatusMastodon {
|
||||||
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 data = await client.getStatusFavouritedBy(convertId(_request.params.id, IdType.SharkeyId));
|
const data = await client.getStatusFavouritedBy(_request.params.id);
|
||||||
reply.send(data.data.map((account: Entity.Account) => convertAccount(account)));
|
reply.send(data.data.map((account: Entity.Account) => this.mastoconverter.convertAccount(account)));
|
||||||
} 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);
|
||||||
|
@ -121,7 +118,7 @@ export class ApiStatusMastodon {
|
||||||
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 data = await client.getMedia(convertId(_request.params.id, IdType.SharkeyId));
|
const data = await client.getMedia(_request.params.id);
|
||||||
reply.send(convertAttachment(data.data));
|
reply.send(convertAttachment(data.data));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -136,7 +133,7 @@ export class ApiStatusMastodon {
|
||||||
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 data = await client.getPoll(convertId(_request.params.id, IdType.SharkeyId));
|
const data = await client.getPoll(_request.params.id);
|
||||||
reply.send(convertPoll(data.data));
|
reply.send(convertPoll(data.data));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -152,7 +149,7 @@ export class ApiStatusMastodon {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
const body: any = _request.body;
|
const body: any = _request.body;
|
||||||
try {
|
try {
|
||||||
const data = await client.votePoll(convertId(_request.params.id, IdType.SharkeyId), body.choices);
|
const data = await client.votePoll(_request.params.id, body.choices);
|
||||||
reply.send(convertPoll(data.data));
|
reply.send(convertPoll(data.data));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -168,8 +165,6 @@ export class ApiStatusMastodon {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
let body: any = _request.body;
|
let body: any = _request.body;
|
||||||
try {
|
try {
|
||||||
if (body.in_reply_to_id) body.in_reply_to_id = convertId(body.in_reply_to_id, IdType.SharkeyId);
|
|
||||||
if (body.quote_id) body.quote_id = convertId(body.quote_id, IdType.SharkeyId);
|
|
||||||
if (
|
if (
|
||||||
(!body.poll && body['poll[options][]']) ||
|
(!body.poll && body['poll[options][]']) ||
|
||||||
(!body.media_ids && body['media_ids[]'])
|
(!body.media_ids && body['media_ids[]'])
|
||||||
|
@ -201,9 +196,6 @@ export class ApiStatusMastodon {
|
||||||
}
|
}
|
||||||
if (!body.media_ids) body.media_ids = undefined;
|
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.length) body.media_ids = undefined;
|
||||||
if (body.media_ids) {
|
|
||||||
body.media_ids = (body.media_ids as string[]).map((p) => convertId(p, IdType.SharkeyId));
|
|
||||||
}
|
|
||||||
|
|
||||||
const { sensitive } = body;
|
const { sensitive } = body;
|
||||||
body.sensitive = typeof sensitive === 'string' ? sensitive === 'true' : sensitive;
|
body.sensitive = typeof sensitive === 'string' ? sensitive === 'true' : sensitive;
|
||||||
|
@ -241,10 +233,7 @@ export class ApiStatusMastodon {
|
||||||
try {
|
try {
|
||||||
if (!body.media_ids) body.media_ids = undefined;
|
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.length) body.media_ids = undefined;
|
||||||
if (body.media_ids) {
|
const data = await client.editStatus(_request.params.id, body);
|
||||||
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);
|
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(data.data));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -259,10 +248,7 @@ export class ApiStatusMastodon {
|
||||||
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 data = (await client.createEmojiReaction(
|
const data = (await client.createEmojiReaction(_request.params.id, '❤')) as any;
|
||||||
convertId(_request.params.id, IdType.SharkeyId),
|
|
||||||
'❤',
|
|
||||||
)) as any;
|
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(data.data));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -277,10 +263,7 @@ export class ApiStatusMastodon {
|
||||||
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 data = await client.deleteEmojiReaction(
|
const data = await client.deleteEmojiReaction(_request.params.id, '❤');
|
||||||
convertId(_request.params.id, IdType.SharkeyId),
|
|
||||||
'❤',
|
|
||||||
);
|
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(data.data));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -295,7 +278,7 @@ export class ApiStatusMastodon {
|
||||||
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 data = await client.reblogStatus(convertId(_request.params.id, IdType.SharkeyId));
|
const data = await client.reblogStatus(_request.params.id);
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(data.data));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -310,7 +293,7 @@ export class ApiStatusMastodon {
|
||||||
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 data = await client.unreblogStatus(convertId(_request.params.id, IdType.SharkeyId));
|
const data = await client.unreblogStatus(_request.params.id);
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(data.data));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -325,7 +308,7 @@ export class ApiStatusMastodon {
|
||||||
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 data = await client.bookmarkStatus(convertId(_request.params.id, IdType.SharkeyId));
|
const data = await client.bookmarkStatus(_request.params.id);
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(data.data));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -340,7 +323,7 @@ export class ApiStatusMastodon {
|
||||||
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 data = await client.unbookmarkStatus(convertId(_request.params.id, IdType.SharkeyId));
|
const data = await client.unbookmarkStatus(_request.params.id);
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(data.data));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -355,7 +338,7 @@ export class ApiStatusMastodon {
|
||||||
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 data = await client.pinStatus(convertId(_request.params.id, IdType.SharkeyId));
|
const data = await client.pinStatus(_request.params.id);
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(data.data));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -370,7 +353,7 @@ export class ApiStatusMastodon {
|
||||||
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 data = await client.unpinStatus(convertId(_request.params.id, IdType.SharkeyId));
|
const data = await client.unpinStatus(_request.params.id);
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(data.data));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -385,7 +368,7 @@ export class ApiStatusMastodon {
|
||||||
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 data = await client.createEmojiReaction(convertId(_request.params.id, IdType.SharkeyId), _request.params.name);
|
const data = await client.createEmojiReaction(_request.params.id, _request.params.name);
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(data.data));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -400,7 +383,7 @@ export class ApiStatusMastodon {
|
||||||
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 data = await client.deleteEmojiReaction(convertId(_request.params.id, IdType.SharkeyId), _request.params.name);
|
const data = await client.deleteEmojiReaction(_request.params.id, _request.params.name);
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
reply.send(await this.mastoconverter.convertStatus(data.data));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -415,7 +398,7 @@ export class ApiStatusMastodon {
|
||||||
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 data = await client.deleteStatus(convertId(_request.params.id, IdType.SharkeyId));
|
const data = await client.deleteStatus(_request.params.id);
|
||||||
reply.send(data.data);
|
reply.send(data.data);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { ParsedUrlQuery } from 'querystring';
|
import { ParsedUrlQuery } from 'querystring';
|
||||||
import { convertId, IdConvertType as IdType, convertAccount, convertConversation, convertList, MastoConverters } from '../converters.js';
|
import { 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';
|
||||||
|
@ -32,13 +32,6 @@ export function argsToBools(q: ParsedUrlQuery) {
|
||||||
return q;
|
return q;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertTimelinesArgsId(q: ParsedUrlQuery) {
|
|
||||||
if (typeof q.min_id === 'string') q.min_id = convertId(q.min_id, IdType.SharkeyId);
|
|
||||||
if (typeof q.max_id === 'string') q.max_id = convertId(q.max_id, IdType.SharkeyId);
|
|
||||||
if (typeof q.since_id === 'string') q.since_id = convertId(q.since_id, IdType.SharkeyId);
|
|
||||||
return q;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ApiTimelineMastodon {
|
export class ApiTimelineMastodon {
|
||||||
private fastify: FastifyInstance;
|
private fastify: FastifyInstance;
|
||||||
private mastoconverter: MastoConverters;
|
private mastoconverter: MastoConverters;
|
||||||
|
@ -56,8 +49,8 @@ export class ApiTimelineMastodon {
|
||||||
try {
|
try {
|
||||||
const query: any = _request.query;
|
const query: any = _request.query;
|
||||||
const data = query.local === 'true'
|
const data = query.local === 'true'
|
||||||
? await client.getLocalTimeline(convertTimelinesArgsId(argsToBools(limitToInt(query))))
|
? await client.getLocalTimeline(argsToBools(limitToInt(query)))
|
||||||
: await client.getPublicTimeline(convertTimelinesArgsId(argsToBools(limitToInt(query))));
|
: await client.getPublicTimeline(argsToBools(limitToInt(query)));
|
||||||
reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoconverter.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);
|
||||||
|
@ -74,7 +67,7 @@ export class ApiTimelineMastodon {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
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(limitToInt(query));
|
||||||
reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoconverter.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);
|
||||||
|
@ -92,7 +85,7 @@ export class ApiTimelineMastodon {
|
||||||
try {
|
try {
|
||||||
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, limitToInt(query));
|
||||||
reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoconverter.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);
|
||||||
|
@ -110,7 +103,7 @@ export class ApiTimelineMastodon {
|
||||||
try {
|
try {
|
||||||
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(params.id, limitToInt(query));
|
||||||
reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoconverter.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);
|
||||||
|
@ -127,7 +120,7 @@ export class ApiTimelineMastodon {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const query: any = _request.query;
|
const query: any = _request.query;
|
||||||
const data = await client.getConversationTimeline(convertTimelinesArgsId(limitToInt(query)));
|
const data = await client.getConversationTimeline(limitToInt(query));
|
||||||
reply.send(data.data.map((conversation: Entity.Conversation) => convertConversation(conversation)));
|
reply.send(data.data.map((conversation: Entity.Conversation) => convertConversation(conversation)));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -144,7 +137,7 @@ export class ApiTimelineMastodon {
|
||||||
const accessTokens = _request.headers.authorization;
|
const accessTokens = _request.headers.authorization;
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
const params: any = _request.params;
|
const params: any = _request.params;
|
||||||
const data = await client.getList(convertId(params.id, IdType.SharkeyId));
|
const data = await client.getList(params.id);
|
||||||
reply.send(convertList(data.data));
|
reply.send(convertList(data.data));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -178,11 +171,8 @@ export class ApiTimelineMastodon {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
const params: any = _request.params;
|
const params: any = _request.params;
|
||||||
const query: any = _request.query;
|
const query: any = _request.query;
|
||||||
const data = await client.getAccountsInList(
|
const data = await client.getAccountsInList(params.id, query);
|
||||||
convertId(params.id, IdType.SharkeyId),
|
reply.send(data.data.map((account: Entity.Account) => this.mastoconverter.convertAccount(account)));
|
||||||
convertTimelinesArgsId(query),
|
|
||||||
);
|
|
||||||
reply.send(data.data.map((account: Entity.Account) => convertAccount(account)));
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
console.error(e.response.data);
|
console.error(e.response.data);
|
||||||
|
@ -199,10 +189,7 @@ export class ApiTimelineMastodon {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
const params: any = _request.params;
|
const params: any = _request.params;
|
||||||
const query: any = _request.query;
|
const query: any = _request.query;
|
||||||
const data = await client.addAccountsToList(
|
const data = await client.addAccountsToList(params.id, query.accounts_id);
|
||||||
convertId(params.id, IdType.SharkeyId),
|
|
||||||
(query.accounts_id as string[]).map((id) => convertId(id, IdType.SharkeyId)),
|
|
||||||
);
|
|
||||||
reply.send(data.data);
|
reply.send(data.data);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -220,10 +207,7 @@ export class ApiTimelineMastodon {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
const params: any = _request.params;
|
const params: any = _request.params;
|
||||||
const query: any = _request.query;
|
const query: any = _request.query;
|
||||||
const data = await client.deleteAccountsFromList(
|
const data = await client.deleteAccountsFromList(params.id, query.accounts_id);
|
||||||
convertId(params.id, IdType.SharkeyId),
|
|
||||||
(query.accounts_id as string[]).map((id) => convertId(id, IdType.SharkeyId)),
|
|
||||||
);
|
|
||||||
reply.send(data.data);
|
reply.send(data.data);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -258,7 +242,7 @@ export class ApiTimelineMastodon {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
const body: any = _request.body;
|
const body: any = _request.body;
|
||||||
const params: any = _request.params;
|
const params: any = _request.params;
|
||||||
const data = await client.updateList(convertId(params.id, IdType.SharkeyId), body.title);
|
const data = await client.updateList(params.id, body.title);
|
||||||
reply.send(convertList(data.data));
|
reply.send(convertList(data.data));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -275,7 +259,7 @@ export class ApiTimelineMastodon {
|
||||||
const accessTokens = _request.headers.authorization;
|
const accessTokens = _request.headers.authorization;
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
const params: any = _request.params;
|
const params: any = _request.params;
|
||||||
const data = await client.deleteList(convertId(params.id, IdType.SharkeyId));
|
const data = await client.deleteList(params.id);
|
||||||
reply.send(data.data);
|
reply.send(data.data);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
|
@ -64,15 +64,15 @@
|
||||||
"socks-proxy-agent": "^8.0.2",
|
"socks-proxy-agent": "^8.0.2",
|
||||||
"typescript": "5.1.6",
|
"typescript": "5.1.6",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"ws": "8.14.2"
|
"ws": "8.14.2",
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/core-js": "^2.5.6",
|
"@types/core-js": "^2.5.6",
|
||||||
"@types/form-data": "^2.5.0",
|
"@types/form-data": "^2.5.0",
|
||||||
"@types/jest": "^29.5.5",
|
"@types/jest": "^29.5.5",
|
||||||
"@types/object-assign-deep": "^0.4.1",
|
"@types/object-assign-deep": "^0.4.1",
|
||||||
"@types/parse-link-header": "^2.0.1",
|
"@types/parse-link-header": "^2.0.1",
|
||||||
"@types/uuid": "^9.0.4",
|
"@types/uuid": "^9.0.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "^6.7.2",
|
"@typescript-eslint/eslint-plugin": "^6.7.2",
|
||||||
"@typescript-eslint/parser": "^6.7.2",
|
"@typescript-eslint/parser": "^6.7.2",
|
||||||
"eslint": "^8.49.0",
|
"eslint": "^8.49.0",
|
||||||
|
|
|
@ -5,15 +5,16 @@
|
||||||
namespace Entity {
|
namespace Entity {
|
||||||
export type Account = {
|
export type Account = {
|
||||||
id: string
|
id: string
|
||||||
|
fqn?: string
|
||||||
username: string
|
username: string
|
||||||
acct: string
|
acct: string
|
||||||
display_name: string
|
display_name: string
|
||||||
locked: boolean
|
locked: boolean
|
||||||
discoverable?: boolean
|
discoverable?: boolean
|
||||||
group: boolean | null
|
group?: boolean | null
|
||||||
noindex: boolean | null
|
noindex?: boolean | null
|
||||||
suspended: boolean | null
|
suspended?: boolean | null
|
||||||
limited: boolean | null
|
limited?: boolean | null
|
||||||
created_at: string
|
created_at: string
|
||||||
followers_count: number
|
followers_count: number
|
||||||
following_count: number
|
following_count: number
|
||||||
|
|
|
@ -17,7 +17,7 @@ namespace Entity {
|
||||||
in_reply_to_account_id: string | null
|
in_reply_to_account_id: string | null
|
||||||
reblog: Status | null
|
reblog: Status | null
|
||||||
content: string
|
content: string
|
||||||
plain_content: string | null
|
plain_content?: string | null
|
||||||
created_at: string
|
created_at: string
|
||||||
emojis: Emoji[]
|
emojis: Emoji[]
|
||||||
replies_count: number
|
replies_count: number
|
||||||
|
|
|
@ -2017,7 +2017,7 @@ export default class Misskey implements MegalodonInterface {
|
||||||
}
|
}
|
||||||
if (options.exclude_type) {
|
if (options.exclude_type) {
|
||||||
params = Object.assign(params, {
|
params = Object.assign(params, {
|
||||||
excludeType: options.exclude_type.map(e => MisskeyAPI.Converter.encodeNotificationType(e))
|
excludeTypes: options.exclude_type.map(e => MisskeyAPI.Converter.encodeNotificationType(e))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,8 +78,10 @@ namespace MisskeyAPI {
|
||||||
acct = `${u.username}@${u.host}`;
|
acct = `${u.username}@${u.host}`;
|
||||||
acctUrl = `https://${u.host}/@${u.username}`;
|
acctUrl = `https://${u.host}/@${u.username}`;
|
||||||
}
|
}
|
||||||
|
const fqn = `${u.username}@${u.host ?? host}`;
|
||||||
return {
|
return {
|
||||||
id: u.id,
|
id: u.id,
|
||||||
|
fqn: fqn,
|
||||||
username: u.username,
|
username: u.username,
|
||||||
acct: acct,
|
acct: acct,
|
||||||
display_name: u.name ? u.name : '',
|
display_name: u.name ? u.name : '',
|
||||||
|
@ -465,8 +467,8 @@ namespace MisskeyAPI {
|
||||||
|
|
||||||
export const stats = (s: Entity.Stats): MegalodonEntity.Stats => {
|
export const stats = (s: Entity.Stats): MegalodonEntity.Stats => {
|
||||||
return {
|
return {
|
||||||
user_count: s.usersCount,
|
user_count: s.originalUsersCount,
|
||||||
status_count: s.notesCount,
|
status_count: s.originalNotesCount,
|
||||||
domain_count: s.instances
|
domain_count: s.instances
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue