rm: mastodon api
This commit is contained in:
parent
dcce9ba70b
commit
4cfc110e00
|
@ -117,7 +117,6 @@
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"jsonld": "8.3.1",
|
"jsonld": "8.3.1",
|
||||||
"jsrsasign": "10.8.6",
|
"jsrsasign": "10.8.6",
|
||||||
"megalodon": "workspace:*",
|
|
||||||
"meilisearch": "0.35.0",
|
"meilisearch": "0.35.0",
|
||||||
"mfm-js": "0.23.3",
|
"mfm-js": "0.23.3",
|
||||||
"microformats-parser": "1.5.2",
|
"microformats-parser": "1.5.2",
|
||||||
|
|
|
@ -23,7 +23,6 @@ import { SigninService } from './api/SigninService.js';
|
||||||
import { SignupApiService } from './api/SignupApiService.js';
|
import { SignupApiService } from './api/SignupApiService.js';
|
||||||
import { StreamingApiServerService } from './api/StreamingApiServerService.js';
|
import { StreamingApiServerService } from './api/StreamingApiServerService.js';
|
||||||
import { ClientServerService } from './web/ClientServerService.js';
|
import { ClientServerService } from './web/ClientServerService.js';
|
||||||
import { MastoConverters } from './api/mastodon/converters.js';
|
|
||||||
import { FeedService } from './web/FeedService.js';
|
import { FeedService } from './web/FeedService.js';
|
||||||
import { UrlPreviewService } from './web/UrlPreviewService.js';
|
import { UrlPreviewService } from './web/UrlPreviewService.js';
|
||||||
import { MainChannelService } from './api/stream/channels/main.js';
|
import { MainChannelService } from './api/stream/channels/main.js';
|
||||||
|
@ -40,7 +39,6 @@ import { QueueStatsChannelService } from './api/stream/channels/queue-stats.js';
|
||||||
import { ServerStatsChannelService } from './api/stream/channels/server-stats.js';
|
import { ServerStatsChannelService } from './api/stream/channels/server-stats.js';
|
||||||
import { UserListChannelService } from './api/stream/channels/user-list.js';
|
import { UserListChannelService } from './api/stream/channels/user-list.js';
|
||||||
import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
|
import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
|
||||||
import { MastodonApiServerService } from './api/mastodon/MastodonApiServerService.js';
|
|
||||||
import { ClientLoggerService } from './web/ClientLoggerService.js';
|
import { ClientLoggerService } from './web/ClientLoggerService.js';
|
||||||
import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.js';
|
import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.js';
|
||||||
import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
|
import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
|
||||||
|
@ -86,9 +84,7 @@ import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
|
||||||
ServerStatsChannelService,
|
ServerStatsChannelService,
|
||||||
UserListChannelService,
|
UserListChannelService,
|
||||||
OpenApiServerService,
|
OpenApiServerService,
|
||||||
MastodonApiServerService,
|
|
||||||
OAuth2ProviderService,
|
OAuth2ProviderService,
|
||||||
MastoConverters,
|
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
ServerService,
|
ServerService,
|
||||||
|
|
|
@ -31,7 +31,6 @@ import { WellKnownServerService } from './WellKnownServerService.js';
|
||||||
import { FileServerService } from './FileServerService.js';
|
import { FileServerService } from './FileServerService.js';
|
||||||
import { ClientServerService } from './web/ClientServerService.js';
|
import { ClientServerService } from './web/ClientServerService.js';
|
||||||
import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
|
import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
|
||||||
import { MastodonApiServerService } from './api/mastodon/MastodonApiServerService.js';
|
|
||||||
import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
|
import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
|
||||||
|
|
||||||
const _dirname = fileURLToPath(new URL('.', import.meta.url));
|
const _dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||||
|
@ -105,7 +104,6 @@ export class ServerService implements OnApplicationShutdown {
|
||||||
|
|
||||||
fastify.register(this.apiServerService.createServer, { prefix: '/api' });
|
fastify.register(this.apiServerService.createServer, { prefix: '/api' });
|
||||||
fastify.register(this.openApiServerService.createServer);
|
fastify.register(this.openApiServerService.createServer);
|
||||||
fastify.register(this.mastodonApiServerService.createServer, { prefix: '/api' });
|
|
||||||
fastify.register(this.fileServerService.createServer);
|
fastify.register(this.fileServerService.createServer);
|
||||||
fastify.register(this.activityPubServerService.createServer);
|
fastify.register(this.activityPubServerService.createServer);
|
||||||
fastify.register(this.nodeinfoServerService.createServer);
|
fastify.register(this.nodeinfoServerService.createServer);
|
||||||
|
|
|
@ -1,906 +0,0 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import megalodon, { Entity, MegalodonInterface } from 'megalodon';
|
|
||||||
import querystring from 'querystring';
|
|
||||||
import { IsNull } from 'typeorm';
|
|
||||||
import multer from 'fastify-multer';
|
|
||||||
import type { AccessTokensRepository, NoteEditRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
import type { Config } from '@/config.js';
|
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { convertAnnouncement, convertFilter, convertAttachment, convertFeaturedTag, convertList, MastoConverters } from './converters.js';
|
|
||||||
import { getInstance } from './endpoints/meta.js';
|
|
||||||
import { ApiAuthMastodon, ApiAccountMastodon, ApiFilterMastodon, ApiNotifyMastodon, ApiSearchMastodon, ApiTimelineMastodon, ApiStatusMastodon } from './endpoints.js';
|
|
||||||
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
|
||||||
import { DriveService } from '@/core/DriveService.js';
|
|
||||||
|
|
||||||
export function getClient(BASE_URL: string, authorization: string | undefined): MegalodonInterface {
|
|
||||||
const accessTokenArr = authorization?.split(' ') ?? [null];
|
|
||||||
const accessToken = accessTokenArr[accessTokenArr.length - 1];
|
|
||||||
const generator = (megalodon as any).default;
|
|
||||||
const client = generator('misskey', BASE_URL, accessToken) as MegalodonInterface;
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class MastodonApiServerService {
|
|
||||||
constructor(
|
|
||||||
@Inject(DI.usersRepository)
|
|
||||||
private usersRepository: UsersRepository,
|
|
||||||
@Inject(DI.notesRepository)
|
|
||||||
private notesRepository: NotesRepository,
|
|
||||||
@Inject(DI.userProfilesRepository)
|
|
||||||
private userProfilesRepository: UserProfilesRepository,
|
|
||||||
@Inject(DI.noteEditRepository)
|
|
||||||
private noteEditRepository: NoteEditRepository,
|
|
||||||
@Inject(DI.accessTokensRepository)
|
|
||||||
private accessTokensRepository: AccessTokensRepository,
|
|
||||||
@Inject(DI.config)
|
|
||||||
private config: Config,
|
|
||||||
private metaService: MetaService,
|
|
||||||
private userEntityService: UserEntityService,
|
|
||||||
private driveService: DriveService,
|
|
||||||
private mastoConverter: MastoConverters,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public createServer(fastify: FastifyInstance, _options: FastifyPluginOptions, done: (err?: Error) => void) {
|
|
||||||
const upload = multer({
|
|
||||||
storage: multer.diskStorage({}),
|
|
||||||
limits: {
|
|
||||||
fileSize: this.config.maxFileSize || 262144000,
|
|
||||||
files: 1,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.addHook('onRequest', (request, reply, done) => {
|
|
||||||
reply.header('Access-Control-Allow-Origin', '*');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.addContentTypeParser('application/x-www-form-urlencoded', (request, payload, done) => {
|
|
||||||
let body = '';
|
|
||||||
payload.on('data', (data) => {
|
|
||||||
body += data;
|
|
||||||
});
|
|
||||||
payload.on('end', () => {
|
|
||||||
try {
|
|
||||||
const parsed = querystring.parse(body);
|
|
||||||
done(null, parsed);
|
|
||||||
} catch (e: any) {
|
|
||||||
done(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
payload.on('error', done);
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.register(multer.contentParser);
|
|
||||||
|
|
||||||
fastify.get('/v1/custom_emojis', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.getInstanceCustomEmojis();
|
|
||||||
reply.send(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get('/v1/instance', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt
|
|
||||||
// displayed without being logged in
|
|
||||||
try {
|
|
||||||
const data = await client.getInstance();
|
|
||||||
const admin = await this.usersRepository.findOne({
|
|
||||||
where: {
|
|
||||||
host: IsNull(),
|
|
||||||
isRoot: true,
|
|
||||||
isDeleted: false,
|
|
||||||
isSuspended: false,
|
|
||||||
},
|
|
||||||
order: { id: 'ASC' },
|
|
||||||
});
|
|
||||||
const contact = admin == null ? null : await this.mastoConverter.convertAccount((await client.getAccount(admin.id)).data);
|
|
||||||
reply.send(await getInstance(data.data, contact as Entity.Account, this.config, await this.metaService.fetch()));
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get('/v1/announcements', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.getInstanceAnnouncements();
|
|
||||||
reply.send(data.data.map((announcement) => convertAnnouncement(announcement)));
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.post<{ Body: { id: string } }>('/v1/announcements/:id/dismiss', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.dismissInstanceAnnouncement(
|
|
||||||
_request.body['id'],
|
|
||||||
);
|
|
||||||
reply.send(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
fastify.post('/v1/media', { preHandler: upload.single('file') }, async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const multipartData = await _request.file;
|
|
||||||
if (!multipartData) {
|
|
||||||
reply.code(401).send({ error: 'No image' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const data = await client.uploadMedia(multipartData);
|
|
||||||
reply.send(convertAttachment(data.data as Entity.Attachment));
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.post('/v2/media', { preHandler: upload.single('file') }, async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const multipartData = await _request.file;
|
|
||||||
if (!multipartData) {
|
|
||||||
reply.code(401).send({ error: 'No image' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const data = await client.uploadMedia(multipartData, _request.body!);
|
|
||||||
reply.send(convertAttachment(data.data as Entity.Attachment));
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get('/v1/filters', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt
|
|
||||||
// displayed without being logged in
|
|
||||||
try {
|
|
||||||
const data = await client.getFilters();
|
|
||||||
reply.send(data.data.map((filter) => convertFilter(filter)));
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get('/v1/trends', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt
|
|
||||||
// displayed without being logged in
|
|
||||||
try {
|
|
||||||
const data = await client.getInstanceTrends();
|
|
||||||
reply.send(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get('/v1/trends/tags', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt
|
|
||||||
// displayed without being logged in
|
|
||||||
try {
|
|
||||||
const data = await client.getInstanceTrends();
|
|
||||||
reply.send(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get('/v1/trends/links', async (_request, reply) => {
|
|
||||||
// As we do not have any system for news/links this will just return empty
|
|
||||||
reply.send([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.post('/v1/apps', { preHandler: upload.single('none') }, async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const client = getClient(BASE_URL, ''); // we are using this here, because in private mode some info isnt
|
|
||||||
// displayed without being logged in
|
|
||||||
try {
|
|
||||||
const data = await ApiAuthMastodon(_request, client);
|
|
||||||
reply.send(data);
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get('/v1/preferences', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt
|
|
||||||
// displayed without being logged in
|
|
||||||
try {
|
|
||||||
const data = await client.getPreferences();
|
|
||||||
reply.send(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//#region Accounts
|
|
||||||
fastify.get('/v1/accounts/verify_credentials', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt
|
|
||||||
// displayed without being logged in
|
|
||||||
try {
|
|
||||||
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter);
|
|
||||||
reply.send(await account.verifyCredentials());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.patch('/v1/accounts/update_credentials', { preHandler: upload.any() }, async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt
|
|
||||||
// displayed without being logged in
|
|
||||||
try {
|
|
||||||
// Check if there is an Header or Avatar being uploaded, if there is proceed to upload it to the drive of the user and then set it.
|
|
||||||
if (_request.files.length > 0 && accessTokens) {
|
|
||||||
const tokeninfo = await this.accessTokensRepository.findOneBy({ token: accessTokens.replace('Bearer ', '') });
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const avatar = (_request.files as any).find((obj: any) => {
|
|
||||||
return obj.fieldname === 'avatar';
|
|
||||||
});
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const header = (_request.files as any).find((obj: any) => {
|
|
||||||
return obj.fieldname === 'header';
|
|
||||||
});
|
|
||||||
|
|
||||||
if (tokeninfo && avatar) {
|
|
||||||
const upload = await this.driveService.addFile({
|
|
||||||
user: { id: tokeninfo.userId, host: null },
|
|
||||||
path: avatar.path,
|
|
||||||
name: avatar.originalname !== null && avatar.originalname !== 'file' ? avatar.originalname : undefined,
|
|
||||||
sensitive: false,
|
|
||||||
});
|
|
||||||
if (upload.type.startsWith('image/')) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
(_request.body as any).avatar = upload.id;
|
|
||||||
}
|
|
||||||
} else if (tokeninfo && header) {
|
|
||||||
const upload = await this.driveService.addFile({
|
|
||||||
user: { id: tokeninfo.userId, host: null },
|
|
||||||
path: header.path,
|
|
||||||
name: header.originalname !== null && header.originalname !== 'file' ? header.originalname : undefined,
|
|
||||||
sensitive: false,
|
|
||||||
});
|
|
||||||
if (upload.type.startsWith('image/')) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
(_request.body as any).header = upload.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
if ((_request.body as any).fields_attributes) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const fields = (_request.body as any).fields_attributes.map((field: any) => {
|
|
||||||
if (!(field.name.trim() === '' && field.value.trim() === '')) {
|
|
||||||
if (field.name.trim() === '') return reply.code(400).send('Field name can not be empty');
|
|
||||||
if (field.value.trim() === '') return reply.code(400).send('Field value can not be empty');
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...field,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
(_request.body as any).fields_attributes = fields.filter((field: any) => field.name.trim().length > 0 && field.value.length > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await client.updateCredentials(_request.body!);
|
|
||||||
reply.send(await this.mastoConverter.convertAccount(data.data));
|
|
||||||
} catch (e: any) {
|
|
||||||
//console.error(e);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get('/v1/accounts/lookup', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt
|
|
||||||
// displayed without being logged in
|
|
||||||
try {
|
|
||||||
const data = await client.search((_request.query as any).acct, { type: 'accounts' });
|
|
||||||
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(await this.mastoConverter.convertAccount(data.data.accounts[0]));
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get('/v1/accounts/relationships', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt
|
|
||||||
// displayed without being logged in
|
|
||||||
let users;
|
|
||||||
try {
|
|
||||||
let ids = _request.query ? (_request.query as any)['id[]'] ?? (_request.query as any)['id'] : null;
|
|
||||||
if (typeof ids === 'string') {
|
|
||||||
ids = [ids];
|
|
||||||
}
|
|
||||||
users = ids;
|
|
||||||
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter);
|
|
||||||
reply.send(await account.getRelationships(users));
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e); */
|
|
||||||
const data = e.response.data;
|
|
||||||
data.users = users;
|
|
||||||
console.error(data);
|
|
||||||
reply.code(401).send(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get<{ Params: { id: string } }>('/v1/accounts/:id', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const sharkId = _request.params.id;
|
|
||||||
const data = await client.getAccount(sharkId);
|
|
||||||
const account = await this.mastoConverter.convertAccount(data.data);
|
|
||||||
reply.send(account);
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get<{ Params: { id: string } }>('/v1/accounts/:id/statuses', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter);
|
|
||||||
reply.send(await account.getStatuses());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get<{ Params: { id: string } }>('/v1/accounts/:id/featured_tags', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.getFeaturedTags();
|
|
||||||
reply.send(data.data.map((tag) => convertFeaturedTag(tag)));
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get<{ Params: { id: string } }>('/v1/accounts/:id/followers', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter);
|
|
||||||
reply.send(await account.getFollowers());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get<{ Params: { id: string } }>('/v1/accounts/:id/following', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter);
|
|
||||||
reply.send(await account.getFollowing());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get<{ Params: { id: string } }>('/v1/accounts/:id/lists', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.getAccountLists(_request.params.id);
|
|
||||||
reply.send(data.data.map((list) => convertList(list)));
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.post<{ Params: { id: string } }>('/v1/accounts/:id/follow', { preHandler: upload.single('none') }, async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter);
|
|
||||||
reply.send(await account.addFollow());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.post<{ Params: { id: string } }>('/v1/accounts/:id/unfollow', { preHandler: upload.single('none') }, async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter);
|
|
||||||
reply.send(await account.rmFollow());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.post<{ Params: { id: string } }>('/v1/accounts/:id/block', { preHandler: upload.single('none') }, async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter);
|
|
||||||
reply.send(await account.addBlock());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.post<{ Params: { id: string } }>('/v1/accounts/:id/unblock', { preHandler: upload.single('none') }, async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter);
|
|
||||||
reply.send(await account.rmBlock());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.post<{ Params: { id: string } }>('/v1/accounts/:id/mute', { preHandler: upload.single('none') }, async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter);
|
|
||||||
reply.send(await account.addMute());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.post<{ Params: { id: string } }>('/v1/accounts/:id/unmute', { preHandler: upload.single('none') }, async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter);
|
|
||||||
reply.send(await account.rmMute());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get('/v1/followed_tags', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.getFollowedTags();
|
|
||||||
reply.send(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get('/v1/bookmarks', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter);
|
|
||||||
reply.send(await account.getBookmarks());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get('/v1/favourites', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter);
|
|
||||||
reply.send(await account.getFavourites());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get('/v1/mutes', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter);
|
|
||||||
reply.send(await account.getMutes());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get('/v1/blocks', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter);
|
|
||||||
reply.send(await account.getBlocks());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get('/v1/follow_requests', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.getFollowRequests( ((_request.query as any) || { limit: 20 }).limit );
|
|
||||||
reply.send(await Promise.all(data.data.map(async (account) => await this.mastoConverter.convertAccount(account as Entity.Account))));
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.post<{ Params: { id: string } }>('/v1/follow_requests/:id/authorize', { preHandler: upload.single('none') }, async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter);
|
|
||||||
reply.send(await account.acceptFollow());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.post<{ Params: { id: string } }>('/v1/follow_requests/:id/reject', { preHandler: upload.single('none') }, async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter);
|
|
||||||
reply.send(await account.rejectFollow());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region Search
|
|
||||||
fastify.get('/v1/search', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const search = new ApiSearchMastodon(_request, client, BASE_URL, this.mastoConverter);
|
|
||||||
reply.send(await search.SearchV1());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get('/v2/search', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const search = new ApiSearchMastodon(_request, client, BASE_URL, this.mastoConverter);
|
|
||||||
reply.send(await search.SearchV2());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get('/v1/trends/statuses', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const search = new ApiSearchMastodon(_request, client, BASE_URL, this.mastoConverter);
|
|
||||||
reply.send(await search.getStatusTrends());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get('/v2/suggestions', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const search = new ApiSearchMastodon(_request, client, BASE_URL, this.mastoConverter);
|
|
||||||
reply.send(await search.getSuggestions());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region Notifications
|
|
||||||
fastify.get('/v1/notifications', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const notify = new ApiNotifyMastodon(_request, client);
|
|
||||||
reply.send(await notify.getNotifications());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.get<{ Params: { id: string } }>('/v1/notification/:id', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const notify = new ApiNotifyMastodon(_request, client);
|
|
||||||
reply.send(await notify.getNotification());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.post<{ Params: { id: string } }>('/v1/notification/:id/dismiss', { preHandler: upload.single('none') }, async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const notify = new ApiNotifyMastodon(_request, client);
|
|
||||||
reply.send(await notify.rmNotification());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.post('/v1/notifications/clear', { preHandler: upload.single('none') }, async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const notify = new ApiNotifyMastodon(_request, client);
|
|
||||||
reply.send(await notify.rmNotifications());
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region Filters
|
|
||||||
fastify.get<{ Params: { id: string } }>('/v1/filters/:id', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const filter = new ApiFilterMastodon(_request, client);
|
|
||||||
!_request.params.id ? reply.send(await filter.getFilters()) : reply.send(await filter.getFilter());
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.post('/v1/filters', { preHandler: upload.single('none') }, async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const filter = new ApiFilterMastodon(_request, client);
|
|
||||||
reply.send(await filter.createFilter());
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.post<{ Params: { id: string } }>('/v1/filters/:id', { preHandler: upload.single('none') }, async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const filter = new ApiFilterMastodon(_request, client);
|
|
||||||
reply.send(await filter.updateFilter());
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.delete<{ Params: { id: string } }>('/v1/filters/:id', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const filter = new ApiFilterMastodon(_request, client);
|
|
||||||
reply.send(await filter.rmFilter());
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region Timelines
|
|
||||||
const TLEndpoint = new ApiTimelineMastodon(fastify, this.config, this.mastoConverter);
|
|
||||||
|
|
||||||
// GET Endpoints
|
|
||||||
TLEndpoint.getTL();
|
|
||||||
TLEndpoint.getHomeTl();
|
|
||||||
TLEndpoint.getListTL();
|
|
||||||
TLEndpoint.getTagTl();
|
|
||||||
TLEndpoint.getConversations();
|
|
||||||
TLEndpoint.getList();
|
|
||||||
TLEndpoint.getLists();
|
|
||||||
TLEndpoint.getListAccounts();
|
|
||||||
|
|
||||||
// POST Endpoints
|
|
||||||
TLEndpoint.createList();
|
|
||||||
TLEndpoint.addListAccount();
|
|
||||||
|
|
||||||
// PUT Endpoint
|
|
||||||
TLEndpoint.updateList();
|
|
||||||
|
|
||||||
// DELETE Endpoints
|
|
||||||
TLEndpoint.deleteList();
|
|
||||||
TLEndpoint.rmListAccount();
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region Status
|
|
||||||
const NoteEndpoint = new ApiStatusMastodon(fastify, this.mastoConverter);
|
|
||||||
|
|
||||||
// GET Endpoints
|
|
||||||
NoteEndpoint.getStatus();
|
|
||||||
NoteEndpoint.getStatusSource();
|
|
||||||
NoteEndpoint.getContext();
|
|
||||||
NoteEndpoint.getHistory();
|
|
||||||
NoteEndpoint.getReblogged();
|
|
||||||
NoteEndpoint.getFavourites();
|
|
||||||
NoteEndpoint.getMedia();
|
|
||||||
NoteEndpoint.getPoll();
|
|
||||||
|
|
||||||
//POST Endpoints
|
|
||||||
NoteEndpoint.postStatus();
|
|
||||||
NoteEndpoint.addFavourite();
|
|
||||||
NoteEndpoint.rmFavourite();
|
|
||||||
NoteEndpoint.reblogStatus();
|
|
||||||
NoteEndpoint.unreblogStatus();
|
|
||||||
NoteEndpoint.bookmarkStatus();
|
|
||||||
NoteEndpoint.unbookmarkStatus();
|
|
||||||
NoteEndpoint.pinStatus();
|
|
||||||
NoteEndpoint.unpinStatus();
|
|
||||||
NoteEndpoint.reactStatus();
|
|
||||||
NoteEndpoint.unreactStatus();
|
|
||||||
NoteEndpoint.votePoll();
|
|
||||||
|
|
||||||
// PUT Endpoint
|
|
||||||
fastify.put<{ Params: { id: string } }>('/v1/media/:id', { preHandler: upload.none() }, async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.updateMedia(_request.params.id, _request.body!);
|
|
||||||
reply.send(convertAttachment(data.data));
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e); */
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
NoteEndpoint.updateStatus();
|
|
||||||
|
|
||||||
// DELETE Endpoint
|
|
||||||
NoteEndpoint.deleteStatus();
|
|
||||||
//#endregion
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,353 +0,0 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { Entity } from 'megalodon';
|
|
||||||
import mfm from 'mfm-js';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import { MfmService } from '@/core/MfmService.js';
|
|
||||||
import type { Config } from '@/config.js';
|
|
||||||
import type { IMentionedRemoteUsers } from '@/models/Note.js';
|
|
||||||
import type { MiUser } from '@/models/User.js';
|
|
||||||
import type { NoteEditRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
|
||||||
import { awaitAll } from '@/misc/prelude/await-all.js';
|
|
||||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
|
||||||
import { GetterService } from '../GetterService.js';
|
|
||||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
|
||||||
import { IdService } from '@/core/IdService.js';
|
|
||||||
|
|
||||||
export enum IdConvertType {
|
|
||||||
MastodonId,
|
|
||||||
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>');
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class MastoConverters {
|
|
||||||
constructor(
|
|
||||||
@Inject(DI.config)
|
|
||||||
private config: Config,
|
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
|
||||||
private usersRepository: UsersRepository,
|
|
||||||
|
|
||||||
@Inject(DI.userProfilesRepository)
|
|
||||||
private userProfilesRepository: UserProfilesRepository,
|
|
||||||
|
|
||||||
@Inject(DI.noteEditRepository)
|
|
||||||
private noteEditRepository: NoteEditRepository,
|
|
||||||
|
|
||||||
private mfmService: MfmService,
|
|
||||||
private getterService: GetterService,
|
|
||||||
private customEmojiService: CustomEmojiService,
|
|
||||||
private idService: IdService,
|
|
||||||
private driveFileEntityService: DriveFileEntityService,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
private encode(u: MiUser, m: IMentionedRemoteUsers): Entity.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 fileType(s: string): 'unknown' | 'image' | 'gifv' | 'video' | 'audio' {
|
|
||||||
if (s === 'image/gif') {
|
|
||||||
return 'gifv';
|
|
||||||
}
|
|
||||||
if (s.includes('image')) {
|
|
||||||
return 'image';
|
|
||||||
}
|
|
||||||
if (s.includes('video')) {
|
|
||||||
return 'video';
|
|
||||||
}
|
|
||||||
if (s.includes('audio')) {
|
|
||||||
return 'audio';
|
|
||||||
}
|
|
||||||
return 'unknown';
|
|
||||||
}
|
|
||||||
|
|
||||||
public encodeFile(f: any): Entity.Attachment {
|
|
||||||
return {
|
|
||||||
id: f.id,
|
|
||||||
type: this.fileType(f.type),
|
|
||||||
url: f.url,
|
|
||||||
remote_url: f.url,
|
|
||||||
preview_url: f.thumbnailUrl,
|
|
||||||
text_url: f.url,
|
|
||||||
meta: {
|
|
||||||
width: f.properties.width,
|
|
||||||
height: f.properties.height
|
|
||||||
},
|
|
||||||
description: f.comment ? f.comment : null,
|
|
||||||
blurhash: f.blurhash ? f.blurhash : null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getUser(id: string): Promise<MiUser> {
|
|
||||||
return this.getterService.getUser(id).then(p => {
|
|
||||||
return p;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async encodeField(f: Entity.Field): Promise<Entity.Field> {
|
|
||||||
return {
|
|
||||||
name: f.name,
|
|
||||||
value: await this.mfmService.toMastoHtml(mfm.parse(f.value), [], true) ?? escapeMFM(f.value),
|
|
||||||
verified_at: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async convertAccount(account: Entity.Account | MiUser) {
|
|
||||||
const user = await this.getUser(account.id);
|
|
||||||
const profile = await this.userProfilesRepository.findOneBy({ userId: user.id });
|
|
||||||
const emojis = await this.customEmojiService.populateEmojis(user.emojis, user.host ? user.host : this.config.host);
|
|
||||||
const emoji: Entity.Emoji[] = [];
|
|
||||||
Object.entries(emojis).forEach(entry => {
|
|
||||||
const [key, value] = entry;
|
|
||||||
emoji.push({
|
|
||||||
shortcode: key,
|
|
||||||
static_url: value,
|
|
||||||
url: value,
|
|
||||||
visible_in_picker: true,
|
|
||||||
category: undefined,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const fqn = `${user.username}@${user.host ?? this.config.hostname}`;
|
|
||||||
let acct = user.username;
|
|
||||||
let acctUrl = `https://${user.host || this.config.host}/@${user.username}`;
|
|
||||||
const acctUri = `https://${this.config.host}/users/${user.id}`;
|
|
||||||
if (user.host) {
|
|
||||||
acct = `${user.username}@${user.host}`;
|
|
||||||
acctUrl = `https://${user.host}/@${user.username}`;
|
|
||||||
}
|
|
||||||
return awaitAll({
|
|
||||||
id: account.id,
|
|
||||||
username: user.username,
|
|
||||||
acct: acct,
|
|
||||||
fqn: fqn,
|
|
||||||
display_name: user.name ?? user.username,
|
|
||||||
locked: user.isLocked,
|
|
||||||
created_at: this.idService.parse(user.id).date.toISOString(),
|
|
||||||
followers_count: user.followersCount,
|
|
||||||
following_count: user.followingCount,
|
|
||||||
statuses_count: user.notesCount,
|
|
||||||
note: profile?.description ?? '',
|
|
||||||
url: user.uri ?? acctUrl,
|
|
||||||
uri: user.uri ?? acctUri,
|
|
||||||
avatar: user.avatarUrl ? user.avatarUrl : 'https://dev.joinsharkey.org/static-assets/avatar.png',
|
|
||||||
avatar_static: user.avatarUrl ? user.avatarUrl : 'https://dev.joinsharkey.org/static-assets/avatar.png',
|
|
||||||
header: user.bannerUrl ? user.bannerUrl : 'https://dev.joinsharkey.org/static-assets/transparent.png',
|
|
||||||
header_static: user.bannerUrl ? user.bannerUrl : 'https://dev.joinsharkey.org/static-assets/transparent.png',
|
|
||||||
emojis: emoji,
|
|
||||||
moved: null, //FIXME
|
|
||||||
fields: Promise.all(profile?.fields.map(async p => this.encodeField(p)) ?? []),
|
|
||||||
bot: user.isBot,
|
|
||||||
discoverable: user.isExplorable,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getEdits(id: string) {
|
|
||||||
const note = await this.getterService.getNote(id);
|
|
||||||
if (!note) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const noteUser = await this.getUser(note.userId).then(async (p) => await this.convertAccount(p));
|
|
||||||
const edits = await this.noteEditRepository.find({ where: { noteId: note.id }, order: { id: 'ASC' } });
|
|
||||||
const history: Promise<any>[] = [];
|
|
||||||
|
|
||||||
let lastDate = this.idService.parse(note.id).date;
|
|
||||||
for (const edit of edits) {
|
|
||||||
const files = this.driveFileEntityService.packManyByIds(edit.fileIds);
|
|
||||||
const item = {
|
|
||||||
account: noteUser,
|
|
||||||
content: this.mfmService.toMastoHtml(mfm.parse(edit.newText ?? ''), JSON.parse(note.mentionedRemoteUsers)).then(p => p ?? ''),
|
|
||||||
created_at: lastDate.toISOString(),
|
|
||||||
emojis: [],
|
|
||||||
sensitive: files.then(files => files.length > 0 ? files.some((f) => f.isSensitive) : false),
|
|
||||||
spoiler_text: edit.cw ?? '',
|
|
||||||
poll: null,
|
|
||||||
media_attachments: files.then(files => files.length > 0 ? files.map((f) => this.encodeFile(f)) : [])
|
|
||||||
};
|
|
||||||
lastDate = edit.updatedAt;
|
|
||||||
history.push(awaitAll(item));
|
|
||||||
}
|
|
||||||
|
|
||||||
return await Promise.all(history);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async convertReblog(status: Entity.Status | null): Promise<any> {
|
|
||||||
if (!status) return null;
|
|
||||||
return await this.convertStatus(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async convertStatus(status: Entity.Status) {
|
|
||||||
const convertedAccount = this.convertAccount(status.account);
|
|
||||||
const note = await this.getterService.getNote(status.id);
|
|
||||||
const noteUser = await this.getUser(status.account.id);
|
|
||||||
|
|
||||||
const emojis = await this.customEmojiService.populateEmojis(note.emojis, noteUser.host ? noteUser.host : this.config.host);
|
|
||||||
const emoji: Entity.Emoji[] = [];
|
|
||||||
Object.entries(emojis).forEach(entry => {
|
|
||||||
const [key, value] = entry;
|
|
||||||
emoji.push({
|
|
||||||
shortcode: key,
|
|
||||||
static_url: value,
|
|
||||||
url: value,
|
|
||||||
visible_in_picker: true,
|
|
||||||
category: undefined,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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<Entity.Mention[]>;
|
|
||||||
|
|
||||||
const tags = note.tags.map(tag => {
|
|
||||||
return {
|
|
||||||
name: tag,
|
|
||||||
url: `${this.config.url}/tags/${tag}`,
|
|
||||||
} as Entity.Tag;
|
|
||||||
});
|
|
||||||
|
|
||||||
const isQuote = note.renoteId && note.text ? true : false;
|
|
||||||
|
|
||||||
const renote = note.renoteId ? this.getterService.getNote(note.renoteId) : null;
|
|
||||||
|
|
||||||
const quoteUri = Promise.resolve(renote).then(renote => {
|
|
||||||
if (!renote || !isQuote) return null;
|
|
||||||
return renote.url ?? renote.uri ?? `${this.config.url}/notes/${renote.id}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
const content = note.text !== null
|
|
||||||
? quoteUri.then(quoteUri => this.mfmService.toMastoHtml(mfm.parse(note.text!), JSON.parse(note.mentionedRemoteUsers), false, quoteUri))
|
|
||||||
.then(p => p ?? escapeMFM(note.text!))
|
|
||||||
: '';
|
|
||||||
|
|
||||||
// noinspection ES6MissingAwait
|
|
||||||
return await awaitAll({
|
|
||||||
id: note.id,
|
|
||||||
uri: note.uri ?? `https://${this.config.host}/notes/${note.id}`,
|
|
||||||
url: note.url ?? note.uri ?? `https://${this.config.host}/notes/${note.id}`,
|
|
||||||
account: convertedAccount,
|
|
||||||
in_reply_to_id: note.replyId,
|
|
||||||
in_reply_to_account_id: note.replyUserId,
|
|
||||||
reblog: !isQuote ? await this.convertReblog(status.reblog) : null,
|
|
||||||
content: content,
|
|
||||||
content_type: 'text/x.misskeymarkdown',
|
|
||||||
text: note.text,
|
|
||||||
created_at: status.created_at,
|
|
||||||
emojis: emoji,
|
|
||||||
replies_count: note.repliesCount,
|
|
||||||
reblogs_count: note.renoteCount,
|
|
||||||
favourites_count: status.favourites_count,
|
|
||||||
reblogged: false,
|
|
||||||
favourited: status.favourited,
|
|
||||||
muted: status.muted,
|
|
||||||
sensitive: status.sensitive,
|
|
||||||
spoiler_text: note.cw ? note.cw : '',
|
|
||||||
visibility: status.visibility,
|
|
||||||
media_attachments: status.media_attachments,
|
|
||||||
mentions: mentions,
|
|
||||||
tags: tags,
|
|
||||||
card: null, //FIXME
|
|
||||||
poll: status.poll ?? null,
|
|
||||||
application: null, //FIXME
|
|
||||||
language: null, //FIXME
|
|
||||||
pinned: false,
|
|
||||||
reactions: status.emoji_reactions,
|
|
||||||
emoji_reactions: status.emoji_reactions,
|
|
||||||
bookmarked: false,
|
|
||||||
quote: isQuote ? await this.convertReblog(status.reblog) : null,
|
|
||||||
edited_at: note.updatedAt?.toISOString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function simpleConvert(data: any) {
|
|
||||||
// copy the object to bypass weird pass by reference bugs
|
|
||||||
const result = Object.assign({}, data);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function convertAccount(account: Entity.Account) {
|
|
||||||
return simpleConvert(account);
|
|
||||||
}
|
|
||||||
export function convertAnnouncement(announcement: Entity.Announcement) {
|
|
||||||
return simpleConvert(announcement);
|
|
||||||
}
|
|
||||||
export function convertAttachment(attachment: Entity.Attachment) {
|
|
||||||
return simpleConvert(attachment);
|
|
||||||
}
|
|
||||||
export function convertFilter(filter: Entity.Filter) {
|
|
||||||
return simpleConvert(filter);
|
|
||||||
}
|
|
||||||
export function convertList(list: Entity.List) {
|
|
||||||
return simpleConvert(list);
|
|
||||||
}
|
|
||||||
export function convertFeaturedTag(tag: Entity.FeaturedTag) {
|
|
||||||
return simpleConvert(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function convertNotification(notification: Entity.Notification) {
|
|
||||||
notification.account = convertAccount(notification.account);
|
|
||||||
if (notification.status) notification.status = convertStatus(notification.status);
|
|
||||||
return notification;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function convertPoll(poll: Entity.Poll) {
|
|
||||||
return simpleConvert(poll);
|
|
||||||
}
|
|
||||||
export function convertReaction(reaction: Entity.Reaction) {
|
|
||||||
if (reaction.accounts) {
|
|
||||||
reaction.accounts = reaction.accounts.map(convertAccount);
|
|
||||||
}
|
|
||||||
return reaction;
|
|
||||||
}
|
|
||||||
export function convertRelationship(relationship: Entity.Relationship) {
|
|
||||||
return simpleConvert(relationship);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function convertStatus(status: Entity.Status) {
|
|
||||||
status.account = convertAccount(status.account);
|
|
||||||
status.media_attachments = status.media_attachments.map((attachment) =>
|
|
||||||
convertAttachment(attachment),
|
|
||||||
);
|
|
||||||
if (status.poll) status.poll = convertPoll(status.poll);
|
|
||||||
if (status.reblog) status.reblog = convertStatus(status.reblog);
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function convertStatusSource(status: Entity.StatusSource) {
|
|
||||||
return simpleConvert(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function convertConversation(conversation: Entity.Conversation) {
|
|
||||||
conversation.accounts = conversation.accounts.map(convertAccount);
|
|
||||||
if (conversation.last_status) {
|
|
||||||
conversation.last_status = convertStatus(conversation.last_status);
|
|
||||||
}
|
|
||||||
|
|
||||||
return conversation;
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
import { ApiAuthMastodon } from './endpoints/auth.js';
|
|
||||||
import { ApiAccountMastodon } from './endpoints/account.js';
|
|
||||||
import { ApiSearchMastodon } from './endpoints/search.js';
|
|
||||||
import { ApiNotifyMastodon } from './endpoints/notifications.js';
|
|
||||||
import { ApiFilterMastodon } from './endpoints/filter.js';
|
|
||||||
import { ApiTimelineMastodon } from './endpoints/timeline.js';
|
|
||||||
import { ApiStatusMastodon } from './endpoints/status.js';
|
|
||||||
|
|
||||||
export {
|
|
||||||
ApiAccountMastodon,
|
|
||||||
ApiAuthMastodon,
|
|
||||||
ApiSearchMastodon,
|
|
||||||
ApiNotifyMastodon,
|
|
||||||
ApiFilterMastodon,
|
|
||||||
ApiTimelineMastodon,
|
|
||||||
ApiStatusMastodon,
|
|
||||||
};
|
|
|
@ -1,270 +0,0 @@
|
||||||
import { MastoConverters, convertRelationship } from '../converters.js';
|
|
||||||
import { argsToBools, limitToInt } from './timeline.js';
|
|
||||||
import type { MegalodonInterface } from 'megalodon';
|
|
||||||
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';
|
|
||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
|
|
||||||
const relationshipModel = {
|
|
||||||
id: '',
|
|
||||||
following: false,
|
|
||||||
followed_by: false,
|
|
||||||
delivery_following: false,
|
|
||||||
blocking: false,
|
|
||||||
blocked_by: false,
|
|
||||||
muting: false,
|
|
||||||
muting_notifications: false,
|
|
||||||
requested: false,
|
|
||||||
domain_blocking: false,
|
|
||||||
showing_reblogs: false,
|
|
||||||
endorsed: false,
|
|
||||||
notifying: false,
|
|
||||||
note: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ApiAccountMastodon {
|
|
||||||
private request: FastifyRequest;
|
|
||||||
private client: MegalodonInterface;
|
|
||||||
private BASE_URL: string;
|
|
||||||
|
|
||||||
constructor(request: FastifyRequest, client: MegalodonInterface, BASE_URL: string, private mastoconverter: MastoConverters) {
|
|
||||||
this.request = request;
|
|
||||||
this.client = client;
|
|
||||||
this.BASE_URL = BASE_URL;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async verifyCredentials() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.verifyAccountCredentials();
|
|
||||||
const acct = await this.mastoconverter.convertAccount(data.data);
|
|
||||||
const newAcct = Object.assign({}, acct, {
|
|
||||||
source: {
|
|
||||||
note: acct.note,
|
|
||||||
fields: acct.fields,
|
|
||||||
privacy: '',
|
|
||||||
sensitive: false,
|
|
||||||
language: '',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return newAcct;
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e);
|
|
||||||
console.error(e.response.data); */
|
|
||||||
return e.response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async lookup() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.search((this.request.query as any).acct, { type: 'accounts' });
|
|
||||||
return this.mastoconverter.convertAccount(data.data.accounts[0]);
|
|
||||||
} catch (e: any) {
|
|
||||||
/* console.error(e)
|
|
||||||
console.error(e.response.data); */
|
|
||||||
return e.response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getRelationships(users: [string]) {
|
|
||||||
try {
|
|
||||||
relationshipModel.id = users.toString() || '1';
|
|
||||||
|
|
||||||
if (!(users.length > 0)) {
|
|
||||||
return [relationshipModel];
|
|
||||||
}
|
|
||||||
|
|
||||||
const reqIds = [];
|
|
||||||
for (let i = 0; i < users.length; i++) {
|
|
||||||
reqIds.push(users[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await this.client.getRelationships(reqIds);
|
|
||||||
return data.data.map((relationship) => convertRelationship(relationship));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getStatuses() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.getAccountStatuses((this.request.params as any).id, argsToBools(limitToInt(this.request.query as any)));
|
|
||||||
return await Promise.all(data.data.map(async (status) => await this.mastoconverter.convertStatus(status)));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getFollowers() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.getAccountFollowers(
|
|
||||||
(this.request.params as any).id,
|
|
||||||
limitToInt(this.request.query as any),
|
|
||||||
);
|
|
||||||
return await Promise.all(data.data.map(async (account) => await this.mastoconverter.convertAccount(account)));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getFollowing() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.getAccountFollowing(
|
|
||||||
(this.request.params as any).id,
|
|
||||||
limitToInt(this.request.query as any),
|
|
||||||
);
|
|
||||||
return await Promise.all(data.data.map(async (account) => await this.mastoconverter.convertAccount(account)));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async addFollow() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.followAccount( (this.request.params as any).id );
|
|
||||||
const acct = convertRelationship(data.data);
|
|
||||||
acct.following = true;
|
|
||||||
return acct;
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async rmFollow() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.unfollowAccount( (this.request.params as any).id );
|
|
||||||
const acct = convertRelationship(data.data);
|
|
||||||
acct.following = false;
|
|
||||||
return acct;
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async addBlock() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.blockAccount( (this.request.params as any).id );
|
|
||||||
return convertRelationship(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async rmBlock() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.unblockAccount( (this.request.params as any).id );
|
|
||||||
return convertRelationship(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async addMute() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.muteAccount(
|
|
||||||
(this.request.params as any).id,
|
|
||||||
this.request.body as any,
|
|
||||||
);
|
|
||||||
return convertRelationship(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async rmMute() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.unmuteAccount( (this.request.params as any).id );
|
|
||||||
return convertRelationship(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getBookmarks() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.getBookmarks( limitToInt(this.request.query as any) );
|
|
||||||
return data.data.map((status) => this.mastoconverter.convertStatus(status));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getFavourites() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.getFavourites( limitToInt(this.request.query as any) );
|
|
||||||
return data.data.map((status) => this.mastoconverter.convertStatus(status));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getMutes() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.getMutes( limitToInt(this.request.query as any) );
|
|
||||||
return data.data.map((account) => this.mastoconverter.convertAccount(account));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getBlocks() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.getBlocks( limitToInt(this.request.query as any) );
|
|
||||||
return data.data.map((account) => this.mastoconverter.convertAccount(account));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async acceptFollow() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.acceptFollowRequest( (this.request.params as any).id );
|
|
||||||
return convertRelationship(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async rejectFollow() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.rejectFollowRequest( (this.request.params as any).id );
|
|
||||||
return convertRelationship(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
import type { MegalodonInterface } from 'megalodon';
|
|
||||||
import type { FastifyRequest } from 'fastify';
|
|
||||||
|
|
||||||
const readScope = [
|
|
||||||
'read:account',
|
|
||||||
'read:drive',
|
|
||||||
'read:blocks',
|
|
||||||
'read:favorites',
|
|
||||||
'read:following',
|
|
||||||
'read:messaging',
|
|
||||||
'read:mutes',
|
|
||||||
'read:notifications',
|
|
||||||
'read:reactions',
|
|
||||||
'read:pages',
|
|
||||||
'read:page-likes',
|
|
||||||
'read:user-groups',
|
|
||||||
'read:channels',
|
|
||||||
'read:gallery',
|
|
||||||
'read:gallery-likes',
|
|
||||||
];
|
|
||||||
|
|
||||||
const writeScope = [
|
|
||||||
'write:account',
|
|
||||||
'write:drive',
|
|
||||||
'write:blocks',
|
|
||||||
'write:favorites',
|
|
||||||
'write:following',
|
|
||||||
'write:messaging',
|
|
||||||
'write:mutes',
|
|
||||||
'write:notes',
|
|
||||||
'write:notifications',
|
|
||||||
'write:reactions',
|
|
||||||
'write:votes',
|
|
||||||
'write:pages',
|
|
||||||
'write:page-likes',
|
|
||||||
'write:user-groups',
|
|
||||||
'write:channels',
|
|
||||||
'write:gallery',
|
|
||||||
'write:gallery-likes',
|
|
||||||
];
|
|
||||||
|
|
||||||
export async function ApiAuthMastodon(request: FastifyRequest, client: MegalodonInterface) {
|
|
||||||
const body: any = request.body || request.query;
|
|
||||||
try {
|
|
||||||
let scope = body.scopes;
|
|
||||||
if (typeof scope === 'string') scope = scope.split(' ') || scope.split('+');
|
|
||||||
const pushScope = new Set<string>();
|
|
||||||
for (const s of scope) {
|
|
||||||
if (s.match(/^read/)) for (const r of readScope) pushScope.add(r);
|
|
||||||
if (s.match(/^write/)) for (const r of writeScope) pushScope.add(r);
|
|
||||||
}
|
|
||||||
const scopeArr = Array.from(pushScope);
|
|
||||||
|
|
||||||
const red = body.redirect_uris;
|
|
||||||
const appData = await client.registerApp(body.client_name, {
|
|
||||||
scopes: scopeArr,
|
|
||||||
redirect_uris: red,
|
|
||||||
website: body.website,
|
|
||||||
});
|
|
||||||
const returns = {
|
|
||||||
id: Math.floor(Math.random() * 100).toString(),
|
|
||||||
name: appData.name,
|
|
||||||
website: body.website,
|
|
||||||
redirect_uri: red,
|
|
||||||
client_id: Buffer.from(appData.url || '').toString('base64'),
|
|
||||||
client_secret: appData.clientSecret,
|
|
||||||
};
|
|
||||||
|
|
||||||
return returns;
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
import { convertFilter } from '../converters.js';
|
|
||||||
import type { MegalodonInterface } from 'megalodon';
|
|
||||||
import type { FastifyRequest } from 'fastify';
|
|
||||||
|
|
||||||
export class ApiFilterMastodon {
|
|
||||||
private request: FastifyRequest;
|
|
||||||
private client: MegalodonInterface;
|
|
||||||
|
|
||||||
constructor(request: FastifyRequest, client: MegalodonInterface) {
|
|
||||||
this.request = request;
|
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getFilters() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.getFilters();
|
|
||||||
return data.data.map((filter) => convertFilter(filter));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getFilter() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.getFilter( (this.request.params as any).id );
|
|
||||||
return convertFilter(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async createFilter() {
|
|
||||||
try {
|
|
||||||
const body: any = this.request.body;
|
|
||||||
const data = await this.client.createFilter(body.pharse, body.context, body);
|
|
||||||
return convertFilter(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async updateFilter() {
|
|
||||||
try {
|
|
||||||
const body: any = this.request.body;
|
|
||||||
const data = await this.client.updateFilter((this.request.params as any).id, body.pharse, body.context);
|
|
||||||
return convertFilter(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async rmFilter() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.deleteFilter( (this.request.params as any).id );
|
|
||||||
return data.data;
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
import { Entity } from 'megalodon';
|
|
||||||
import { MAX_NOTE_TEXT_LENGTH, FILE_TYPE_BROWSERSAFE } from '@/const.js';
|
|
||||||
import type { Config } from '@/config.js';
|
|
||||||
import type { MiMeta } from '@/models/Meta.js';
|
|
||||||
|
|
||||||
export async function getInstance(
|
|
||||||
response: Entity.Instance,
|
|
||||||
contact: Entity.Account,
|
|
||||||
config: Config,
|
|
||||||
meta: MiMeta,
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
uri: config.url,
|
|
||||||
title: meta.name || 'Sharkey',
|
|
||||||
short_description:
|
|
||||||
meta.description || 'This is a vanilla Sharkey Instance. It doesn\'t seem to have a description.',
|
|
||||||
description:
|
|
||||||
meta.description ||
|
|
||||||
'This is a vanilla Sharkey Instance. It doesn\'t seem to have a description.',
|
|
||||||
email: response.email || '',
|
|
||||||
version: `3.0.0 (compatible; Sharkey ${config.version})`,
|
|
||||||
urls: response.urls,
|
|
||||||
stats: {
|
|
||||||
user_count: response.stats.user_count,
|
|
||||||
status_count: response.stats.status_count,
|
|
||||||
domain_count: response.stats.domain_count,
|
|
||||||
},
|
|
||||||
thumbnail: meta.backgroundImageUrl || '/static-assets/transparent.png',
|
|
||||||
languages: meta.langs,
|
|
||||||
registrations: !meta.disableRegistration || response.registrations,
|
|
||||||
approval_required: meta.approvalRequiredForSignup,
|
|
||||||
invites_enabled: response.registrations,
|
|
||||||
configuration: {
|
|
||||||
accounts: {
|
|
||||||
max_featured_tags: 20,
|
|
||||||
},
|
|
||||||
statuses: {
|
|
||||||
max_characters: MAX_NOTE_TEXT_LENGTH,
|
|
||||||
max_media_attachments: 16,
|
|
||||||
characters_reserved_per_url: response.uri.length,
|
|
||||||
},
|
|
||||||
media_attachments: {
|
|
||||||
supported_mime_types: FILE_TYPE_BROWSERSAFE,
|
|
||||||
image_size_limit: 10485760,
|
|
||||||
image_matrix_limit: 16777216,
|
|
||||||
video_size_limit: 41943040,
|
|
||||||
video_frame_rate_limit: 60,
|
|
||||||
video_matrix_limit: 2304000,
|
|
||||||
},
|
|
||||||
polls: {
|
|
||||||
max_options: 10,
|
|
||||||
max_characters_per_option: 50,
|
|
||||||
min_expiration: 50,
|
|
||||||
max_expiration: 2629746,
|
|
||||||
},
|
|
||||||
reactions: {
|
|
||||||
max_reactions: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
contact_account: contact,
|
|
||||||
rules: [],
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
import { convertNotification } from '../converters.js';
|
|
||||||
import type { MegalodonInterface, Entity } from 'megalodon';
|
|
||||||
import type { FastifyRequest } from 'fastify';
|
|
||||||
|
|
||||||
function toLimitToInt(q: any) {
|
|
||||||
if (q.limit) if (typeof q.limit === 'string') q.limit = parseInt(q.limit, 10);
|
|
||||||
return q;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ApiNotifyMastodon {
|
|
||||||
private request: FastifyRequest;
|
|
||||||
private client: MegalodonInterface;
|
|
||||||
|
|
||||||
constructor(request: FastifyRequest, client: MegalodonInterface) {
|
|
||||||
this.request = request;
|
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getNotifications() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.getNotifications( toLimitToInt(this.request.query) );
|
|
||||||
const notifs = data.data;
|
|
||||||
const processed = notifs.map((n: Entity.Notification) => {
|
|
||||||
const convertedn = convertNotification(n);
|
|
||||||
if (convertedn.type !== 'follow' && convertedn.type !== 'follow_request') {
|
|
||||||
if (convertedn.type === 'reaction') convertedn.type = 'favourite';
|
|
||||||
return convertedn;
|
|
||||||
} else {
|
|
||||||
return convertedn;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return processed;
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getNotification() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.getNotification( (this.request.params as any).id );
|
|
||||||
const notif = convertNotification(data.data);
|
|
||||||
if (notif.type !== 'follow' && notif.type !== 'follow_request' && notif.type === 'reaction') notif.type = 'favourite';
|
|
||||||
return notif;
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async rmNotification() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.dismissNotification( (this.request.params as any).id );
|
|
||||||
return data.data;
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async rmNotifications() {
|
|
||||||
try {
|
|
||||||
const data = await this.client.dismissNotifications();
|
|
||||||
return data.data;
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
import { MastoConverters } from '../converters.js';
|
|
||||||
import { limitToInt } from './timeline.js';
|
|
||||||
import type { MegalodonInterface } from 'megalodon';
|
|
||||||
import type { FastifyRequest } from 'fastify';
|
|
||||||
|
|
||||||
export class ApiSearchMastodon {
|
|
||||||
private request: FastifyRequest;
|
|
||||||
private client: MegalodonInterface;
|
|
||||||
private BASE_URL: string;
|
|
||||||
|
|
||||||
constructor(request: FastifyRequest, client: MegalodonInterface, BASE_URL: string, private mastoConverter: MastoConverters) {
|
|
||||||
this.request = request;
|
|
||||||
this.client = client;
|
|
||||||
this.BASE_URL = BASE_URL;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async SearchV1() {
|
|
||||||
try {
|
|
||||||
const query: any = limitToInt(this.request.query as any);
|
|
||||||
const type = query.type || '';
|
|
||||||
const data = await this.client.search(query.q, { type: type, ...query });
|
|
||||||
return data.data;
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async SearchV2() {
|
|
||||||
try {
|
|
||||||
const query: any = limitToInt(this.request.query as any);
|
|
||||||
const type = query.type;
|
|
||||||
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 tags = !type || type === 'hashtags' ? await this.client.search(query.q, { type: 'hashtags', ...query }) : null;
|
|
||||||
const data = {
|
|
||||||
accounts: await Promise.all(acct?.data.accounts.map(async (account: any) => await this.mastoConverter.convertAccount(account)) ?? []),
|
|
||||||
statuses: await Promise.all(stat?.data.statuses.map(async (status: any) => await this.mastoConverter.convertStatus(status)) ?? []),
|
|
||||||
hashtags: tags?.data.hashtags ?? [],
|
|
||||||
};
|
|
||||||
return data;
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getStatusTrends() {
|
|
||||||
try {
|
|
||||||
const data = await fetch(`${this.BASE_URL}/api/notes/featured`,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({}),
|
|
||||||
})
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(data => data.map((status: any) => this.mastoConverter.convertStatus(status)));
|
|
||||||
return data;
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getSuggestions() {
|
|
||||||
try {
|
|
||||||
const data = await fetch(`${this.BASE_URL}/api/users`,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ i: this.request.headers.authorization?.replace('Bearer ', ''), limit: parseInt((this.request.query as any).limit) || 20, origin: 'local', sort: '+follower', state: 'alive' }),
|
|
||||||
}).then((res) => res.json()).then(data => data.map(((entry: any) => { return { source: 'global', account: entry }; })));
|
|
||||||
return Promise.all(data.map(async (suggestion: any) => { suggestion.account = await this.mastoConverter.convertAccount(suggestion.account); return suggestion; }));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,410 +0,0 @@
|
||||||
import querystring from 'querystring';
|
|
||||||
import { emojiRegexAtStartToEnd } from '@/misc/emoji-regex.js';
|
|
||||||
import { convertAttachment, convertPoll, convertStatusSource, MastoConverters } from '../converters.js';
|
|
||||||
import { getClient } from '../MastodonApiServerService.js';
|
|
||||||
import { limitToInt } from './timeline.js';
|
|
||||||
import type { Entity } from 'megalodon';
|
|
||||||
import type { FastifyInstance } from 'fastify';
|
|
||||||
import type { Config } from '@/config.js';
|
|
||||||
import { NoteEditRepository, NotesRepository, UsersRepository } from '@/models/_.js';
|
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
|
||||||
|
|
||||||
function normalizeQuery(data: any) {
|
|
||||||
const str = querystring.stringify(data);
|
|
||||||
return querystring.parse(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ApiStatusMastodon {
|
|
||||||
private fastify: FastifyInstance;
|
|
||||||
private mastoconverter: MastoConverters;
|
|
||||||
|
|
||||||
constructor(fastify: FastifyInstance, mastoconverter: MastoConverters) {
|
|
||||||
this.fastify = fastify;
|
|
||||||
this.mastoconverter = mastoconverter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getStatus() {
|
|
||||||
this.fastify.get<{ Params: { id: string } }>('/v1/statuses/:id', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.getStatus(_request.params.id);
|
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(_request.is404 ? 404 : 401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getStatusSource() {
|
|
||||||
this.fastify.get<{ Params: { id: string } }>('/v1/statuses/:id/source', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.getStatusSource(_request.params.id);
|
|
||||||
reply.send(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(_request.is404 ? 404 : 401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getContext() {
|
|
||||||
this.fastify.get<{ Params: { id: string } }>('/v1/statuses/:id/context', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
const query: any = _request.query;
|
|
||||||
try {
|
|
||||||
const data = await client.getStatusContext(_request.params.id, limitToInt(query));
|
|
||||||
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)));
|
|
||||||
reply.send(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(_request.is404 ? 404 : 401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getHistory() {
|
|
||||||
this.fastify.get<{ Params: { id: string } }>('/v1/statuses/:id/history', async (_request, reply) => {
|
|
||||||
try {
|
|
||||||
const edits = await this.mastoconverter.getEdits(_request.params.id);
|
|
||||||
reply.send(edits);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getReblogged() {
|
|
||||||
this.fastify.get<{ Params: { id: string } }>('/v1/statuses/:id/reblogged_by', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.getStatusRebloggedBy(_request.params.id);
|
|
||||||
reply.send(await Promise.all(data.data.map(async (account: Entity.Account) => await this.mastoconverter.convertAccount(account))));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getFavourites() {
|
|
||||||
this.fastify.get<{ Params: { id: string } }>('/v1/statuses/:id/favourited_by', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.getStatusFavouritedBy(_request.params.id);
|
|
||||||
reply.send(await Promise.all(data.data.map(async (account: Entity.Account) => await this.mastoconverter.convertAccount(account))));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getMedia() {
|
|
||||||
this.fastify.get<{ Params: { id: string } }>('/v1/media/:id', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.getMedia(_request.params.id);
|
|
||||||
reply.send(convertAttachment(data.data));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getPoll() {
|
|
||||||
this.fastify.get<{ Params: { id: string } }>('/v1/polls/:id', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.getPoll(_request.params.id);
|
|
||||||
reply.send(convertPoll(data.data));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async votePoll() {
|
|
||||||
this.fastify.post<{ Params: { id: string } }>('/v1/polls/:id/votes', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
const body: any = _request.body;
|
|
||||||
try {
|
|
||||||
const data = await client.votePoll(_request.params.id, body.choices);
|
|
||||||
reply.send(convertPoll(data.data));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async postStatus() {
|
|
||||||
this.fastify.post('/v1/statuses', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
let body: any = _request.body;
|
|
||||||
try {
|
|
||||||
if (
|
|
||||||
(!body.poll && body['poll[options][]']) ||
|
|
||||||
(!body.media_ids && body['media_ids[]'])
|
|
||||||
) {
|
|
||||||
body = normalizeQuery(body);
|
|
||||||
}
|
|
||||||
const text = body.status ? body.status : ' ';
|
|
||||||
const removed = text.replace(/@\S+/g, '').replace(/\s|/g, '');
|
|
||||||
const isDefaultEmoji = emojiRegexAtStartToEnd.test(removed);
|
|
||||||
const isCustomEmoji = /^:[a-zA-Z0-9@_]+:$/.test(removed);
|
|
||||||
if ((body.in_reply_to_id && isDefaultEmoji) || (body.in_reply_to_id && isCustomEmoji)) {
|
|
||||||
const a = await client.createEmojiReaction(
|
|
||||||
body.in_reply_to_id,
|
|
||||||
removed,
|
|
||||||
);
|
|
||||||
reply.send(a.data);
|
|
||||||
}
|
|
||||||
if (body.in_reply_to_id && removed === '/unreact') {
|
|
||||||
try {
|
|
||||||
const id = body.in_reply_to_id;
|
|
||||||
const post = await client.getStatus(id);
|
|
||||||
const react = post.data.emoji_reactions.filter((e: any) => e.me)[0].name;
|
|
||||||
const data = await client.deleteEmojiReaction(id, react);
|
|
||||||
reply.send(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!body.media_ids) body.media_ids = undefined;
|
|
||||||
if (body.media_ids && !body.media_ids.length) body.media_ids = undefined;
|
|
||||||
|
|
||||||
const { sensitive } = body;
|
|
||||||
body.sensitive = typeof sensitive === 'string' ? sensitive === 'true' : sensitive;
|
|
||||||
|
|
||||||
if (body.poll) {
|
|
||||||
if (
|
|
||||||
body.poll.expires_in != null &&
|
|
||||||
typeof body.poll.expires_in === 'string'
|
|
||||||
) body.poll.expires_in = parseInt(body.poll.expires_in);
|
|
||||||
if (
|
|
||||||
body.poll.multiple != null &&
|
|
||||||
typeof body.poll.multiple === 'string'
|
|
||||||
) body.poll.multiple = body.poll.multiple === 'true';
|
|
||||||
if (
|
|
||||||
body.poll.hide_totals != null &&
|
|
||||||
typeof body.poll.hide_totals === 'string'
|
|
||||||
) body.poll.hide_totals = body.poll.hide_totals === 'true';
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await client.postStatus(text, body);
|
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data as Entity.Status));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async updateStatus() {
|
|
||||||
this.fastify.put<{ Params: { id: string } }>('/v1/statuses/:id', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
const body: any = _request.body;
|
|
||||||
try {
|
|
||||||
if (!body.media_ids) body.media_ids = undefined;
|
|
||||||
if (body.media_ids && !body.media_ids.length) body.media_ids = undefined;
|
|
||||||
const data = await client.editStatus(_request.params.id, body);
|
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(_request.is404 ? 404 : 401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async addFavourite() {
|
|
||||||
this.fastify.post<{ Params: { id: string } }>('/v1/statuses/:id/favourite', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = (await client.createEmojiReaction(_request.params.id, '❤')) as any;
|
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async rmFavourite() {
|
|
||||||
this.fastify.post<{ Params: { id: string } }>('/v1/statuses/:id/unfavourite', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.deleteEmojiReaction(_request.params.id, '❤');
|
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async reblogStatus() {
|
|
||||||
this.fastify.post<{ Params: { id: string } }>('/v1/statuses/:id/reblog', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.reblogStatus(_request.params.id);
|
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async unreblogStatus() {
|
|
||||||
this.fastify.post<{ Params: { id: string } }>('/v1/statuses/:id/unreblog', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.unreblogStatus(_request.params.id);
|
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async bookmarkStatus() {
|
|
||||||
this.fastify.post<{ Params: { id: string } }>('/v1/statuses/:id/bookmark', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.bookmarkStatus(_request.params.id);
|
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async unbookmarkStatus() {
|
|
||||||
this.fastify.post<{ Params: { id: string } }>('/v1/statuses/:id/unbookmark', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.unbookmarkStatus(_request.params.id);
|
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async pinStatus() {
|
|
||||||
this.fastify.post<{ Params: { id: string } }>('/v1/statuses/:id/pin', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.pinStatus(_request.params.id);
|
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async unpinStatus() {
|
|
||||||
this.fastify.post<{ Params: { id: string } }>('/v1/statuses/:id/unpin', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.unpinStatus(_request.params.id);
|
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async reactStatus() {
|
|
||||||
this.fastify.post<{ Params: { id: string, name: string } }>('/v1/statuses/:id/react/:name', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.createEmojiReaction(_request.params.id, _request.params.name);
|
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async unreactStatus() {
|
|
||||||
this.fastify.post<{ Params: { id: string, name: string } }>('/v1/statuses/:id/unreact/:name', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.deleteEmojiReaction(_request.params.id, _request.params.name);
|
|
||||||
reply.send(await this.mastoconverter.convertStatus(data.data));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async deleteStatus() {
|
|
||||||
this.fastify.delete<{ Params: { id: string } }>('/v1/statuses/:id', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const data = await client.deleteStatus(_request.params.id);
|
|
||||||
reply.send(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,268 +0,0 @@
|
||||||
import { ParsedUrlQuery } from 'querystring';
|
|
||||||
import { convertConversation, convertList, MastoConverters } from '../converters.js';
|
|
||||||
import { getClient } from '../MastodonApiServerService.js';
|
|
||||||
import type { Entity } from 'megalodon';
|
|
||||||
import type { FastifyInstance } from 'fastify';
|
|
||||||
import type { Config } from '@/config.js';
|
|
||||||
import { NoteEditRepository, NotesRepository, UsersRepository } from '@/models/_.js';
|
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
|
||||||
|
|
||||||
export function limitToInt(q: ParsedUrlQuery) {
|
|
||||||
const object: any = q;
|
|
||||||
if (q.limit) if (typeof q.limit === 'string') object.limit = parseInt(q.limit, 10);
|
|
||||||
if (q.offset) if (typeof q.offset === 'string') object.offset = parseInt(q.offset, 10);
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function argsToBools(q: ParsedUrlQuery) {
|
|
||||||
// Values taken from https://docs.joinmastodon.org/client/intro/#boolean
|
|
||||||
const toBoolean = (value: string) =>
|
|
||||||
!['0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].includes(value);
|
|
||||||
|
|
||||||
// Keys taken from:
|
|
||||||
// - https://docs.joinmastodon.org/methods/accounts/#statuses
|
|
||||||
// - https://docs.joinmastodon.org/methods/timelines/#public
|
|
||||||
// - https://docs.joinmastodon.org/methods/timelines/#tag
|
|
||||||
const object: any = q;
|
|
||||||
if (q.only_media) if (typeof q.only_media === 'string') object.only_media = toBoolean(q.only_media);
|
|
||||||
if (q.exclude_replies) if (typeof q.exclude_replies === 'string') object.exclude_replies = toBoolean(q.exclude_replies);
|
|
||||||
if (q.exclude_reblogs) if (typeof q.exclude_reblogs === 'string') object.exclude_reblogs = toBoolean(q.exclude_reblogs);
|
|
||||||
if (q.pinned) if (typeof q.pinned === 'string') object.pinned = toBoolean(q.pinned);
|
|
||||||
if (q.local) if (typeof q.local === 'string') object.local = toBoolean(q.local);
|
|
||||||
return q;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ApiTimelineMastodon {
|
|
||||||
private fastify: FastifyInstance;
|
|
||||||
|
|
||||||
constructor(fastify: FastifyInstance, config: Config, private mastoconverter: MastoConverters) {
|
|
||||||
this.fastify = fastify;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getTL() {
|
|
||||||
this.fastify.get('/v1/timelines/public', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const query: any = _request.query;
|
|
||||||
const data = query.local === 'true'
|
|
||||||
? await client.getLocalTimeline(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))));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getHomeTl() {
|
|
||||||
this.fastify.get('/v1/timelines/home', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const query: any = _request.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))));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getTagTl() {
|
|
||||||
this.fastify.get<{ Params: { hashtag: string } }>('/v1/timelines/tag/:hashtag', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const query: any = _request.query;
|
|
||||||
const params: any = _request.params;
|
|
||||||
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))));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getListTL() {
|
|
||||||
this.fastify.get<{ Params: { id: string } }>('/v1/timelines/list/:id', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const query: any = _request.query;
|
|
||||||
const params: any = _request.params;
|
|
||||||
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))));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getConversations() {
|
|
||||||
this.fastify.get('/v1/conversations', async (_request, reply) => {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
try {
|
|
||||||
const query: any = _request.query;
|
|
||||||
const data = await client.getConversationTimeline(limitToInt(query));
|
|
||||||
reply.send(data.data.map((conversation: Entity.Conversation) => convertConversation(conversation)));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getList() {
|
|
||||||
this.fastify.get<{ Params: { id: string } }>('/v1/lists/:id', async (_request, reply) => {
|
|
||||||
try {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
const params: any = _request.params;
|
|
||||||
const data = await client.getList(params.id);
|
|
||||||
reply.send(convertList(data.data));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getLists() {
|
|
||||||
this.fastify.get('/v1/lists', async (_request, reply) => {
|
|
||||||
try {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
const data = await client.getLists();
|
|
||||||
reply.send(data.data.map((list: Entity.List) => convertList(list)));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
return e.response.data;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getListAccounts() {
|
|
||||||
this.fastify.get<{ Params: { id: string } }>('/v1/lists/:id/accounts', async (_request, reply) => {
|
|
||||||
try {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
const params: any = _request.params;
|
|
||||||
const query: any = _request.query;
|
|
||||||
const data = await client.getAccountsInList(params.id, query);
|
|
||||||
reply.send(data.data.map((account: Entity.Account) => this.mastoconverter.convertAccount(account)));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async addListAccount() {
|
|
||||||
this.fastify.post<{ Params: { id: string } }>('/v1/lists/:id/accounts', async (_request, reply) => {
|
|
||||||
try {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
const params: any = _request.params;
|
|
||||||
const query: any = _request.query;
|
|
||||||
const data = await client.addAccountsToList(params.id, query.accounts_id);
|
|
||||||
reply.send(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async rmListAccount() {
|
|
||||||
this.fastify.delete<{ Params: { id: string } }>('/v1/lists/:id/accounts', async (_request, reply) => {
|
|
||||||
try {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
const params: any = _request.params;
|
|
||||||
const query: any = _request.query;
|
|
||||||
const data = await client.deleteAccountsFromList(params.id, query.accounts_id);
|
|
||||||
reply.send(data.data);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async createList() {
|
|
||||||
this.fastify.post('/v1/lists', async (_request, reply) => {
|
|
||||||
try {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
const body: any = _request.body;
|
|
||||||
const data = await client.createList(body.title);
|
|
||||||
reply.send(convertList(data.data));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async updateList() {
|
|
||||||
this.fastify.put<{ Params: { id: string } }>('/v1/lists/:id', async (_request, reply) => {
|
|
||||||
try {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
const body: any = _request.body;
|
|
||||||
const params: any = _request.params;
|
|
||||||
const data = await client.updateList(params.id, body.title);
|
|
||||||
reply.send(convertList(data.data));
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async deleteList() {
|
|
||||||
this.fastify.delete<{ Params: { id: string } }>('/v1/lists/:id', async (_request, reply) => {
|
|
||||||
try {
|
|
||||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
|
||||||
const accessTokens = _request.headers.authorization;
|
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
|
||||||
const params: any = _request.params;
|
|
||||||
const data = await client.deleteList(params.id);
|
|
||||||
reply.send({});
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
console.error(e.response.data);
|
|
||||||
reply.code(401).send(e.response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
node_modules
|
|
||||||
./src
|
|
||||||
tsconfig.json
|
|
|
@ -1,90 +0,0 @@
|
||||||
{
|
|
||||||
"name": "megalodon",
|
|
||||||
"version": "7.0.1",
|
|
||||||
"description": "Mastodon API client for node.js and browser",
|
|
||||||
"main": "./lib/src/index.js",
|
|
||||||
"typings": "./lib/src/index.d.ts",
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsc -p ./",
|
|
||||||
"lint": "eslint --ext .js,.ts src",
|
|
||||||
"doc": "typedoc --out ../docs ./src",
|
|
||||||
"test": "NODE_ENV=test jest -u --maxWorkers=3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=15.0.0"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/h3poteto/megalodon.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"mastodon",
|
|
||||||
"client",
|
|
||||||
"api",
|
|
||||||
"streaming",
|
|
||||||
"rest",
|
|
||||||
"proxy"
|
|
||||||
],
|
|
||||||
"author": "h3poteto",
|
|
||||||
"license": "MIT",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/h3poteto/megalodon/issues"
|
|
||||||
},
|
|
||||||
"jest": {
|
|
||||||
"moduleFileExtensions": [
|
|
||||||
"ts",
|
|
||||||
"js"
|
|
||||||
],
|
|
||||||
"moduleNameMapper": {
|
|
||||||
"^@/(.+)": "<rootDir>/src/$1",
|
|
||||||
"^~/(.+)": "<rootDir>/$1"
|
|
||||||
},
|
|
||||||
"testMatch": [
|
|
||||||
"**/test/**/*.spec.ts"
|
|
||||||
],
|
|
||||||
"preset": "ts-jest/presets/default",
|
|
||||||
"transform": {
|
|
||||||
"^.+\\.(ts|tsx)$": [
|
|
||||||
"ts-jest",
|
|
||||||
{
|
|
||||||
"tsconfig": "tsconfig.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"testEnvironment": "node"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/h3poteto/megalodon#readme",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/core-js": "^2.5.8",
|
|
||||||
"@types/form-data": "^2.5.0",
|
|
||||||
"@types/jest": "^29.5.10",
|
|
||||||
"@types/oauth": "^0.9.4",
|
|
||||||
"@types/object-assign-deep": "^0.4.3",
|
|
||||||
"@types/parse-link-header": "^2.0.3",
|
|
||||||
"@types/uuid": "^9.0.7",
|
|
||||||
"@types/ws": "^8.5.10",
|
|
||||||
"axios": "1.6.0",
|
|
||||||
"dayjs": "^1.11.10",
|
|
||||||
"form-data": "^4.0.0",
|
|
||||||
"https-proxy-agent": "^7.0.2",
|
|
||||||
"oauth": "^0.10.0",
|
|
||||||
"object-assign-deep": "^0.4.0",
|
|
||||||
"parse-link-header": "^2.0.0",
|
|
||||||
"socks-proxy-agent": "^8.0.2",
|
|
||||||
"typescript": "5.1.6",
|
|
||||||
"uuid": "^9.0.1",
|
|
||||||
"ws": "8.14.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
|
||||||
"@typescript-eslint/parser": "^6.12.0",
|
|
||||||
"eslint": "^8.54.0",
|
|
||||||
"eslint-config-prettier": "^9.0.0",
|
|
||||||
"jest": "^29.7.0",
|
|
||||||
"jest-worker": "^29.7.0",
|
|
||||||
"lodash": "4.17.21",
|
|
||||||
"prettier": "^3.1.0",
|
|
||||||
"ts-jest": "^29.1.1",
|
|
||||||
"typedoc": "^0.25.3"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
declare module 'axios/lib/adapters/http'
|
|
|
@ -1,13 +0,0 @@
|
||||||
export class RequestCanceledError extends Error {
|
|
||||||
public isCancel: boolean
|
|
||||||
|
|
||||||
constructor(msg: string) {
|
|
||||||
super(msg)
|
|
||||||
this.isCancel = true
|
|
||||||
Object.setPrototypeOf(this, RequestCanceledError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isCancel = (value: any): boolean => {
|
|
||||||
return value && value.isCancel
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
import MisskeyAPI from "./misskey/api_client";
|
|
||||||
|
|
||||||
export default MisskeyAPI.Converter;
|
|
|
@ -1,3 +0,0 @@
|
||||||
export const NO_REDIRECT = 'urn:ietf:wg:oauth:2.0:oob'
|
|
||||||
export const DEFAULT_SCOPE = ['read', 'write', 'follow']
|
|
||||||
export const DEFAULT_UA = 'megalodon'
|
|
|
@ -1,137 +0,0 @@
|
||||||
import axios, { AxiosRequestConfig } from 'axios'
|
|
||||||
import proxyAgent, { ProxyConfig } from './proxy_config'
|
|
||||||
import { NodeinfoError } from './megalodon'
|
|
||||||
|
|
||||||
const NODEINFO_10 = 'http://nodeinfo.diaspora.software/ns/schema/1.0'
|
|
||||||
const NODEINFO_20 = 'http://nodeinfo.diaspora.software/ns/schema/2.0'
|
|
||||||
const NODEINFO_21 = 'http://nodeinfo.diaspora.software/ns/schema/2.1'
|
|
||||||
|
|
||||||
type Links = {
|
|
||||||
links: Array<Link>
|
|
||||||
}
|
|
||||||
|
|
||||||
type Link = {
|
|
||||||
href: string
|
|
||||||
rel: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Nodeinfo10 = {
|
|
||||||
software: Software
|
|
||||||
metadata: Metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
type Nodeinfo20 = {
|
|
||||||
software: Software
|
|
||||||
metadata: Metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
type Nodeinfo21 = {
|
|
||||||
software: Software
|
|
||||||
metadata: Metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
type Software = {
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Metadata = {
|
|
||||||
upstream?: {
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detect SNS type.
|
|
||||||
* Now support Mastodon, Pleroma and Pixelfed. Throws an error when no known platform can be detected.
|
|
||||||
*
|
|
||||||
* @param url Base URL of SNS.
|
|
||||||
* @param proxyConfig Proxy setting, or set false if don't use proxy.
|
|
||||||
* @return SNS name.
|
|
||||||
*/
|
|
||||||
export const detector = async (
|
|
||||||
url: string,
|
|
||||||
proxyConfig: ProxyConfig | false = false
|
|
||||||
): Promise<'mastodon' | 'pleroma' | 'misskey' | 'friendica'> => {
|
|
||||||
let options: AxiosRequestConfig = {
|
|
||||||
timeout: 20000
|
|
||||||
}
|
|
||||||
if (proxyConfig) {
|
|
||||||
options = Object.assign(options, {
|
|
||||||
httpsAgent: proxyAgent(proxyConfig)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await axios.get<Links>(url + '/.well-known/nodeinfo', options)
|
|
||||||
const link = res.data.links.find(l => l.rel === NODEINFO_20 || l.rel === NODEINFO_21)
|
|
||||||
if (!link) throw new NodeinfoError('Could not find nodeinfo')
|
|
||||||
switch (link.rel) {
|
|
||||||
case NODEINFO_10: {
|
|
||||||
const res = await axios.get<Nodeinfo10>(link.href, options)
|
|
||||||
switch (res.data.software.name) {
|
|
||||||
case 'pleroma':
|
|
||||||
return 'pleroma'
|
|
||||||
case 'akkoma':
|
|
||||||
return 'pleroma'
|
|
||||||
case 'mastodon':
|
|
||||||
return 'mastodon'
|
|
||||||
case "wildebeest":
|
|
||||||
return "mastodon"
|
|
||||||
case 'misskey':
|
|
||||||
return 'misskey'
|
|
||||||
case 'friendica':
|
|
||||||
return 'friendica'
|
|
||||||
default:
|
|
||||||
if (res.data.metadata.upstream?.name && res.data.metadata.upstream.name === 'mastodon') {
|
|
||||||
return 'mastodon'
|
|
||||||
}
|
|
||||||
throw new NodeinfoError('Unknown SNS')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case NODEINFO_20: {
|
|
||||||
const res = await axios.get<Nodeinfo20>(link.href, options)
|
|
||||||
switch (res.data.software.name) {
|
|
||||||
case 'pleroma':
|
|
||||||
return 'pleroma'
|
|
||||||
case 'akkoma':
|
|
||||||
return 'pleroma'
|
|
||||||
case 'mastodon':
|
|
||||||
return 'mastodon'
|
|
||||||
case "wildebeest":
|
|
||||||
return "mastodon"
|
|
||||||
case 'misskey':
|
|
||||||
return 'misskey'
|
|
||||||
case 'friendica':
|
|
||||||
return 'friendica'
|
|
||||||
default:
|
|
||||||
if (res.data.metadata.upstream?.name && res.data.metadata.upstream.name === 'mastodon') {
|
|
||||||
return 'mastodon'
|
|
||||||
}
|
|
||||||
throw new NodeinfoError('Unknown SNS')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case NODEINFO_21: {
|
|
||||||
const res = await axios.get<Nodeinfo21>(link.href, options)
|
|
||||||
switch (res.data.software.name) {
|
|
||||||
case 'pleroma':
|
|
||||||
return 'pleroma'
|
|
||||||
case 'akkoma':
|
|
||||||
return 'pleroma'
|
|
||||||
case 'mastodon':
|
|
||||||
return 'mastodon'
|
|
||||||
case "wildebeest":
|
|
||||||
return "mastodon"
|
|
||||||
case 'misskey':
|
|
||||||
return 'misskey'
|
|
||||||
case 'friendica':
|
|
||||||
return 'friendica'
|
|
||||||
default:
|
|
||||||
if (res.data.metadata.upstream?.name && res.data.metadata.upstream.name === 'mastodon') {
|
|
||||||
return 'mastodon'
|
|
||||||
}
|
|
||||||
throw new NodeinfoError('Unknown SNS')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new NodeinfoError('Could not find nodeinfo')
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
/// <reference path="emoji.ts" />
|
|
||||||
/// <reference path="source.ts" />
|
|
||||||
/// <reference path="field.ts" />
|
|
||||||
/// <reference path="role.ts" />
|
|
||||||
namespace Entity {
|
|
||||||
export type Account = {
|
|
||||||
id: string
|
|
||||||
fqn?: string
|
|
||||||
username: string
|
|
||||||
acct: string
|
|
||||||
display_name: string
|
|
||||||
locked: boolean
|
|
||||||
discoverable?: boolean
|
|
||||||
group?: boolean | null
|
|
||||||
noindex?: boolean | null
|
|
||||||
suspended?: boolean | null
|
|
||||||
limited?: boolean | null
|
|
||||||
created_at: string
|
|
||||||
followers_count: number
|
|
||||||
following_count: number
|
|
||||||
statuses_count: number
|
|
||||||
note: string
|
|
||||||
url: string
|
|
||||||
avatar: string
|
|
||||||
avatar_static: string
|
|
||||||
header: string
|
|
||||||
header_static: string
|
|
||||||
emojis: Array<Emoji>
|
|
||||||
moved: Account | null
|
|
||||||
fields: Array<Field>
|
|
||||||
bot: boolean | null
|
|
||||||
source?: Source
|
|
||||||
role?: Role
|
|
||||||
mute_expires_at?: string
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace Entity {
|
|
||||||
export type Activity = {
|
|
||||||
week: string
|
|
||||||
statuses: string
|
|
||||||
logins: string
|
|
||||||
registrations: string
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
/// <reference path="emoji.ts" />
|
|
||||||
|
|
||||||
namespace Entity {
|
|
||||||
export type Announcement = {
|
|
||||||
id: string
|
|
||||||
content: string
|
|
||||||
starts_at: string | null
|
|
||||||
ends_at: string | null
|
|
||||||
published: boolean
|
|
||||||
all_day: boolean
|
|
||||||
published_at: string
|
|
||||||
updated_at: string | null
|
|
||||||
read: boolean | null
|
|
||||||
mentions: Array<AnnouncementAccount>
|
|
||||||
statuses: Array<AnnouncementStatus>
|
|
||||||
tags: Array<StatusTag>
|
|
||||||
emojis: Array<Emoji>
|
|
||||||
reactions: Array<AnnouncementReaction>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AnnouncementAccount = {
|
|
||||||
id: string
|
|
||||||
username: string
|
|
||||||
url: string
|
|
||||||
acct: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AnnouncementStatus = {
|
|
||||||
id: string
|
|
||||||
url: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AnnouncementReaction = {
|
|
||||||
name: string
|
|
||||||
count: number
|
|
||||||
me: boolean | null
|
|
||||||
url: string | null
|
|
||||||
static_url: string | null
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
namespace Entity {
|
|
||||||
export type Application = {
|
|
||||||
name: string
|
|
||||||
website?: string | null
|
|
||||||
vapid_key?: string | null
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
/// <reference path="attachment.ts" />
|
|
||||||
namespace Entity {
|
|
||||||
export type AsyncAttachment = {
|
|
||||||
id: string
|
|
||||||
type: 'unknown' | 'image' | 'gifv' | 'video' | 'audio'
|
|
||||||
url: string | null
|
|
||||||
remote_url: string | null
|
|
||||||
preview_url: string
|
|
||||||
text_url: string | null
|
|
||||||
meta: Meta | null
|
|
||||||
description: string | null
|
|
||||||
blurhash: string | null
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
namespace Entity {
|
|
||||||
export type Sub = {
|
|
||||||
// For Image, Gifv, and Video
|
|
||||||
width?: number
|
|
||||||
height?: number
|
|
||||||
size?: string
|
|
||||||
aspect?: number
|
|
||||||
|
|
||||||
// For Gifv and Video
|
|
||||||
frame_rate?: string
|
|
||||||
|
|
||||||
// For Audio, Gifv, and Video
|
|
||||||
duration?: number
|
|
||||||
bitrate?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Focus = {
|
|
||||||
x: number
|
|
||||||
y: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Meta = {
|
|
||||||
original?: Sub
|
|
||||||
small?: Sub
|
|
||||||
focus?: Focus
|
|
||||||
length?: string
|
|
||||||
duration?: number
|
|
||||||
fps?: number
|
|
||||||
size?: string
|
|
||||||
width?: number
|
|
||||||
height?: number
|
|
||||||
aspect?: number
|
|
||||||
audio_encode?: string
|
|
||||||
audio_bitrate?: string
|
|
||||||
audio_channel?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Attachment = {
|
|
||||||
id: string
|
|
||||||
type: 'unknown' | 'image' | 'gifv' | 'video' | 'audio'
|
|
||||||
url: string
|
|
||||||
remote_url: string | null
|
|
||||||
preview_url: string | null
|
|
||||||
text_url: string | null
|
|
||||||
meta: Meta | null
|
|
||||||
description: string | null
|
|
||||||
blurhash: string | null
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
namespace Entity {
|
|
||||||
export type Card = {
|
|
||||||
url: string
|
|
||||||
title: string
|
|
||||||
description: string
|
|
||||||
type: 'link' | 'photo' | 'video' | 'rich'
|
|
||||||
image: string | null
|
|
||||||
author_name: string | null
|
|
||||||
author_url: string | null
|
|
||||||
provider_name: string | null
|
|
||||||
provider_url: string | null
|
|
||||||
html: string | null
|
|
||||||
width: number | null
|
|
||||||
height: number | null
|
|
||||||
embed_url: string | null
|
|
||||||
blurhash: string | null
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
/// <reference path="status.ts" />
|
|
||||||
|
|
||||||
namespace Entity {
|
|
||||||
export type Context = {
|
|
||||||
ancestors: Array<Status>
|
|
||||||
descendants: Array<Status>
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
/// <reference path="account.ts" />
|
|
||||||
/// <reference path="status.ts" />
|
|
||||||
|
|
||||||
namespace Entity {
|
|
||||||
export type Conversation = {
|
|
||||||
id: string
|
|
||||||
accounts: Array<Account>
|
|
||||||
last_status: Status | null
|
|
||||||
unread: boolean
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
namespace Entity {
|
|
||||||
export type Emoji = {
|
|
||||||
shortcode: string
|
|
||||||
static_url: string
|
|
||||||
url: string
|
|
||||||
visible_in_picker: boolean
|
|
||||||
category?: string
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace Entity {
|
|
||||||
export type FeaturedTag = {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
statuses_count: number
|
|
||||||
last_status_at: string
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
namespace Entity {
|
|
||||||
export type Field = {
|
|
||||||
name: string
|
|
||||||
value: string
|
|
||||||
verified_at?: string | null
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
namespace Entity {
|
|
||||||
export type Filter = {
|
|
||||||
id: string
|
|
||||||
phrase: string
|
|
||||||
context: Array<FilterContext>
|
|
||||||
expires_at: string | null
|
|
||||||
irreversible: boolean
|
|
||||||
whole_word: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FilterContext = string
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
/// <reference path="emoji.ts" />
|
|
||||||
/// <reference path="field.ts" />
|
|
||||||
|
|
||||||
namespace Entity {
|
|
||||||
export type FollowRequest = {
|
|
||||||
id: number
|
|
||||||
username: string
|
|
||||||
acct: string
|
|
||||||
display_name: string
|
|
||||||
locked: boolean
|
|
||||||
bot: boolean
|
|
||||||
discoverable?: boolean
|
|
||||||
group: boolean
|
|
||||||
created_at: string
|
|
||||||
note: string
|
|
||||||
url: string
|
|
||||||
avatar: string
|
|
||||||
avatar_static: string
|
|
||||||
header: string
|
|
||||||
header_static: string
|
|
||||||
followers_count: number
|
|
||||||
following_count: number
|
|
||||||
statuses_count: number
|
|
||||||
emojis: Array<Emoji>
|
|
||||||
fields: Array<Field>
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
namespace Entity {
|
|
||||||
export type History = {
|
|
||||||
day: string
|
|
||||||
uses: number
|
|
||||||
accounts: number
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
namespace Entity {
|
|
||||||
export type IdentityProof = {
|
|
||||||
provider: string
|
|
||||||
provider_username: string
|
|
||||||
updated_at: string
|
|
||||||
proof_url: string
|
|
||||||
profile_url: string
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
/// <reference path="account.ts" />
|
|
||||||
/// <reference path="urls.ts" />
|
|
||||||
/// <reference path="stats.ts" />
|
|
||||||
|
|
||||||
namespace Entity {
|
|
||||||
export type Instance = {
|
|
||||||
uri: string
|
|
||||||
title: string
|
|
||||||
description: string
|
|
||||||
email: string
|
|
||||||
version: string
|
|
||||||
thumbnail: string | null
|
|
||||||
urls: URLs | null
|
|
||||||
stats: Stats
|
|
||||||
languages: Array<string>
|
|
||||||
registrations: boolean
|
|
||||||
approval_required: boolean
|
|
||||||
invites_enabled?: boolean
|
|
||||||
configuration: {
|
|
||||||
statuses: {
|
|
||||||
max_characters: number
|
|
||||||
max_media_attachments?: number
|
|
||||||
characters_reserved_per_url?: number
|
|
||||||
}
|
|
||||||
polls?: {
|
|
||||||
max_options: number
|
|
||||||
max_characters_per_option: number
|
|
||||||
min_expiration: number
|
|
||||||
max_expiration: number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
contact_account?: Account
|
|
||||||
rules?: Array<InstanceRule>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type InstanceRule = {
|
|
||||||
id: string
|
|
||||||
text: string
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
namespace Entity {
|
|
||||||
export type List = {
|
|
||||||
id: string
|
|
||||||
title: string
|
|
||||||
replies_policy?: RepliesPolicy | null
|
|
||||||
exclusive?: RepliesPolicy | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type RepliesPolicy = 'followed' | 'list' | 'none'
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
namespace Entity {
|
|
||||||
export type Marker = {
|
|
||||||
home?: {
|
|
||||||
last_read_id: string
|
|
||||||
version: number
|
|
||||||
updated_at: string
|
|
||||||
}
|
|
||||||
notifications?: {
|
|
||||||
last_read_id: string
|
|
||||||
version: number
|
|
||||||
updated_at: string
|
|
||||||
unread_count?: number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace Entity {
|
|
||||||
export type Mention = {
|
|
||||||
id: string
|
|
||||||
username: string
|
|
||||||
url: string
|
|
||||||
acct: string
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
/// <reference path="account.ts" />
|
|
||||||
/// <reference path="status.ts" />
|
|
||||||
|
|
||||||
namespace Entity {
|
|
||||||
export type Notification = {
|
|
||||||
account: Account
|
|
||||||
created_at: string
|
|
||||||
id: string
|
|
||||||
status?: Status
|
|
||||||
emoji?: string
|
|
||||||
type: NotificationType
|
|
||||||
target?: Account
|
|
||||||
}
|
|
||||||
|
|
||||||
export type NotificationType = string
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
/// <reference path="poll_option.ts" />
|
|
||||||
|
|
||||||
namespace Entity {
|
|
||||||
export type Poll = {
|
|
||||||
id: string
|
|
||||||
expires_at: string | null
|
|
||||||
expired: boolean
|
|
||||||
multiple: boolean
|
|
||||||
votes_count: number
|
|
||||||
options: Array<PollOption>
|
|
||||||
voted: boolean
|
|
||||||
emojis?: []
|
|
||||||
own_votes?: Array<number>
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
namespace Entity {
|
|
||||||
export type PollOption = {
|
|
||||||
title: string
|
|
||||||
votes_count: number | null
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
namespace Entity {
|
|
||||||
export type Preferences = {
|
|
||||||
'posting:default:visibility': 'public' | 'unlisted' | 'private' | 'direct'
|
|
||||||
'posting:default:sensitive': boolean
|
|
||||||
'posting:default:language': string | null
|
|
||||||
'reading:expand:media': 'default' | 'show_all' | 'hide_all'
|
|
||||||
'reading:expand:spoilers': boolean
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
namespace Entity {
|
|
||||||
export type Alerts = {
|
|
||||||
follow: boolean
|
|
||||||
favourite: boolean
|
|
||||||
mention: boolean
|
|
||||||
reblog: boolean
|
|
||||||
poll: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PushSubscription = {
|
|
||||||
id: string
|
|
||||||
endpoint: string
|
|
||||||
server_key: string
|
|
||||||
alerts: Alerts
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
/// <reference path="account.ts" />
|
|
||||||
|
|
||||||
namespace Entity {
|
|
||||||
export type Reaction = {
|
|
||||||
count: number
|
|
||||||
me: boolean
|
|
||||||
name: string
|
|
||||||
accounts?: Array<Account>
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
namespace Entity {
|
|
||||||
export type Relationship = {
|
|
||||||
id: string
|
|
||||||
following: boolean
|
|
||||||
followed_by: boolean
|
|
||||||
blocking: boolean
|
|
||||||
blocked_by: boolean
|
|
||||||
muting: boolean
|
|
||||||
muting_notifications: boolean
|
|
||||||
requested: boolean
|
|
||||||
domain_blocking: boolean
|
|
||||||
showing_reblogs: boolean
|
|
||||||
endorsed: boolean
|
|
||||||
notifying: boolean
|
|
||||||
note: string | null
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
/// <reference path="account.ts" />
|
|
||||||
|
|
||||||
namespace Entity {
|
|
||||||
export type Report = {
|
|
||||||
id: string
|
|
||||||
action_taken: boolean
|
|
||||||
action_taken_at: string | null
|
|
||||||
status_ids: Array<string> | null
|
|
||||||
rule_ids: Array<string> | null
|
|
||||||
// These parameters don't exist in Pleroma
|
|
||||||
category: Category | null
|
|
||||||
comment: string | null
|
|
||||||
forwarded: boolean | null
|
|
||||||
target_account?: Account | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Category = 'spam' | 'violation' | 'other'
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
/// <reference path="account.ts" />
|
|
||||||
/// <reference path="status.ts" />
|
|
||||||
/// <reference path="tag.ts" />
|
|
||||||
|
|
||||||
namespace Entity {
|
|
||||||
export type Results = {
|
|
||||||
accounts: Array<Account>
|
|
||||||
statuses: Array<Status>
|
|
||||||
hashtags: Array<Tag>
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
namespace Entity {
|
|
||||||
export type Role = {
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
/// <reference path="attachment.ts" />
|
|
||||||
/// <reference path="status_params.ts" />
|
|
||||||
namespace Entity {
|
|
||||||
export type ScheduledStatus = {
|
|
||||||
id: string
|
|
||||||
scheduled_at: string
|
|
||||||
params: StatusParams
|
|
||||||
media_attachments: Array<Attachment> | null
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
/// <reference path="field.ts" />
|
|
||||||
namespace Entity {
|
|
||||||
export type Source = {
|
|
||||||
privacy: string | null
|
|
||||||
sensitive: boolean | null
|
|
||||||
language: string | null
|
|
||||||
note: string
|
|
||||||
fields: Array<Field>
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
namespace Entity {
|
|
||||||
export type Stats = {
|
|
||||||
user_count: number
|
|
||||||
status_count: number
|
|
||||||
domain_count: number
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
/// <reference path="account.ts" />
|
|
||||||
/// <reference path="application.ts" />
|
|
||||||
/// <reference path="mention.ts" />
|
|
||||||
/// <reference path="attachment.ts" />
|
|
||||||
/// <reference path="emoji.ts" />
|
|
||||||
/// <reference path="card.ts" />
|
|
||||||
/// <reference path="poll.ts" />
|
|
||||||
/// <reference path="reaction.ts" />
|
|
||||||
|
|
||||||
namespace Entity {
|
|
||||||
export type Status = {
|
|
||||||
id: string
|
|
||||||
uri: string
|
|
||||||
url: string
|
|
||||||
account: Account
|
|
||||||
in_reply_to_id: string | null
|
|
||||||
in_reply_to_account_id: string | null
|
|
||||||
reblog: Status | null
|
|
||||||
content: string
|
|
||||||
plain_content?: string | null
|
|
||||||
created_at: string
|
|
||||||
emojis: Emoji[]
|
|
||||||
replies_count: number
|
|
||||||
reblogs_count: number
|
|
||||||
favourites_count: number
|
|
||||||
reblogged: boolean | null
|
|
||||||
favourited: boolean | null
|
|
||||||
muted: boolean | null
|
|
||||||
sensitive: boolean
|
|
||||||
spoiler_text: string
|
|
||||||
visibility: 'public' | 'unlisted' | 'private' | 'direct'
|
|
||||||
media_attachments: Array<Attachment>
|
|
||||||
mentions: Array<Mention>
|
|
||||||
tags: Array<StatusTag>
|
|
||||||
card: Card | null
|
|
||||||
poll: Poll | null
|
|
||||||
application: Application | null
|
|
||||||
language: string | null
|
|
||||||
pinned: boolean | null
|
|
||||||
emoji_reactions: Array<Reaction>
|
|
||||||
quote: Status | boolean
|
|
||||||
bookmarked: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export type StatusTag = {
|
|
||||||
name: string
|
|
||||||
url: string
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
/// <reference path="account.ts" />
|
|
||||||
/// <reference path="application.ts" />
|
|
||||||
/// <reference path="mention.ts" />
|
|
||||||
/// <reference path="tag.ts" />
|
|
||||||
/// <reference path="attachment.ts" />
|
|
||||||
/// <reference path="emoji.ts" />
|
|
||||||
/// <reference path="card.ts" />
|
|
||||||
/// <reference path="poll.ts" />
|
|
||||||
/// <reference path="reaction.ts" />
|
|
||||||
|
|
||||||
namespace Entity {
|
|
||||||
export type StatusEdit = {
|
|
||||||
account: Account;
|
|
||||||
content: string;
|
|
||||||
plain_content: string | null;
|
|
||||||
created_at: string;
|
|
||||||
emojis: Emoji[];
|
|
||||||
sensitive: boolean;
|
|
||||||
spoiler_text: string;
|
|
||||||
media_attachments: Array<Attachment>;
|
|
||||||
poll: Poll | null;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
namespace Entity {
|
|
||||||
export type StatusParams = {
|
|
||||||
text: string
|
|
||||||
in_reply_to_id: string | null
|
|
||||||
media_ids: Array<string> | null
|
|
||||||
sensitive: boolean | null
|
|
||||||
spoiler_text: string | null
|
|
||||||
visibility: 'public' | 'unlisted' | 'private' | 'direct' | null
|
|
||||||
scheduled_at: string | null
|
|
||||||
application_id: number | null
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
namespace Entity {
|
|
||||||
export type StatusSource = {
|
|
||||||
id: string
|
|
||||||
text: string
|
|
||||||
spoiler_text: string
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
/// <reference path="history.ts" />
|
|
||||||
|
|
||||||
namespace Entity {
|
|
||||||
export type Tag = {
|
|
||||||
name: string
|
|
||||||
url: string
|
|
||||||
history: Array<History>
|
|
||||||
following?: boolean
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace Entity {
|
|
||||||
export type Token = {
|
|
||||||
access_token: string
|
|
||||||
token_type: string
|
|
||||||
scope: string
|
|
||||||
created_at: number
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
namespace Entity {
|
|
||||||
export type URLs = {
|
|
||||||
streaming_api: string
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
/// <reference path="./entities/account.ts" />
|
|
||||||
/// <reference path="./entities/activity.ts" />
|
|
||||||
/// <reference path="./entities/announcement.ts" />
|
|
||||||
/// <reference path="./entities/application.ts" />
|
|
||||||
/// <reference path="./entities/async_attachment.ts" />
|
|
||||||
/// <reference path="./entities/attachment.ts" />
|
|
||||||
/// <reference path="./entities/card.ts" />
|
|
||||||
/// <reference path="./entities/context.ts" />
|
|
||||||
/// <reference path="./entities/conversation.ts" />
|
|
||||||
/// <reference path="./entities/emoji.ts" />
|
|
||||||
/// <reference path="./entities/featured_tag.ts" />
|
|
||||||
/// <reference path="./entities/field.ts" />
|
|
||||||
/// <reference path="./entities/filter.ts" />
|
|
||||||
/// <reference path="./entities/follow_request.ts" />
|
|
||||||
/// <reference path="./entities/history.ts" />
|
|
||||||
/// <reference path="./entities/identity_proof.ts" />
|
|
||||||
/// <reference path="./entities/instance.ts" />
|
|
||||||
/// <reference path="./entities/list.ts" />
|
|
||||||
/// <reference path="./entities/marker.ts" />
|
|
||||||
/// <reference path="./entities/mention.ts" />
|
|
||||||
/// <reference path="./entities/notification.ts" />
|
|
||||||
/// <reference path="./entities/poll.ts" />
|
|
||||||
/// <reference path="./entities/poll_option.ts" />
|
|
||||||
/// <reference path="./entities/preferences.ts" />
|
|
||||||
/// <reference path="./entities/push_subscription.ts" />
|
|
||||||
/// <reference path="./entities/reaction.ts" />
|
|
||||||
/// <reference path="./entities/relationship.ts" />
|
|
||||||
/// <reference path="./entities/report.ts" />
|
|
||||||
/// <reference path="./entities/results.ts" />
|
|
||||||
/// <reference path="./entities/scheduled_status.ts" />
|
|
||||||
/// <reference path="./entities/source.ts" />
|
|
||||||
/// <reference path="./entities/stats.ts" />
|
|
||||||
/// <reference path="./entities/status.ts" />
|
|
||||||
/// <reference path="./entities/status_params.ts" />
|
|
||||||
/// <reference path="./entities/status_source.ts" />
|
|
||||||
/// <reference path="./entities/tag.ts" />
|
|
||||||
/// <reference path="./entities/token.ts" />
|
|
||||||
/// <reference path="./entities/urls.ts" />
|
|
||||||
|
|
||||||
export default Entity
|
|
|
@ -1,11 +0,0 @@
|
||||||
import Entity from './entity'
|
|
||||||
|
|
||||||
namespace FilterContext {
|
|
||||||
export const Home: Entity.FilterContext = 'home'
|
|
||||||
export const Notifications: Entity.FilterContext = 'notifications'
|
|
||||||
export const Public: Entity.FilterContext = 'public'
|
|
||||||
export const Thread: Entity.FilterContext = 'thread'
|
|
||||||
export const Account: Entity.FilterContext = 'account'
|
|
||||||
}
|
|
||||||
|
|
||||||
export default FilterContext
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,768 +0,0 @@
|
||||||
import axios, { AxiosResponse, AxiosRequestConfig } from 'axios'
|
|
||||||
import objectAssignDeep from 'object-assign-deep'
|
|
||||||
|
|
||||||
import WebSocket from './web_socket'
|
|
||||||
import Response from '../response'
|
|
||||||
import { RequestCanceledError } from '../cancel'
|
|
||||||
import proxyAgent, { ProxyConfig } from '../proxy_config'
|
|
||||||
import { NO_REDIRECT, DEFAULT_SCOPE, DEFAULT_UA } from '../default'
|
|
||||||
import FriendicaEntity from './entity'
|
|
||||||
import MegalodonEntity from '../entity'
|
|
||||||
import NotificationType, { UnknownNotificationTypeError } from '../notification'
|
|
||||||
import FriendicaNotificationType from './notification'
|
|
||||||
|
|
||||||
namespace FriendicaAPI {
|
|
||||||
/**
|
|
||||||
* Interface
|
|
||||||
*/
|
|
||||||
export interface Interface {
|
|
||||||
get<T = any>(path: string, params?: any, headers?: { [key: string]: string }, pathIsFullyQualified?: boolean): Promise<Response<T>>
|
|
||||||
put<T = any>(path: string, params?: any, headers?: { [key: string]: string }): Promise<Response<T>>
|
|
||||||
putForm<T = any>(path: string, params?: any, headers?: { [key: string]: string }): Promise<Response<T>>
|
|
||||||
patch<T = any>(path: string, params?: any, headers?: { [key: string]: string }): Promise<Response<T>>
|
|
||||||
patchForm<T = any>(path: string, params?: any, headers?: { [key: string]: string }): Promise<Response<T>>
|
|
||||||
post<T = any>(path: string, params?: any, headers?: { [key: string]: string }): Promise<Response<T>>
|
|
||||||
postForm<T = any>(path: string, params?: any, headers?: { [key: string]: string }): Promise<Response<T>>
|
|
||||||
del<T = any>(path: string, params?: any, headers?: { [key: string]: string }): Promise<Response<T>>
|
|
||||||
cancel(): void
|
|
||||||
socket(path: string, stream: string, params?: string): WebSocket
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Friendica API client.
|
|
||||||
*
|
|
||||||
* Using axios for request, you will handle promises.
|
|
||||||
*/
|
|
||||||
export class Client implements Interface {
|
|
||||||
static DEFAULT_SCOPE = DEFAULT_SCOPE
|
|
||||||
static DEFAULT_URL = 'https://mastodon.social'
|
|
||||||
static NO_REDIRECT = NO_REDIRECT
|
|
||||||
|
|
||||||
private accessToken: string | null
|
|
||||||
private baseUrl: string
|
|
||||||
private userAgent: string
|
|
||||||
private abortController: AbortController
|
|
||||||
private proxyConfig: ProxyConfig | false = false
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param baseUrl hostname or base URL
|
|
||||||
* @param accessToken access token from OAuth2 authorization
|
|
||||||
* @param userAgent UserAgent is specified in header on request.
|
|
||||||
* @param proxyConfig Proxy setting, or set false if don't use proxy.
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
baseUrl: string,
|
|
||||||
accessToken: string | null = null,
|
|
||||||
userAgent: string = DEFAULT_UA,
|
|
||||||
proxyConfig: ProxyConfig | false = false
|
|
||||||
) {
|
|
||||||
this.accessToken = accessToken
|
|
||||||
this.baseUrl = baseUrl
|
|
||||||
this.userAgent = userAgent
|
|
||||||
this.proxyConfig = proxyConfig
|
|
||||||
this.abortController = new AbortController()
|
|
||||||
axios.defaults.signal = this.abortController.signal
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET request to mastodon REST API.
|
|
||||||
* @param path relative path from baseUrl
|
|
||||||
* @param params Query parameters
|
|
||||||
* @param headers Request header object
|
|
||||||
*/
|
|
||||||
public async get<T>(
|
|
||||||
path: string,
|
|
||||||
params = {},
|
|
||||||
headers: { [key: string]: string } = {},
|
|
||||||
pathIsFullyQualified = false
|
|
||||||
): Promise<Response<T>> {
|
|
||||||
let options: AxiosRequestConfig = {
|
|
||||||
params: params,
|
|
||||||
headers: headers,
|
|
||||||
maxContentLength: Infinity,
|
|
||||||
maxBodyLength: Infinity
|
|
||||||
}
|
|
||||||
if (this.accessToken) {
|
|
||||||
options = objectAssignDeep({}, options, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${this.accessToken}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (this.proxyConfig) {
|
|
||||||
options = Object.assign(options, {
|
|
||||||
httpAgent: proxyAgent(this.proxyConfig),
|
|
||||||
httpsAgent: proxyAgent(this.proxyConfig)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return axios
|
|
||||||
.get<T>((pathIsFullyQualified ? '' : this.baseUrl) + path, options)
|
|
||||||
.catch((err: Error) => {
|
|
||||||
if (axios.isCancel(err)) {
|
|
||||||
throw new RequestCanceledError(err.message)
|
|
||||||
} else {
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((resp: AxiosResponse<T>) => {
|
|
||||||
const res: Response<T> = {
|
|
||||||
data: resp.data,
|
|
||||||
status: resp.status,
|
|
||||||
statusText: resp.statusText,
|
|
||||||
headers: resp.headers
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PUT request to mastodon REST API.
|
|
||||||
* @param path relative path from baseUrl
|
|
||||||
* @param params Form data. If you want to post file, please use FormData()
|
|
||||||
* @param headers Request header object
|
|
||||||
*/
|
|
||||||
public async put<T>(path: string, params = {}, headers: { [key: string]: string } = {}): Promise<Response<T>> {
|
|
||||||
let options: AxiosRequestConfig = {
|
|
||||||
headers: headers,
|
|
||||||
maxContentLength: Infinity,
|
|
||||||
maxBodyLength: Infinity
|
|
||||||
}
|
|
||||||
if (this.accessToken) {
|
|
||||||
options = objectAssignDeep({}, options, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${this.accessToken}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (this.proxyConfig) {
|
|
||||||
options = Object.assign(options, {
|
|
||||||
httpAgent: proxyAgent(this.proxyConfig),
|
|
||||||
httpsAgent: proxyAgent(this.proxyConfig)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return axios
|
|
||||||
.put<T>(this.baseUrl + path, params, options)
|
|
||||||
.catch((err: Error) => {
|
|
||||||
if (axios.isCancel(err)) {
|
|
||||||
throw new RequestCanceledError(err.message)
|
|
||||||
} else {
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((resp: AxiosResponse<T>) => {
|
|
||||||
const res: Response<T> = {
|
|
||||||
data: resp.data,
|
|
||||||
status: resp.status,
|
|
||||||
statusText: resp.statusText,
|
|
||||||
headers: resp.headers
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PUT request to mastodon REST API for multipart.
|
|
||||||
* @param path relative path from baseUrl
|
|
||||||
* @param params Form data. If you want to post file, please use FormData()
|
|
||||||
* @param headers Request header object
|
|
||||||
*/
|
|
||||||
public async putForm<T>(path: string, params = {}, headers: { [key: string]: string } = {}): Promise<Response<T>> {
|
|
||||||
let options: AxiosRequestConfig = {
|
|
||||||
headers: headers,
|
|
||||||
maxContentLength: Infinity,
|
|
||||||
maxBodyLength: Infinity
|
|
||||||
}
|
|
||||||
if (this.accessToken) {
|
|
||||||
options = objectAssignDeep({}, options, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${this.accessToken}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (this.proxyConfig) {
|
|
||||||
options = Object.assign(options, {
|
|
||||||
httpAgent: proxyAgent(this.proxyConfig),
|
|
||||||
httpsAgent: proxyAgent(this.proxyConfig)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return axios
|
|
||||||
.putForm<T>(this.baseUrl + path, params, options)
|
|
||||||
.catch((err: Error) => {
|
|
||||||
if (axios.isCancel(err)) {
|
|
||||||
throw new RequestCanceledError(err.message)
|
|
||||||
} else {
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((resp: AxiosResponse<T>) => {
|
|
||||||
const res: Response<T> = {
|
|
||||||
data: resp.data,
|
|
||||||
status: resp.status,
|
|
||||||
statusText: resp.statusText,
|
|
||||||
headers: resp.headers
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PATCH request to mastodon REST API.
|
|
||||||
* @param path relative path from baseUrl
|
|
||||||
* @param params Form data. If you want to post file, please use FormData()
|
|
||||||
* @param headers Request header object
|
|
||||||
*/
|
|
||||||
public async patch<T>(path: string, params = {}, headers: { [key: string]: string } = {}): Promise<Response<T>> {
|
|
||||||
let options: AxiosRequestConfig = {
|
|
||||||
headers: headers,
|
|
||||||
maxContentLength: Infinity,
|
|
||||||
maxBodyLength: Infinity
|
|
||||||
}
|
|
||||||
if (this.accessToken) {
|
|
||||||
options = objectAssignDeep({}, options, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${this.accessToken}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (this.proxyConfig) {
|
|
||||||
options = Object.assign(options, {
|
|
||||||
httpAgent: proxyAgent(this.proxyConfig),
|
|
||||||
httpsAgent: proxyAgent(this.proxyConfig)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return axios
|
|
||||||
.patch<T>(this.baseUrl + path, params, options)
|
|
||||||
.catch((err: Error) => {
|
|
||||||
if (axios.isCancel(err)) {
|
|
||||||
throw new RequestCanceledError(err.message)
|
|
||||||
} else {
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((resp: AxiosResponse<T>) => {
|
|
||||||
const res: Response<T> = {
|
|
||||||
data: resp.data,
|
|
||||||
status: resp.status,
|
|
||||||
statusText: resp.statusText,
|
|
||||||
headers: resp.headers
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PATCH request to mastodon REST API for multipart.
|
|
||||||
* @param path relative path from baseUrl
|
|
||||||
* @param params Form data. If you want to post file, please use FormData()
|
|
||||||
* @param headers Request header object
|
|
||||||
*/
|
|
||||||
public async patchForm<T>(path: string, params = {}, headers: { [key: string]: string } = {}): Promise<Response<T>> {
|
|
||||||
let options: AxiosRequestConfig = {
|
|
||||||
headers: headers,
|
|
||||||
maxContentLength: Infinity,
|
|
||||||
maxBodyLength: Infinity
|
|
||||||
}
|
|
||||||
if (this.accessToken) {
|
|
||||||
options = objectAssignDeep({}, options, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${this.accessToken}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (this.proxyConfig) {
|
|
||||||
options = Object.assign(options, {
|
|
||||||
httpAgent: proxyAgent(this.proxyConfig),
|
|
||||||
httpsAgent: proxyAgent(this.proxyConfig)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return axios
|
|
||||||
.patchForm<T>(this.baseUrl + path, params, options)
|
|
||||||
.catch((err: Error) => {
|
|
||||||
if (axios.isCancel(err)) {
|
|
||||||
throw new RequestCanceledError(err.message)
|
|
||||||
} else {
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((resp: AxiosResponse<T>) => {
|
|
||||||
const res: Response<T> = {
|
|
||||||
data: resp.data,
|
|
||||||
status: resp.status,
|
|
||||||
statusText: resp.statusText,
|
|
||||||
headers: resp.headers
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POST request to mastodon REST API.
|
|
||||||
* @param path relative path from baseUrl
|
|
||||||
* @param params Form data
|
|
||||||
* @param headers Request header object
|
|
||||||
*/
|
|
||||||
public async post<T>(path: string, params = {}, headers: { [key: string]: string } = {}): Promise<Response<T>> {
|
|
||||||
let options: AxiosRequestConfig = {
|
|
||||||
headers: headers,
|
|
||||||
maxContentLength: Infinity,
|
|
||||||
maxBodyLength: Infinity
|
|
||||||
}
|
|
||||||
if (this.accessToken) {
|
|
||||||
options = objectAssignDeep({}, options, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${this.accessToken}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (this.proxyConfig) {
|
|
||||||
options = Object.assign(options, {
|
|
||||||
httpAgent: proxyAgent(this.proxyConfig),
|
|
||||||
httpsAgent: proxyAgent(this.proxyConfig)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return axios.post<T>(this.baseUrl + path, params, options).then((resp: AxiosResponse<T>) => {
|
|
||||||
const res: Response<T> = {
|
|
||||||
data: resp.data,
|
|
||||||
status: resp.status,
|
|
||||||
statusText: resp.statusText,
|
|
||||||
headers: resp.headers
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POST request to mastodon REST API for multipart.
|
|
||||||
* @param path relative path from baseUrl
|
|
||||||
* @param params Form data
|
|
||||||
* @param headers Request header object
|
|
||||||
*/
|
|
||||||
public async postForm<T>(path: string, params = {}, headers: { [key: string]: string } = {}): Promise<Response<T>> {
|
|
||||||
let options: AxiosRequestConfig = {
|
|
||||||
headers: headers,
|
|
||||||
maxContentLength: Infinity,
|
|
||||||
maxBodyLength: Infinity
|
|
||||||
}
|
|
||||||
if (this.accessToken) {
|
|
||||||
options = objectAssignDeep({}, options, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${this.accessToken}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (this.proxyConfig) {
|
|
||||||
options = Object.assign(options, {
|
|
||||||
httpAgent: proxyAgent(this.proxyConfig),
|
|
||||||
httpsAgent: proxyAgent(this.proxyConfig)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return axios.postForm<T>(this.baseUrl + path, params, options).then((resp: AxiosResponse<T>) => {
|
|
||||||
const res: Response<T> = {
|
|
||||||
data: resp.data,
|
|
||||||
status: resp.status,
|
|
||||||
statusText: resp.statusText,
|
|
||||||
headers: resp.headers
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DELETE request to mastodon REST API.
|
|
||||||
* @param path relative path from baseUrl
|
|
||||||
* @param params Form data
|
|
||||||
* @param headers Request header object
|
|
||||||
*/
|
|
||||||
public async del<T>(path: string, params = {}, headers: { [key: string]: string } = {}): Promise<Response<T>> {
|
|
||||||
let options: AxiosRequestConfig = {
|
|
||||||
data: params,
|
|
||||||
headers: headers,
|
|
||||||
maxContentLength: Infinity,
|
|
||||||
maxBodyLength: Infinity
|
|
||||||
}
|
|
||||||
if (this.accessToken) {
|
|
||||||
options = objectAssignDeep({}, options, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${this.accessToken}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (this.proxyConfig) {
|
|
||||||
options = Object.assign(options, {
|
|
||||||
httpAgent: proxyAgent(this.proxyConfig),
|
|
||||||
httpsAgent: proxyAgent(this.proxyConfig)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return axios
|
|
||||||
.delete(this.baseUrl + path, options)
|
|
||||||
.catch((err: Error) => {
|
|
||||||
if (axios.isCancel(err)) {
|
|
||||||
throw new RequestCanceledError(err.message)
|
|
||||||
} else {
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((resp: AxiosResponse) => {
|
|
||||||
const res: Response<T> = {
|
|
||||||
data: resp.data,
|
|
||||||
status: resp.status,
|
|
||||||
statusText: resp.statusText,
|
|
||||||
headers: resp.headers
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancel all requests in this instance.
|
|
||||||
* @returns void
|
|
||||||
*/
|
|
||||||
public cancel(): void {
|
|
||||||
return this.abortController.abort()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get connection and receive websocket connection for Pleroma API.
|
|
||||||
*
|
|
||||||
* @param path relative path from baseUrl: normally it is `/streaming`.
|
|
||||||
* @param stream Stream name, please refer: https://git.pleroma.social/pleroma/pleroma/blob/develop/lib/pleroma/web/mastodon_api/mastodon_socket.ex#L19-28
|
|
||||||
* @returns WebSocket, which inherits from EventEmitter
|
|
||||||
*/
|
|
||||||
public socket(path: string, stream: string, params?: string): WebSocket {
|
|
||||||
if (!this.accessToken) {
|
|
||||||
throw new Error('accessToken is required')
|
|
||||||
}
|
|
||||||
const url = this.baseUrl + path
|
|
||||||
const streaming = new WebSocket(url, stream, params, this.accessToken, this.userAgent, this.proxyConfig)
|
|
||||||
process.nextTick(() => {
|
|
||||||
streaming.start()
|
|
||||||
})
|
|
||||||
return streaming
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace Entity {
|
|
||||||
export type Account = FriendicaEntity.Account
|
|
||||||
export type Activity = FriendicaEntity.Activity
|
|
||||||
export type Application = FriendicaEntity.Application
|
|
||||||
export type AsyncAttachment = FriendicaEntity.AsyncAttachment
|
|
||||||
export type Attachment = FriendicaEntity.Attachment
|
|
||||||
export type Card = FriendicaEntity.Card
|
|
||||||
export type Context = FriendicaEntity.Context
|
|
||||||
export type Conversation = FriendicaEntity.Conversation
|
|
||||||
export type Emoji = FriendicaEntity.Emoji
|
|
||||||
export type FeaturedTag = FriendicaEntity.FeaturedTag
|
|
||||||
export type Field = FriendicaEntity.Field
|
|
||||||
export type Filter = FriendicaEntity.Filter
|
|
||||||
export type FollowRequest = FriendicaEntity.FollowRequest
|
|
||||||
export type History = FriendicaEntity.History
|
|
||||||
export type IdentityProof = FriendicaEntity.IdentityProof
|
|
||||||
export type Instance = FriendicaEntity.Instance
|
|
||||||
export type List = FriendicaEntity.List
|
|
||||||
export type Marker = FriendicaEntity.Marker
|
|
||||||
export type Mention = FriendicaEntity.Mention
|
|
||||||
export type Notification = FriendicaEntity.Notification
|
|
||||||
export type Poll = FriendicaEntity.Poll
|
|
||||||
export type PollOption = FriendicaEntity.PollOption
|
|
||||||
export type Preferences = FriendicaEntity.Preferences
|
|
||||||
export type PushSubscription = FriendicaEntity.PushSubscription
|
|
||||||
export type Relationship = FriendicaEntity.Relationship
|
|
||||||
export type Report = FriendicaEntity.Report
|
|
||||||
export type Results = FriendicaEntity.Results
|
|
||||||
export type ScheduledStatus = FriendicaEntity.ScheduledStatus
|
|
||||||
export type Source = FriendicaEntity.Source
|
|
||||||
export type Stats = FriendicaEntity.Stats
|
|
||||||
export type Status = FriendicaEntity.Status
|
|
||||||
export type StatusParams = FriendicaEntity.StatusParams
|
|
||||||
export type StatusSource = FriendicaEntity.StatusSource
|
|
||||||
export type Tag = FriendicaEntity.Tag
|
|
||||||
export type Token = FriendicaEntity.Token
|
|
||||||
export type URLs = FriendicaEntity.URLs
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace Converter {
|
|
||||||
export const encodeNotificationType = (
|
|
||||||
t: MegalodonEntity.NotificationType
|
|
||||||
): FriendicaEntity.NotificationType | UnknownNotificationTypeError => {
|
|
||||||
switch (t) {
|
|
||||||
case NotificationType.Follow:
|
|
||||||
return FriendicaNotificationType.Follow
|
|
||||||
case NotificationType.Favourite:
|
|
||||||
return FriendicaNotificationType.Favourite
|
|
||||||
case NotificationType.Reblog:
|
|
||||||
return FriendicaNotificationType.Reblog
|
|
||||||
case NotificationType.Mention:
|
|
||||||
return FriendicaNotificationType.Mention
|
|
||||||
case NotificationType.FollowRequest:
|
|
||||||
return FriendicaNotificationType.FollowRequest
|
|
||||||
case NotificationType.Status:
|
|
||||||
return FriendicaNotificationType.Status
|
|
||||||
case NotificationType.PollExpired:
|
|
||||||
return FriendicaNotificationType.Poll
|
|
||||||
case NotificationType.Update:
|
|
||||||
return FriendicaNotificationType.Update
|
|
||||||
default:
|
|
||||||
return new UnknownNotificationTypeError()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const decodeNotificationType = (
|
|
||||||
t: FriendicaEntity.NotificationType
|
|
||||||
): MegalodonEntity.NotificationType | UnknownNotificationTypeError => {
|
|
||||||
switch (t) {
|
|
||||||
case FriendicaNotificationType.Follow:
|
|
||||||
return NotificationType.Follow
|
|
||||||
case FriendicaNotificationType.Favourite:
|
|
||||||
return NotificationType.Favourite
|
|
||||||
case FriendicaNotificationType.Mention:
|
|
||||||
return NotificationType.Mention
|
|
||||||
case FriendicaNotificationType.Reblog:
|
|
||||||
return NotificationType.Reblog
|
|
||||||
case FriendicaNotificationType.FollowRequest:
|
|
||||||
return NotificationType.FollowRequest
|
|
||||||
case FriendicaNotificationType.Status:
|
|
||||||
return NotificationType.Status
|
|
||||||
case FriendicaNotificationType.Poll:
|
|
||||||
return NotificationType.PollExpired
|
|
||||||
case FriendicaNotificationType.Update:
|
|
||||||
return NotificationType.Update
|
|
||||||
default:
|
|
||||||
return new UnknownNotificationTypeError()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const account = (a: Entity.Account): MegalodonEntity.Account => ({
|
|
||||||
id: a.id,
|
|
||||||
username: a.username,
|
|
||||||
acct: a.acct,
|
|
||||||
display_name: a.display_name,
|
|
||||||
locked: a.locked,
|
|
||||||
discoverable: a.discoverable,
|
|
||||||
group: a.group,
|
|
||||||
noindex: null,
|
|
||||||
suspended: null,
|
|
||||||
limited: null,
|
|
||||||
created_at: a.created_at,
|
|
||||||
followers_count: a.followers_count,
|
|
||||||
following_count: a.following_count,
|
|
||||||
statuses_count: a.statuses_count,
|
|
||||||
note: a.note,
|
|
||||||
url: a.url,
|
|
||||||
avatar: a.avatar,
|
|
||||||
avatar_static: a.avatar_static,
|
|
||||||
header: a.header,
|
|
||||||
header_static: a.header_static,
|
|
||||||
emojis: a.emojis.map(e => emoji(e)),
|
|
||||||
moved: a.moved ? account(a.moved) : null,
|
|
||||||
fields: a.fields.map(f => field(f)),
|
|
||||||
bot: a.bot,
|
|
||||||
source: a.source ? source(a.source) : undefined
|
|
||||||
})
|
|
||||||
export const activity = (a: Entity.Activity): MegalodonEntity.Activity => a
|
|
||||||
export const application = (a: Entity.Application): MegalodonEntity.Application => a
|
|
||||||
export const attachment = (a: Entity.Attachment): MegalodonEntity.Attachment => a
|
|
||||||
export const async_attachment = (a: Entity.AsyncAttachment) => {
|
|
||||||
if (a.url) {
|
|
||||||
return {
|
|
||||||
id: a.id,
|
|
||||||
type: a.type,
|
|
||||||
url: a.url,
|
|
||||||
remote_url: a.remote_url,
|
|
||||||
preview_url: a.preview_url,
|
|
||||||
text_url: a.text_url,
|
|
||||||
meta: a.meta,
|
|
||||||
description: a.description,
|
|
||||||
blurhash: a.blurhash
|
|
||||||
} as MegalodonEntity.Attachment
|
|
||||||
} else {
|
|
||||||
return a as MegalodonEntity.AsyncAttachment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export const card = (c: Entity.Card): MegalodonEntity.Card => ({
|
|
||||||
url: c.url,
|
|
||||||
title: c.title,
|
|
||||||
description: c.description,
|
|
||||||
type: c.type,
|
|
||||||
image: c.image,
|
|
||||||
author_name: c.author_name,
|
|
||||||
author_url: c.author_url,
|
|
||||||
provider_name: c.provider_name,
|
|
||||||
provider_url: c.provider_url,
|
|
||||||
html: c.html,
|
|
||||||
width: c.width,
|
|
||||||
height: c.height,
|
|
||||||
embed_url: null,
|
|
||||||
blurhash: c.blurhash
|
|
||||||
})
|
|
||||||
export const context = (c: Entity.Context): MegalodonEntity.Context => ({
|
|
||||||
ancestors: Array.isArray(c.ancestors) ? c.ancestors.map(a => status(a)) : [],
|
|
||||||
descendants: Array.isArray(c.descendants) ? c.descendants.map(d => status(d)) : []
|
|
||||||
})
|
|
||||||
export const conversation = (c: Entity.Conversation): MegalodonEntity.Conversation => ({
|
|
||||||
id: c.id,
|
|
||||||
accounts: Array.isArray(c.accounts) ? c.accounts.map(a => account(a)) : [],
|
|
||||||
last_status: c.last_status ? status(c.last_status) : null,
|
|
||||||
unread: c.unread
|
|
||||||
})
|
|
||||||
export const emoji = (e: Entity.Emoji): MegalodonEntity.Emoji => ({
|
|
||||||
shortcode: e.shortcode,
|
|
||||||
static_url: e.static_url,
|
|
||||||
url: e.url,
|
|
||||||
visible_in_picker: e.visible_in_picker
|
|
||||||
})
|
|
||||||
export const featured_tag = (e: Entity.FeaturedTag): MegalodonEntity.FeaturedTag => e
|
|
||||||
export const field = (f: Entity.Field): MegalodonEntity.Field => f
|
|
||||||
export const filter = (f: Entity.Filter): MegalodonEntity.Filter => f
|
|
||||||
export const follow_request = (f: Entity.FollowRequest): MegalodonEntity.FollowRequest => ({
|
|
||||||
id: f.id,
|
|
||||||
username: f.username,
|
|
||||||
acct: f.acct,
|
|
||||||
display_name: f.display_name,
|
|
||||||
locked: f.locked,
|
|
||||||
bot: f.bot,
|
|
||||||
discoverable: f.discoverable,
|
|
||||||
group: f.group,
|
|
||||||
created_at: f.created_at,
|
|
||||||
note: f.note,
|
|
||||||
url: f.url,
|
|
||||||
avatar: f.avatar,
|
|
||||||
avatar_static: f.avatar_static,
|
|
||||||
header: f.header,
|
|
||||||
header_static: f.header_static,
|
|
||||||
followers_count: f.followers_count,
|
|
||||||
following_count: f.following_count,
|
|
||||||
statuses_count: f.statuses_count,
|
|
||||||
emojis: f.emojis.map(e => emoji(e)),
|
|
||||||
fields: f.fields.map(f => field(f))
|
|
||||||
})
|
|
||||||
export const history = (h: Entity.History): MegalodonEntity.History => h
|
|
||||||
export const identity_proof = (i: Entity.IdentityProof): MegalodonEntity.IdentityProof => i
|
|
||||||
export const instance = (i: Entity.Instance): MegalodonEntity.Instance => {
|
|
||||||
return {
|
|
||||||
uri: i.uri,
|
|
||||||
title: i.title,
|
|
||||||
description: i.description,
|
|
||||||
email: i.email,
|
|
||||||
version: i.version,
|
|
||||||
thumbnail: i.thumbnail,
|
|
||||||
urls: i.urls ? urls(i.urls) : null,
|
|
||||||
stats: stats(i.stats),
|
|
||||||
languages: i.languages,
|
|
||||||
registrations: i.registrations,
|
|
||||||
approval_required: i.approval_required,
|
|
||||||
invites_enabled: i.invites_enabled,
|
|
||||||
configuration: {
|
|
||||||
statuses: {
|
|
||||||
max_characters: i.max_toot_chars
|
|
||||||
}
|
|
||||||
},
|
|
||||||
contact_account: account(i.contact_account),
|
|
||||||
rules: i.rules
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export const list = (l: Entity.List): MegalodonEntity.List => l
|
|
||||||
export const marker = (m: Entity.Marker): MegalodonEntity.Marker => m
|
|
||||||
export const mention = (m: Entity.Mention): MegalodonEntity.Mention => m
|
|
||||||
export const notification = (n: Entity.Notification): MegalodonEntity.Notification | UnknownNotificationTypeError => {
|
|
||||||
const notificationType = decodeNotificationType(n.type)
|
|
||||||
if (notificationType instanceof UnknownNotificationTypeError) return notificationType
|
|
||||||
if (n.status) {
|
|
||||||
return {
|
|
||||||
account: account(n.account),
|
|
||||||
created_at: n.created_at,
|
|
||||||
id: n.id,
|
|
||||||
status: status(n.status),
|
|
||||||
type: notificationType
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
account: account(n.account),
|
|
||||||
created_at: n.created_at,
|
|
||||||
id: n.id,
|
|
||||||
type: notificationType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export const poll = (p: Entity.Poll): MegalodonEntity.Poll => p
|
|
||||||
export const poll_option = (p: Entity.PollOption): MegalodonEntity.PollOption => p
|
|
||||||
export const preferences = (p: Entity.Preferences): MegalodonEntity.Preferences => p
|
|
||||||
export const push_subscription = (p: Entity.PushSubscription): MegalodonEntity.PushSubscription => p
|
|
||||||
export const relationship = (r: Entity.Relationship): MegalodonEntity.Relationship => r
|
|
||||||
export const report = (r: Entity.Report): MegalodonEntity.Report => ({
|
|
||||||
id: r.id,
|
|
||||||
action_taken: r.action_taken,
|
|
||||||
action_taken_at: null,
|
|
||||||
category: r.category,
|
|
||||||
comment: r.comment,
|
|
||||||
forwarded: r.forwarded,
|
|
||||||
status_ids: r.status_ids,
|
|
||||||
rule_ids: r.rule_ids,
|
|
||||||
target_account: account(r.target_account)
|
|
||||||
})
|
|
||||||
export const results = (r: Entity.Results): MegalodonEntity.Results => ({
|
|
||||||
accounts: Array.isArray(r.accounts) ? r.accounts.map(a => account(a)) : [],
|
|
||||||
statuses: Array.isArray(r.statuses) ? r.statuses.map(s => status(s)) : [],
|
|
||||||
hashtags: Array.isArray(r.hashtags) ? r.hashtags.map(h => tag(h)) : []
|
|
||||||
})
|
|
||||||
export const scheduled_status = (s: Entity.ScheduledStatus): MegalodonEntity.ScheduledStatus => {
|
|
||||||
return {
|
|
||||||
id: s.id,
|
|
||||||
scheduled_at: s.scheduled_at,
|
|
||||||
params: status_params(s.params),
|
|
||||||
media_attachments: s.media_attachments ? s.media_attachments.map(a => attachment(a)) : null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export const source = (s: Entity.Source): MegalodonEntity.Source => s
|
|
||||||
export const stats = (s: Entity.Stats): MegalodonEntity.Stats => s
|
|
||||||
export const status = (s: Entity.Status): MegalodonEntity.Status => ({
|
|
||||||
id: s.id,
|
|
||||||
uri: s.uri,
|
|
||||||
url: s.url,
|
|
||||||
account: account(s.account),
|
|
||||||
in_reply_to_id: s.in_reply_to_id,
|
|
||||||
in_reply_to_account_id: s.in_reply_to_account_id,
|
|
||||||
reblog: s.reblog ? status(s.reblog) : s.quote ? status(s.quote) : null,
|
|
||||||
content: s.content,
|
|
||||||
plain_content: null,
|
|
||||||
created_at: s.created_at,
|
|
||||||
emojis: Array.isArray(s.emojis) ? s.emojis.map(e => emoji(e)) : [],
|
|
||||||
replies_count: s.replies_count,
|
|
||||||
reblogs_count: s.reblogs_count,
|
|
||||||
favourites_count: s.favourites_count,
|
|
||||||
reblogged: s.reblogged,
|
|
||||||
favourited: s.favourited,
|
|
||||||
muted: s.muted,
|
|
||||||
sensitive: s.sensitive,
|
|
||||||
spoiler_text: s.spoiler_text,
|
|
||||||
visibility: s.visibility,
|
|
||||||
media_attachments: Array.isArray(s.media_attachments) ? s.media_attachments.map(m => attachment(m)) : [],
|
|
||||||
mentions: Array.isArray(s.mentions) ? s.mentions.map(m => mention(m)) : [],
|
|
||||||
tags: s.tags,
|
|
||||||
card: s.card ? card(s.card) : null,
|
|
||||||
poll: s.poll ? poll(s.poll) : null,
|
|
||||||
application: s.application ? application(s.application) : null,
|
|
||||||
language: s.language,
|
|
||||||
pinned: s.pinned,
|
|
||||||
emoji_reactions: [],
|
|
||||||
bookmarked: s.bookmarked ? s.bookmarked : false,
|
|
||||||
quote: false
|
|
||||||
})
|
|
||||||
export const status_params = (s: Entity.StatusParams): MegalodonEntity.StatusParams => {
|
|
||||||
return {
|
|
||||||
text: s.text,
|
|
||||||
in_reply_to_id: s.in_reply_to_id,
|
|
||||||
media_ids: s.media_ids,
|
|
||||||
sensitive: s.sensitive,
|
|
||||||
spoiler_text: s.spoiler_text,
|
|
||||||
visibility: s.visibility,
|
|
||||||
scheduled_at: s.scheduled_at,
|
|
||||||
application_id: parseInt(s.application_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export const status_source = (s: Entity.StatusSource): MegalodonEntity.StatusSource => s
|
|
||||||
export const tag = (t: Entity.Tag): MegalodonEntity.Tag => t
|
|
||||||
export const token = (t: Entity.Token): MegalodonEntity.Token => t
|
|
||||||
export const urls = (u: Entity.URLs): MegalodonEntity.URLs => u
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default FriendicaAPI
|
|
|
@ -1,29 +0,0 @@
|
||||||
/// <reference path="emoji.ts" />
|
|
||||||
/// <reference path="source.ts" />
|
|
||||||
/// <reference path="field.ts" />
|
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Account = {
|
|
||||||
id: string
|
|
||||||
username: string
|
|
||||||
acct: string
|
|
||||||
display_name: string
|
|
||||||
locked: boolean
|
|
||||||
discoverable?: boolean
|
|
||||||
group: boolean | null
|
|
||||||
created_at: string
|
|
||||||
followers_count: number
|
|
||||||
following_count: number
|
|
||||||
statuses_count: number
|
|
||||||
note: string
|
|
||||||
url: string
|
|
||||||
avatar: string
|
|
||||||
avatar_static: string
|
|
||||||
header: string
|
|
||||||
header_static: string
|
|
||||||
emojis: Array<Emoji>
|
|
||||||
moved: Account | null
|
|
||||||
fields: Array<Field>
|
|
||||||
bot: boolean
|
|
||||||
source?: Source
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Activity = {
|
|
||||||
week: string
|
|
||||||
statuses: string
|
|
||||||
logins: string
|
|
||||||
registrations: string
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Application = {
|
|
||||||
name: string
|
|
||||||
website?: string | null
|
|
||||||
vapid_key?: string | null
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
/// <reference path="attachment.ts" />
|
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type AsyncAttachment = {
|
|
||||||
id: string
|
|
||||||
type: 'unknown' | 'image' | 'gifv' | 'video' | 'audio'
|
|
||||||
url: string | null
|
|
||||||
remote_url: string | null
|
|
||||||
preview_url: string
|
|
||||||
text_url: string | null
|
|
||||||
meta: Meta | null
|
|
||||||
description: string | null
|
|
||||||
blurhash: string | null
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Sub = {
|
|
||||||
// For Image, Gifv, and Video
|
|
||||||
width?: number
|
|
||||||
height?: number
|
|
||||||
size?: string
|
|
||||||
aspect?: number
|
|
||||||
|
|
||||||
// For Gifv and Video
|
|
||||||
frame_rate?: string
|
|
||||||
|
|
||||||
// For Audio, Gifv, and Video
|
|
||||||
duration?: number
|
|
||||||
bitrate?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Focus = {
|
|
||||||
x: number
|
|
||||||
y: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Meta = {
|
|
||||||
original?: Sub
|
|
||||||
small?: Sub
|
|
||||||
focus?: Focus
|
|
||||||
length?: string
|
|
||||||
duration?: number
|
|
||||||
fps?: number
|
|
||||||
size?: string
|
|
||||||
width?: number
|
|
||||||
height?: number
|
|
||||||
aspect?: number
|
|
||||||
audio_encode?: string
|
|
||||||
audio_bitrate?: string
|
|
||||||
audio_channel?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Attachment = {
|
|
||||||
id: string
|
|
||||||
type: 'unknown' | 'image' | 'gifv' | 'video' | 'audio'
|
|
||||||
url: string
|
|
||||||
remote_url: string | null
|
|
||||||
preview_url: string | null
|
|
||||||
text_url: string | null
|
|
||||||
meta: Meta | null
|
|
||||||
description: string | null
|
|
||||||
blurhash: string | null
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Card = {
|
|
||||||
url: string
|
|
||||||
title: string
|
|
||||||
description: string
|
|
||||||
type: 'link' | 'photo' | 'video' | 'rich'
|
|
||||||
image: string | null
|
|
||||||
author_name: string
|
|
||||||
author_url: string
|
|
||||||
provider_name: string
|
|
||||||
provider_url: string
|
|
||||||
html: string
|
|
||||||
width: number
|
|
||||||
height: number
|
|
||||||
blurhash: string | null
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
/// <reference path="status.ts" />
|
|
||||||
|
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Context = {
|
|
||||||
ancestors: Array<Status>
|
|
||||||
descendants: Array<Status>
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
/// <reference path="account.ts" />
|
|
||||||
/// <reference path="status.ts" />
|
|
||||||
|
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Conversation = {
|
|
||||||
id: string
|
|
||||||
accounts: Array<Account>
|
|
||||||
last_status: Status | null
|
|
||||||
unread: boolean
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Emoji = {
|
|
||||||
shortcode: string
|
|
||||||
static_url: string
|
|
||||||
url: string
|
|
||||||
visible_in_picker: boolean
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type FeaturedTag = {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
statuses_count: number
|
|
||||||
last_status_at: string
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Field = {
|
|
||||||
name: string
|
|
||||||
value: string
|
|
||||||
verified_at: string | null
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Filter = {
|
|
||||||
id: string
|
|
||||||
phrase: string
|
|
||||||
context: Array<FilterContext>
|
|
||||||
expires_at: string | null
|
|
||||||
irreversible: boolean
|
|
||||||
whole_word: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FilterContext = string
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
/// <reference path="emoji.ts" />
|
|
||||||
/// <reference path="field.ts" />
|
|
||||||
|
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type FollowRequest = {
|
|
||||||
id: number
|
|
||||||
username: string
|
|
||||||
acct: string
|
|
||||||
display_name: string
|
|
||||||
locked: boolean
|
|
||||||
bot: boolean
|
|
||||||
discoverable?: boolean
|
|
||||||
group: boolean
|
|
||||||
created_at: string
|
|
||||||
note: string
|
|
||||||
url: string
|
|
||||||
avatar: string
|
|
||||||
avatar_static: string
|
|
||||||
header: string
|
|
||||||
header_static: string
|
|
||||||
followers_count: number
|
|
||||||
following_count: number
|
|
||||||
statuses_count: number
|
|
||||||
emojis: Array<Emoji>
|
|
||||||
fields: Array<Field>
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type History = {
|
|
||||||
day: string
|
|
||||||
uses: number
|
|
||||||
accounts: number
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type IdentityProof = {
|
|
||||||
provider: string
|
|
||||||
provider_username: string
|
|
||||||
updated_at: string
|
|
||||||
proof_url: string
|
|
||||||
profile_url: string
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
/// <reference path="account.ts" />
|
|
||||||
/// <reference path="urls.ts" />
|
|
||||||
/// <reference path="stats.ts" />
|
|
||||||
|
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Instance = {
|
|
||||||
uri: string
|
|
||||||
title: string
|
|
||||||
description: string
|
|
||||||
email: string
|
|
||||||
version: string
|
|
||||||
thumbnail: string | null
|
|
||||||
urls: URLs | null
|
|
||||||
stats: Stats
|
|
||||||
languages: Array<string>
|
|
||||||
registrations: boolean
|
|
||||||
approval_required: boolean
|
|
||||||
invites_enabled: boolean
|
|
||||||
max_toot_chars: number
|
|
||||||
contact_account: Account
|
|
||||||
rules: Array<InstanceRule>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type InstanceRule = {
|
|
||||||
id: string
|
|
||||||
text: string
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type List = {
|
|
||||||
id: string
|
|
||||||
title: string
|
|
||||||
replies_policy: RepliesPolicy
|
|
||||||
}
|
|
||||||
|
|
||||||
export type RepliesPolicy = 'followed' | 'list' | 'none'
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Marker = {
|
|
||||||
home: {
|
|
||||||
last_read_id: string
|
|
||||||
version: number
|
|
||||||
updated_at: string
|
|
||||||
}
|
|
||||||
notifications: {
|
|
||||||
last_read_id: string
|
|
||||||
version: number
|
|
||||||
updated_at: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Mention = {
|
|
||||||
id: string
|
|
||||||
username: string
|
|
||||||
url: string
|
|
||||||
acct: string
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
/// <reference path="account.ts" />
|
|
||||||
/// <reference path="status.ts" />
|
|
||||||
|
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Notification = {
|
|
||||||
account: Account
|
|
||||||
created_at: string
|
|
||||||
id: string
|
|
||||||
status?: Status
|
|
||||||
type: NotificationType
|
|
||||||
}
|
|
||||||
|
|
||||||
export type NotificationType = string
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
/// <reference path="poll_option.ts" />
|
|
||||||
|
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Poll = {
|
|
||||||
id: string
|
|
||||||
expires_at: string | null
|
|
||||||
expired: boolean
|
|
||||||
multiple: boolean
|
|
||||||
votes_count: number
|
|
||||||
options: Array<PollOption>
|
|
||||||
voted: boolean
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type PollOption = {
|
|
||||||
title: string
|
|
||||||
votes_count: number | null
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Preferences = {
|
|
||||||
'posting:default:visibility': 'public' | 'unlisted' | 'private' | 'direct'
|
|
||||||
'posting:default:sensitive': boolean
|
|
||||||
'posting:default:language': string | null
|
|
||||||
'reading:expand:media': 'default' | 'show_all' | 'hide_all'
|
|
||||||
'reading:expand:spoilers': boolean
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Alerts = {
|
|
||||||
follow: boolean
|
|
||||||
favourite: boolean
|
|
||||||
mention: boolean
|
|
||||||
reblog: boolean
|
|
||||||
poll: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PushSubscription = {
|
|
||||||
id: string
|
|
||||||
endpoint: string
|
|
||||||
server_key: string
|
|
||||||
alerts: Alerts
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Relationship = {
|
|
||||||
id: string
|
|
||||||
following: boolean
|
|
||||||
followed_by: boolean
|
|
||||||
blocking: boolean
|
|
||||||
blocked_by: boolean
|
|
||||||
muting: boolean
|
|
||||||
muting_notifications: boolean
|
|
||||||
requested: boolean
|
|
||||||
domain_blocking: boolean
|
|
||||||
showing_reblogs: boolean
|
|
||||||
endorsed: boolean
|
|
||||||
notifying: boolean
|
|
||||||
note: string | null
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
/// <reference path="account.ts" />
|
|
||||||
|
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Report = {
|
|
||||||
id: string
|
|
||||||
action_taken: boolean
|
|
||||||
category: Category
|
|
||||||
comment: string
|
|
||||||
forwarded: boolean
|
|
||||||
status_ids: Array<string> | null
|
|
||||||
rule_ids: Array<string> | null
|
|
||||||
target_account: Account
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Category = 'spam' | 'violation' | 'other'
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
/// <reference path="account.ts" />
|
|
||||||
/// <reference path="status.ts" />
|
|
||||||
/// <reference path="tag.ts" />
|
|
||||||
|
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Results = {
|
|
||||||
accounts: Array<Account>
|
|
||||||
statuses: Array<Status>
|
|
||||||
hashtags: Array<Tag>
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
/// <reference path="attachment.ts" />
|
|
||||||
/// <reference path="status_params.ts" />
|
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type ScheduledStatus = {
|
|
||||||
id: string
|
|
||||||
scheduled_at: string
|
|
||||||
params: StatusParams
|
|
||||||
media_attachments: Array<Attachment>
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
/// <reference path="field.ts" />
|
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Source = {
|
|
||||||
privacy: string | null
|
|
||||||
sensitive: boolean | null
|
|
||||||
language: string | null
|
|
||||||
note: string
|
|
||||||
fields: Array<Field>
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Stats = {
|
|
||||||
user_count: number
|
|
||||||
status_count: number
|
|
||||||
domain_count: number
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
/// <reference path="account.ts" />
|
|
||||||
/// <reference path="application.ts" />
|
|
||||||
/// <reference path="mention.ts" />
|
|
||||||
/// <reference path="attachment.ts" />
|
|
||||||
/// <reference path="emoji.ts" />
|
|
||||||
/// <reference path="card.ts" />
|
|
||||||
/// <reference path="poll.ts" />
|
|
||||||
|
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Status = {
|
|
||||||
id: string
|
|
||||||
uri: string
|
|
||||||
url: string
|
|
||||||
account: Account
|
|
||||||
in_reply_to_id: string | null
|
|
||||||
in_reply_to_account_id: string | null
|
|
||||||
reblog: Status | null
|
|
||||||
content: string
|
|
||||||
created_at: string
|
|
||||||
emojis: Emoji[]
|
|
||||||
replies_count: number
|
|
||||||
reblogs_count: number
|
|
||||||
favourites_count: number
|
|
||||||
reblogged: boolean | null
|
|
||||||
favourited: boolean | null
|
|
||||||
muted: boolean | null
|
|
||||||
sensitive: boolean
|
|
||||||
spoiler_text: string
|
|
||||||
visibility: 'public' | 'unlisted' | 'private' | 'direct'
|
|
||||||
media_attachments: Array<Attachment>
|
|
||||||
mentions: Array<Mention>
|
|
||||||
tags: Array<StatusTag>
|
|
||||||
card: Card | null
|
|
||||||
poll: Poll | null
|
|
||||||
application: Application | null
|
|
||||||
language: string | null
|
|
||||||
pinned: boolean | null
|
|
||||||
bookmarked?: boolean
|
|
||||||
// These parameters are unique parameters in fedibird.com for quote.
|
|
||||||
quote_id?: string
|
|
||||||
quote?: Status | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type StatusTag = {
|
|
||||||
name: string
|
|
||||||
url: string
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type StatusParams = {
|
|
||||||
text: string
|
|
||||||
in_reply_to_id: string | null
|
|
||||||
media_ids: Array<string> | null
|
|
||||||
sensitive: boolean | null
|
|
||||||
spoiler_text: string | null
|
|
||||||
visibility: 'public' | 'unlisted' | 'private' | null
|
|
||||||
scheduled_at: string | null
|
|
||||||
application_id: string
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type StatusSource = {
|
|
||||||
id: string
|
|
||||||
text: string
|
|
||||||
spoiler_text: string
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
/// <reference path="history.ts" />
|
|
||||||
|
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Tag = {
|
|
||||||
name: string
|
|
||||||
url: string
|
|
||||||
history: Array<History>
|
|
||||||
following?: boolean
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace FriendicaEntity {
|
|
||||||
export type Token = {
|
|
||||||
access_token: string
|
|
||||||
token_type: string
|
|
||||||
scope: string
|
|
||||||
created_at: number
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue