Merge remote-tracking branch 'upstream/stable' into nodeinfostats
This commit is contained in:
commit
9adbb114c8
508 changed files with 30187 additions and 14010 deletions
|
|
@ -30,14 +30,14 @@ import type { MiNote } from '@/models/Note.js';
|
|||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IActivity } from '@/core/activitypub/type.js';
|
||||
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||
import * as Acct from '@/misc/acct.js';
|
||||
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify';
|
||||
import type { FindOptionsWhere } from 'typeorm';
|
||||
import type Logger from '@/logger.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
|
||||
const ACTIVITY_JSON = 'application/activity+json; charset=utf-8';
|
||||
const LD_JSON = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8';
|
||||
|
|
@ -103,15 +103,16 @@ export class ActivityPubServerService {
|
|||
/**
|
||||
* Pack Create<Note> or Announce Activity
|
||||
* @param note Note
|
||||
* @param author Author of the note
|
||||
*/
|
||||
@bindThis
|
||||
private async packActivity(note: MiNote): Promise<any> {
|
||||
private async packActivity(note: MiNote, author: MiUser): Promise<any> {
|
||||
if (isRenote(note) && !isQuote(note)) {
|
||||
const renote = await this.notesRepository.findOneByOrFail({ id: note.renoteId });
|
||||
return this.apRendererService.renderAnnounce(renote.uri ? renote.uri : `${this.config.url}/notes/${renote.id}`, note);
|
||||
}
|
||||
|
||||
return this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note);
|
||||
return this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, author, false), note);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
|
@ -506,7 +507,7 @@ export class ActivityPubServerService {
|
|||
this.notesRepository.findOneByOrFail({ id: pining.noteId }))))
|
||||
.filter(note => !note.localOnly && ['public', 'home'].includes(note.visibility));
|
||||
|
||||
const renderedNotes = await Promise.all(pinnedNotes.map(note => this.apRendererService.renderNote(note)));
|
||||
const renderedNotes = await Promise.all(pinnedNotes.map(note => this.apRendererService.renderNote(note, user)));
|
||||
|
||||
const rendered = this.apRendererService.renderOrderedCollection(
|
||||
`${this.config.url}/users/${userId}/collections/featured`,
|
||||
|
|
@ -579,7 +580,7 @@ export class ActivityPubServerService {
|
|||
|
||||
if (sinceId) notes.reverse();
|
||||
|
||||
const activities = await Promise.all(notes.map(note => this.packActivity(note)));
|
||||
const activities = await Promise.all(notes.map(note => this.packActivity(note, user)));
|
||||
const rendered = this.apRendererService.renderOrderedCollectionPage(
|
||||
`${partOf}?${url.query({
|
||||
page: 'true',
|
||||
|
|
@ -653,8 +654,8 @@ export class ActivityPubServerService {
|
|||
},
|
||||
deriveConstraint(request: IncomingMessage) {
|
||||
const accepted = accepts(request).type(['html', ACTIVITY_JSON, LD_JSON]);
|
||||
const isAp = typeof accepted === 'string' && !accepted.match(/html/);
|
||||
return isAp ? 'ap' : 'html';
|
||||
if (accepted === false) return null;
|
||||
return accepted !== 'html' ? 'ap' : 'html';
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -723,7 +724,9 @@ export class ActivityPubServerService {
|
|||
|
||||
if (!this.config.checkActivityPubGetSignature) reply.header('Cache-Control', 'public, max-age=180');
|
||||
this.setResponseType(request, reply);
|
||||
return this.apRendererService.addContext(await this.apRendererService.renderNote(note, false));
|
||||
|
||||
const author = await this.usersRepository.findOneByOrFail({ id: note.userId });
|
||||
return this.apRendererService.addContext(await this.apRendererService.renderNote(note, author, false));
|
||||
});
|
||||
|
||||
// note activity
|
||||
|
|
@ -746,7 +749,9 @@ export class ActivityPubServerService {
|
|||
|
||||
if (!this.config.checkActivityPubGetSignature) reply.header('Cache-Control', 'public, max-age=180');
|
||||
this.setResponseType(request, reply);
|
||||
return (this.apRendererService.addContext(await this.packActivity(note)));
|
||||
|
||||
const author = await this.usersRepository.findOneByOrFail({ id: note.userId });
|
||||
return (this.apRendererService.addContext(await this.packActivity(note, author)));
|
||||
});
|
||||
|
||||
// outbox
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import rename from 'rename';
|
|||
import sharp from 'sharp';
|
||||
import { sharpBmp } from '@misskey-dev/sharp-read-bmp';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { MiDriveFile, DriveFilesRepository } from '@/models/_.js';
|
||||
import type { MiDriveFile, DriveFilesRepository, MiUser } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { createTemp } from '@/misc/create-temp.js';
|
||||
import { FILE_TYPE_BROWSERSAFE } from '@/const.js';
|
||||
|
|
@ -30,8 +30,7 @@ import { correctFilename } from '@/misc/correct-filename.js';
|
|||
import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js';
|
||||
import { getIpHash } from '@/misc/get-ip-hash.js';
|
||||
import { AuthenticateService } from '@/server/api/AuthenticateService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { SkRateLimiterService } from '@/server/api/SkRateLimiterService.js';
|
||||
import { SkRateLimiterService } from '@/server/SkRateLimiterService.js';
|
||||
import { Keyed, RateLimit, sendRateLimitHeaders } from '@/misc/rate-limit-utils.js';
|
||||
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
|
||||
|
||||
|
|
@ -59,7 +58,6 @@ export class FileServerService {
|
|||
private loggerService: LoggerService,
|
||||
private authenticateService: AuthenticateService,
|
||||
private rateLimiterService: SkRateLimiterService,
|
||||
private roleService: RoleService,
|
||||
) {
|
||||
this.logger = this.loggerService.getLogger('server', 'gray');
|
||||
|
||||
|
|
@ -625,14 +623,13 @@ export class FileServerService {
|
|||
|
||||
// koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app.
|
||||
const [user] = await this.authenticateService.authenticate(token);
|
||||
const actor = user?.id ?? getIpHash(request.ip);
|
||||
const factor = user ? (await this.roleService.getUserPolicies(user.id)).rateLimitFactor : 1;
|
||||
const actor = user ?? getIpHash(request.ip);
|
||||
|
||||
// Call both limits: the per-resource limit and the shared cross-resource limit
|
||||
return await this.checkResourceLimit(reply, actor, group, resource, factor) && await this.checkSharedLimit(reply, actor, group, factor);
|
||||
return await this.checkResourceLimit(reply, actor, group, resource) && await this.checkSharedLimit(reply, actor, group);
|
||||
}
|
||||
|
||||
private async checkResourceLimit(reply: FastifyReply, actor: string, group: string, resource: string, factor = 1): Promise<boolean> {
|
||||
private async checkResourceLimit(reply: FastifyReply, actor: string | MiUser, group: string, resource: string): Promise<boolean> {
|
||||
const limit: Keyed<RateLimit> = {
|
||||
// Group by resource
|
||||
key: `${group}${resource}`,
|
||||
|
|
@ -643,10 +640,10 @@ export class FileServerService {
|
|||
dripRate: 1000 * 60,
|
||||
};
|
||||
|
||||
return await this.checkLimit(reply, actor, limit, factor);
|
||||
return await this.checkLimit(reply, actor, limit);
|
||||
}
|
||||
|
||||
private async checkSharedLimit(reply: FastifyReply, actor: string, group: string, factor = 1): Promise<boolean> {
|
||||
private async checkSharedLimit(reply: FastifyReply, actor: string | MiUser, group: string): Promise<boolean> {
|
||||
const limit: Keyed<RateLimit> = {
|
||||
key: group,
|
||||
type: 'bucket',
|
||||
|
|
@ -655,11 +652,11 @@ export class FileServerService {
|
|||
size: 3600,
|
||||
};
|
||||
|
||||
return await this.checkLimit(reply, actor, limit, factor);
|
||||
return await this.checkLimit(reply, actor, limit);
|
||||
}
|
||||
|
||||
private async checkLimit(reply: FastifyReply, actor: string, limit: Keyed<RateLimit>, factor = 1): Promise<boolean> {
|
||||
const info = await this.rateLimiterService.limit(limit, actor, factor);
|
||||
private async checkLimit(reply: FastifyReply, actor: string | MiUser, limit: Keyed<RateLimit>): Promise<boolean> {
|
||||
const info = await this.rateLimiterService.limit(limit, actor);
|
||||
|
||||
sendRateLimitHeaders(reply, info);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { EndpointsModule } from '@/server/api/EndpointsModule.js';
|
||||
import { CoreModule } from '@/core/CoreModule.js';
|
||||
import { SkRateLimiterService } from '@/server/api/SkRateLimiterService.js';
|
||||
import { SkRateLimiterService } from '@/server/SkRateLimiterService.js';
|
||||
import { ApiCallService } from './api/ApiCallService.js';
|
||||
import { FileServerService } from './FileServerService.js';
|
||||
import { HealthServerService } from './HealthServerService.js';
|
||||
|
|
@ -27,6 +27,8 @@ import { StreamingApiServerService } from './api/StreamingApiServerService.js';
|
|||
import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
|
||||
import { ClientServerService } from './web/ClientServerService.js';
|
||||
import { MastoConverters } from './api/mastodon/converters.js';
|
||||
import { MastodonLogger } from './api/mastodon/MastodonLogger.js';
|
||||
import { MastodonDataService } from './api/mastodon/MastodonDataService.js';
|
||||
import { FeedService } from './web/FeedService.js';
|
||||
import { UrlPreviewService } from './web/UrlPreviewService.js';
|
||||
import { ClientLoggerService } from './web/ClientLoggerService.js';
|
||||
|
|
@ -103,6 +105,8 @@ import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.j
|
|||
MastodonApiServerService,
|
||||
OAuth2ProviderService,
|
||||
MastoConverters,
|
||||
MastodonLogger,
|
||||
MastodonDataService,
|
||||
],
|
||||
exports: [
|
||||
ServerService,
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ export class ServerService implements OnApplicationShutdown {
|
|||
}
|
||||
});
|
||||
} else {
|
||||
fastify.listen({ port: this.config.port, host: '0.0.0.0' });
|
||||
fastify.listen({ port: this.config.port, host: this.config.address });
|
||||
}
|
||||
|
||||
await fastify.ready();
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@ SkRateLimiterService is not quite plug-and-play compatible with existing call si
|
|||
Instead, the returned LimitInfo object will have `blocked` set to true.
|
||||
Callers are responsible for checking this property and taking any desired action, such as rejecting a request or returning limit details.
|
||||
|
||||
Rate limit factors are also handled differently.
|
||||
Instead of providing an optional parameter for callers, SkRateLimiterServer accepts an `MiUser` parameter that is used to compute the factor directly.
|
||||
If a user is not available (such as for unauthenticated callers), then the Role Template factor is used instead.
|
||||
To avoid confusion, the `factor` parameter has been removed entirely and is now an implementation detail.
|
||||
|
||||
## Headers
|
||||
|
||||
LimitInfo objects (returned by `SkRateLimitService.limit()`) can be passed to `rate-limit-utils.sendRateLimitHeaders()` to send standard rate limit headers with an HTTP response.
|
||||
|
|
@ -34,6 +39,7 @@ The first call is read-only, while the others perform at least one write operati
|
|||
Two integer keys are stored per client/subject, and both expire together after the maximum duration of the limit.
|
||||
While performance has not been formally tested, it's expected that SkRateLimiterService has an impact roughly on par with the legacy RateLimiterService.
|
||||
Redis memory usage should be notably lower due to the reduced number of keys and avoidance of set / array constructions.
|
||||
If redis load does become a concern, then a dedicated node can be assigned via the `redisForRateLimit` config setting.
|
||||
|
||||
## Concurrency and Multi-Node Correctness
|
||||
|
||||
|
|
|
|||
|
|
@ -5,36 +5,67 @@
|
|||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import Redis from 'ioredis';
|
||||
import { TimeService } from '@/core/TimeService.js';
|
||||
import { EnvService } from '@/core/EnvService.js';
|
||||
import type { TimeService } from '@/core/TimeService.js';
|
||||
import type { EnvService } from '@/core/EnvService.js';
|
||||
import { BucketRateLimit, LegacyRateLimit, LimitInfo, RateLimit, hasMinLimit, isLegacyRateLimit, Keyed, hasMaxLimit, disabledLimitInfo, MaxLegacyLimit, MinLegacyLimit } from '@/misc/rate-limit-utils.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { MemoryKVCache } from '@/misc/cache.js';
|
||||
import type { MiUser } from '@/models/_.js';
|
||||
import type { RoleService } from '@/core/RoleService.js';
|
||||
|
||||
// Sentinel value used for caching the default role template.
|
||||
// Required because MemoryKVCache doesn't support null keys.
|
||||
const defaultUserKey = '';
|
||||
|
||||
@Injectable()
|
||||
export class SkRateLimiterService {
|
||||
// 1-minute cache interval
|
||||
private readonly factorCache = new MemoryKVCache<number>(1000 * 60);
|
||||
private readonly disabled: boolean;
|
||||
|
||||
constructor(
|
||||
@Inject(TimeService)
|
||||
@Inject('TimeService')
|
||||
private readonly timeService: TimeService,
|
||||
|
||||
@Inject(DI.redis)
|
||||
@Inject(DI.redisForRateLimit)
|
||||
private readonly redisClient: Redis.Redis,
|
||||
|
||||
@Inject(EnvService)
|
||||
@Inject('RoleService')
|
||||
private readonly roleService: RoleService,
|
||||
|
||||
@Inject('EnvService')
|
||||
envService: EnvService,
|
||||
) {
|
||||
this.disabled = envService.env.NODE_ENV === 'test';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check & increment a rate limit
|
||||
* Check & increment a rate limit for a client.
|
||||
*
|
||||
* If the client (actorOrUser) is passed as a string, then it uses the default rate limit factor from the role template.
|
||||
* If the client (actorOrUser) is passed as an MiUser, then it queries the user's actual rate limit factor from their assigned roles.
|
||||
*
|
||||
* A factor of zero (0) will disable the limit, while any negative number will produce an error.
|
||||
* A factor between zero (0) and one (1) will increase the limit from its default values (allowing more actions per time interval).
|
||||
* A factor greater than one (1) will decrease the limit from its default values (allowing fewer actions per time interval).
|
||||
*
|
||||
* @param limit The limit definition
|
||||
* @param actor Client who is calling this limit
|
||||
* @param factor Scaling factor - smaller = larger limit (less restrictive)
|
||||
* @param actorOrUser authenticated client user or IP hash
|
||||
*/
|
||||
public async limit(limit: Keyed<RateLimit>, actor: string, factor = 1): Promise<LimitInfo> {
|
||||
if (this.disabled || factor === 0) {
|
||||
public async limit(limit: Keyed<RateLimit>, actorOrUser: string | MiUser): Promise<LimitInfo> {
|
||||
if (this.disabled) {
|
||||
return disabledLimitInfo;
|
||||
}
|
||||
|
||||
const actor = typeof(actorOrUser) === 'object' ? actorOrUser.id : actorOrUser;
|
||||
const userCacheKey = typeof(actorOrUser) === 'object' ? actorOrUser.id : defaultUserKey;
|
||||
const userRoleKey = typeof(actorOrUser) === 'object' ? actorOrUser.id : null;
|
||||
const factor = this.factorCache.get(userCacheKey) ?? await this.factorCache.fetch(userCacheKey, async () => {
|
||||
const role = await this.roleService.getUserPolicies(userRoleKey);
|
||||
return role.rateLimitFactor;
|
||||
});
|
||||
|
||||
if (factor === 0) {
|
||||
return disabledLimitInfo;
|
||||
}
|
||||
|
||||
|
|
@ -42,10 +73,6 @@ export class SkRateLimiterService {
|
|||
throw new Error(`Rate limit factor is zero or negative: ${factor}`);
|
||||
}
|
||||
|
||||
return await this.tryLimit(limit, actor, factor);
|
||||
}
|
||||
|
||||
private async tryLimit(limit: Keyed<RateLimit>, actor: string, factor: number): Promise<LimitInfo> {
|
||||
if (isLegacyRateLimit(limit)) {
|
||||
return await this.limitLegacy(limit, actor, factor);
|
||||
} else {
|
||||
|
|
@ -19,7 +19,7 @@ import { bindThis } from '@/decorators.js';
|
|||
import { RoleService } from '@/core/RoleService.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { sendRateLimitHeaders } from '@/misc/rate-limit-utils.js';
|
||||
import { SkRateLimiterService } from '@/server/api/SkRateLimiterService.js';
|
||||
import { SkRateLimiterService } from '@/server/SkRateLimiterService.js';
|
||||
import { ApiError } from './error.js';
|
||||
import { ApiLoggerService } from './ApiLoggerService.js';
|
||||
import { AuthenticateService, AuthenticationError } from './AuthenticateService.js';
|
||||
|
|
@ -313,35 +313,30 @@ export class ApiCallService implements OnApplicationShutdown {
|
|||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (endpointLimit) {
|
||||
// koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app.
|
||||
let limitActor: string;
|
||||
let limitActor: string | MiLocalUser;
|
||||
if (user) {
|
||||
limitActor = user.id;
|
||||
limitActor = user;
|
||||
} else {
|
||||
limitActor = getIpHash(request.ip);
|
||||
}
|
||||
|
||||
// TODO: 毎リクエスト計算するのもあれだしキャッシュしたい
|
||||
const factor = user ? (await this.roleService.getUserPolicies(user.id)).rateLimitFactor : 1;
|
||||
const limit = {
|
||||
key: ep.name,
|
||||
...endpointLimit,
|
||||
};
|
||||
|
||||
if (factor > 0) {
|
||||
const limit = {
|
||||
key: ep.name,
|
||||
...endpointLimit,
|
||||
};
|
||||
// Rate limit
|
||||
const info = await this.rateLimiterService.limit(limit, limitActor);
|
||||
|
||||
// Rate limit
|
||||
const info = await this.rateLimiterService.limit(limit, limitActor, factor);
|
||||
sendRateLimitHeaders(reply, info);
|
||||
|
||||
sendRateLimitHeaders(reply, info);
|
||||
|
||||
if (info.blocked) {
|
||||
throw new ApiError({
|
||||
message: 'Rate limit exceeded. Please try again later.',
|
||||
code: 'RATE_LIMIT_EXCEEDED',
|
||||
id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
|
||||
httpStatusCode: 429,
|
||||
}, info);
|
||||
}
|
||||
if (info.blocked) {
|
||||
throw new ApiError({
|
||||
message: 'Rate limit exceeded. Please try again later.',
|
||||
code: 'RATE_LIMIT_EXCEEDED',
|
||||
id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
|
||||
httpStatusCode: 429,
|
||||
}, info);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -26,12 +26,19 @@ import { UserAuthService } from '@/core/UserAuthService.js';
|
|||
import { CaptchaService } from '@/core/CaptchaService.js';
|
||||
import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
|
||||
import { isSystemAccount } from '@/misc/is-system-account.js';
|
||||
import { SkRateLimiterService } from '@/server/api/SkRateLimiterService.js';
|
||||
import { sendRateLimitHeaders } from '@/misc/rate-limit-utils.js';
|
||||
import { SkRateLimiterService } from '@/server/SkRateLimiterService.js';
|
||||
import { Keyed, RateLimit, sendRateLimitHeaders } from '@/misc/rate-limit-utils.js';
|
||||
import { SigninService } from './SigninService.js';
|
||||
import type { AuthenticationResponseJSON } from '@simplewebauthn/types';
|
||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
|
||||
// Up to 10 attempts, then 1 per minute
|
||||
const signinRateLimit: Keyed<RateLimit> = {
|
||||
key: 'signin',
|
||||
max: 10,
|
||||
dripRate: 1000 * 60,
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class SigninApiService {
|
||||
constructor(
|
||||
|
|
@ -94,7 +101,7 @@ export class SigninApiService {
|
|||
}
|
||||
|
||||
// not more than 1 attempt per second and not more than 10 attempts per hour
|
||||
const rateLimit = await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(request.ip));
|
||||
const rateLimit = await this.rateLimiterService.limit(signinRateLimit, getIpHash(request.ip));
|
||||
|
||||
sendRateLimitHeaders(reply, rateLimit);
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { WebAuthnService } from '@/core/WebAuthnService.js';
|
|||
import Logger from '@/logger.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import type { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { SkRateLimiterService } from '@/server/api/SkRateLimiterService.js';
|
||||
import { SkRateLimiterService } from '@/server/SkRateLimiterService.js';
|
||||
import { sendRateLimitHeaders } from '@/misc/rate-limit-utils.js';
|
||||
import { SigninService } from './SigninService.js';
|
||||
import type { AuthenticationResponseJSON } from '@simplewebauthn/types';
|
||||
|
|
|
|||
|
|
@ -19,10 +19,9 @@ import { MiLocalUser } from '@/models/User.js';
|
|||
import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { SigninService } from './SigninService.js';
|
||||
import type { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import instance from './endpoints/charts/instance.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
|
||||
@Injectable()
|
||||
export class SignupApiService {
|
||||
|
|
|
|||
|
|
@ -18,10 +18,9 @@ import { CacheService } from '@/core/CacheService.js';
|
|||
import { MiLocalUser } from '@/models/User.js';
|
||||
import { UserService } from '@/core/UserService.js';
|
||||
import { ChannelFollowingService } from '@/core/ChannelFollowingService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { getIpHash } from '@/misc/get-ip-hash.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import { SkRateLimiterService } from '@/server/api/SkRateLimiterService.js';
|
||||
import { SkRateLimiterService } from '@/server/SkRateLimiterService.js';
|
||||
import { AuthenticateService, AuthenticationError } from './AuthenticateService.js';
|
||||
import MainStreamConnection from './stream/Connection.js';
|
||||
import { ChannelsService } from './stream/ChannelsService.js';
|
||||
|
|
@ -49,7 +48,6 @@ export class StreamingApiServerService {
|
|||
private usersService: UserService,
|
||||
private channelFollowingService: ChannelFollowingService,
|
||||
private rateLimiterService: SkRateLimiterService,
|
||||
private roleService: RoleService,
|
||||
private loggerService: LoggerService,
|
||||
) {
|
||||
}
|
||||
|
|
@ -57,22 +55,18 @@ export class StreamingApiServerService {
|
|||
@bindThis
|
||||
private async rateLimitThis(
|
||||
user: MiLocalUser | null | undefined,
|
||||
requestIp: string | undefined,
|
||||
requestIp: string,
|
||||
limit: IEndpointMeta['limit'] & { key: NonNullable<string> },
|
||||
) : Promise<boolean> {
|
||||
let limitActor: string;
|
||||
let limitActor: string | MiLocalUser;
|
||||
if (user) {
|
||||
limitActor = user.id;
|
||||
limitActor = user;
|
||||
} else {
|
||||
limitActor = getIpHash(requestIp || 'wtf');
|
||||
limitActor = getIpHash(requestIp);
|
||||
}
|
||||
|
||||
const factor = user ? (await this.roleService.getUserPolicies(user.id)).rateLimitFactor : 1;
|
||||
|
||||
if (factor <= 0) return false;
|
||||
|
||||
// Rate limit
|
||||
const rateLimit = await this.rateLimiterService.limit(limit, limitActor, factor);
|
||||
const rateLimit = await this.rateLimiterService.limit(limit, limitActor);
|
||||
return rateLimit.blocked;
|
||||
}
|
||||
|
||||
|
|
|
|||
421
packages/backend/src/server/api/endpoint-list.ts
Normal file
421
packages/backend/src/server/api/endpoint-list.ts
Normal file
|
|
@ -0,0 +1,421 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file contains list of all endpoints exported as pathname of API endpoint
|
||||
*
|
||||
* When you add new endpoint, you should add it to this file.
|
||||
* This file is used to generate API documentation and EndpointsModule.
|
||||
*/
|
||||
|
||||
export * as 'admin/abuse-report/notification-recipient/create' from './endpoints/admin/abuse-report/notification-recipient/create.js';
|
||||
export * as 'admin/abuse-report/notification-recipient/delete' from './endpoints/admin/abuse-report/notification-recipient/delete.js';
|
||||
export * as 'admin/abuse-report/notification-recipient/list' from './endpoints/admin/abuse-report/notification-recipient/list.js';
|
||||
export * as 'admin/abuse-report/notification-recipient/show' from './endpoints/admin/abuse-report/notification-recipient/show.js';
|
||||
export * as 'admin/abuse-report/notification-recipient/update' from './endpoints/admin/abuse-report/notification-recipient/update.js';
|
||||
export * as 'admin/abuse-user-reports' from './endpoints/admin/abuse-user-reports.js';
|
||||
export * as 'admin/accounts/create' from './endpoints/admin/accounts/create.js';
|
||||
export * as 'admin/accounts/delete' from './endpoints/admin/accounts/delete.js';
|
||||
export * as 'admin/accounts/find-by-email' from './endpoints/admin/accounts/find-by-email.js';
|
||||
export * as 'admin/ad/create' from './endpoints/admin/ad/create.js';
|
||||
export * as 'admin/ad/delete' from './endpoints/admin/ad/delete.js';
|
||||
export * as 'admin/ad/list' from './endpoints/admin/ad/list.js';
|
||||
export * as 'admin/ad/update' from './endpoints/admin/ad/update.js';
|
||||
export * as 'admin/announcements/create' from './endpoints/admin/announcements/create.js';
|
||||
export * as 'admin/announcements/delete' from './endpoints/admin/announcements/delete.js';
|
||||
export * as 'admin/announcements/list' from './endpoints/admin/announcements/list.js';
|
||||
export * as 'admin/announcements/update' from './endpoints/admin/announcements/update.js';
|
||||
export * as 'admin/approve-user' from './endpoints/admin/approve-user.js';
|
||||
export * as 'admin/avatar-decorations/create' from './endpoints/admin/avatar-decorations/create.js';
|
||||
export * as 'admin/avatar-decorations/delete' from './endpoints/admin/avatar-decorations/delete.js';
|
||||
export * as 'admin/avatar-decorations/list' from './endpoints/admin/avatar-decorations/list.js';
|
||||
export * as 'admin/avatar-decorations/update' from './endpoints/admin/avatar-decorations/update.js';
|
||||
export * as 'admin/captcha/current' from './endpoints/admin/captcha/current.js';
|
||||
export * as 'admin/captcha/save' from './endpoints/admin/captcha/save.js';
|
||||
export * as 'admin/cw-user' from './endpoints/admin/cw-user.js';
|
||||
export * as 'admin/decline-user' from './endpoints/admin/decline-user.js';
|
||||
export * as 'admin/delete-account' from './endpoints/admin/delete-account.js';
|
||||
export * as 'admin/delete-all-files-of-a-user' from './endpoints/admin/delete-all-files-of-a-user.js';
|
||||
export * as 'admin/drive/clean-remote-files' from './endpoints/admin/drive/clean-remote-files.js';
|
||||
export * as 'admin/drive/cleanup' from './endpoints/admin/drive/cleanup.js';
|
||||
export * as 'admin/drive/files' from './endpoints/admin/drive/files.js';
|
||||
export * as 'admin/drive/show-file' from './endpoints/admin/drive/show-file.js';
|
||||
export * as 'admin/emoji/add' from './endpoints/admin/emoji/add.js';
|
||||
export * as 'admin/emoji/add-aliases-bulk' from './endpoints/admin/emoji/add-aliases-bulk.js';
|
||||
export * as 'admin/emoji/copy' from './endpoints/admin/emoji/copy.js';
|
||||
export * as 'admin/emoji/delete' from './endpoints/admin/emoji/delete.js';
|
||||
export * as 'admin/emoji/delete-bulk' from './endpoints/admin/emoji/delete-bulk.js';
|
||||
export * as 'admin/emoji/import-zip' from './endpoints/admin/emoji/import-zip.js';
|
||||
export * as 'admin/emoji/list' from './endpoints/admin/emoji/list.js';
|
||||
export * as 'admin/emoji/list-remote' from './endpoints/admin/emoji/list-remote.js';
|
||||
export * as 'admin/emoji/remove-aliases-bulk' from './endpoints/admin/emoji/remove-aliases-bulk.js';
|
||||
export * as 'admin/emoji/set-aliases-bulk' from './endpoints/admin/emoji/set-aliases-bulk.js';
|
||||
export * as 'admin/emoji/set-category-bulk' from './endpoints/admin/emoji/set-category-bulk.js';
|
||||
export * as 'admin/emoji/set-license-bulk' from './endpoints/admin/emoji/set-license-bulk.js';
|
||||
export * as 'admin/emoji/update' from './endpoints/admin/emoji/update.js';
|
||||
export * as 'admin/federation/delete-all-files' from './endpoints/admin/federation/delete-all-files.js';
|
||||
export * as 'admin/federation/refresh-remote-instance-metadata' from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
|
||||
export * as 'admin/federation/remove-all-following' from './endpoints/admin/federation/remove-all-following.js';
|
||||
export * as 'admin/federation/update-instance' from './endpoints/admin/federation/update-instance.js';
|
||||
export * as 'admin/forward-abuse-user-report' from './endpoints/admin/forward-abuse-user-report.js';
|
||||
export * as 'admin/gen-vapid-keys' from './endpoints/admin/gen-vapid-keys.js';
|
||||
export * as 'admin/get-index-stats' from './endpoints/admin/get-index-stats.js';
|
||||
export * as 'admin/get-table-stats' from './endpoints/admin/get-table-stats.js';
|
||||
export * as 'admin/get-user-ips' from './endpoints/admin/get-user-ips.js';
|
||||
export * as 'admin/invite/create' from './endpoints/admin/invite/create.js';
|
||||
export * as 'admin/invite/list' from './endpoints/admin/invite/list.js';
|
||||
export * as 'admin/meta' from './endpoints/admin/meta.js';
|
||||
export * as 'admin/nsfw-user' from './endpoints/admin/nsfw-user.js';
|
||||
export * as 'admin/promo/create' from './endpoints/admin/promo/create.js';
|
||||
export * as 'admin/queue/clear' from './endpoints/admin/queue/clear.js';
|
||||
export * as 'admin/queue/deliver-delayed' from './endpoints/admin/queue/deliver-delayed.js';
|
||||
export * as 'admin/queue/inbox-delayed' from './endpoints/admin/queue/inbox-delayed.js';
|
||||
export * as 'admin/queue/promote' from './endpoints/admin/queue/promote.js';
|
||||
export * as 'admin/queue/stats' from './endpoints/admin/queue/stats.js';
|
||||
export * as 'admin/reject-quotes' from './endpoints/admin/reject-quotes.js';
|
||||
export * as 'admin/relays/add' from './endpoints/admin/relays/add.js';
|
||||
export * as 'admin/relays/list' from './endpoints/admin/relays/list.js';
|
||||
export * as 'admin/relays/remove' from './endpoints/admin/relays/remove.js';
|
||||
export * as 'admin/reset-password' from './endpoints/admin/reset-password.js';
|
||||
export * as 'admin/resolve-abuse-user-report' from './endpoints/admin/resolve-abuse-user-report.js';
|
||||
export * as 'admin/roles/assign' from './endpoints/admin/roles/assign.js';
|
||||
export * as 'admin/roles/create' from './endpoints/admin/roles/create.js';
|
||||
export * as 'admin/roles/delete' from './endpoints/admin/roles/delete.js';
|
||||
export * as 'admin/roles/list' from './endpoints/admin/roles/list.js';
|
||||
export * as 'admin/roles/show' from './endpoints/admin/roles/show.js';
|
||||
export * as 'admin/roles/unassign' from './endpoints/admin/roles/unassign.js';
|
||||
export * as 'admin/roles/update' from './endpoints/admin/roles/update.js';
|
||||
export * as 'admin/roles/update-default-policies' from './endpoints/admin/roles/update-default-policies.js';
|
||||
export * as 'admin/roles/users' from './endpoints/admin/roles/users.js';
|
||||
export * as 'admin/send-email' from './endpoints/admin/send-email.js';
|
||||
export * as 'admin/server-info' from './endpoints/admin/server-info.js';
|
||||
export * as 'admin/show-moderation-logs' from './endpoints/admin/show-moderation-logs.js';
|
||||
export * as 'admin/show-user' from './endpoints/admin/show-user.js';
|
||||
export * as 'admin/show-users' from './endpoints/admin/show-users.js';
|
||||
export * as 'admin/silence-user' from './endpoints/admin/silence-user.js';
|
||||
export * as 'admin/suspend-user' from './endpoints/admin/suspend-user.js';
|
||||
export * as 'admin/system-webhook/create' from './endpoints/admin/system-webhook/create.js';
|
||||
export * as 'admin/system-webhook/delete' from './endpoints/admin/system-webhook/delete.js';
|
||||
export * as 'admin/system-webhook/list' from './endpoints/admin/system-webhook/list.js';
|
||||
export * as 'admin/system-webhook/show' from './endpoints/admin/system-webhook/show.js';
|
||||
export * as 'admin/system-webhook/test' from './endpoints/admin/system-webhook/test.js';
|
||||
export * as 'admin/system-webhook/update' from './endpoints/admin/system-webhook/update.js';
|
||||
export * as 'admin/unnsfw-user' from './endpoints/admin/unnsfw-user.js';
|
||||
export * as 'admin/unset-user-avatar' from './endpoints/admin/unset-user-avatar.js';
|
||||
export * as 'admin/unset-user-banner' from './endpoints/admin/unset-user-banner.js';
|
||||
export * as 'admin/unsilence-user' from './endpoints/admin/unsilence-user.js';
|
||||
export * as 'admin/unsuspend-user' from './endpoints/admin/unsuspend-user.js';
|
||||
export * as 'admin/update-abuse-user-report' from './endpoints/admin/update-abuse-user-report.js';
|
||||
export * as 'admin/update-meta' from './endpoints/admin/update-meta.js';
|
||||
export * as 'admin/update-user-note' from './endpoints/admin/update-user-note.js';
|
||||
export * as 'announcements' from './endpoints/announcements.js';
|
||||
export * as 'announcements/show' from './endpoints/announcements/show.js';
|
||||
export * as 'antennas/create' from './endpoints/antennas/create.js';
|
||||
export * as 'antennas/delete' from './endpoints/antennas/delete.js';
|
||||
export * as 'antennas/list' from './endpoints/antennas/list.js';
|
||||
export * as 'antennas/notes' from './endpoints/antennas/notes.js';
|
||||
export * as 'antennas/show' from './endpoints/antennas/show.js';
|
||||
export * as 'antennas/update' from './endpoints/antennas/update.js';
|
||||
export * as 'ap/get' from './endpoints/ap/get.js';
|
||||
export * as 'ap/show' from './endpoints/ap/show.js';
|
||||
export * as 'app/create' from './endpoints/app/create.js';
|
||||
export * as 'app/show' from './endpoints/app/show.js';
|
||||
export * as 'auth/accept' from './endpoints/auth/accept.js';
|
||||
export * as 'auth/session/generate' from './endpoints/auth/session/generate.js';
|
||||
export * as 'auth/session/show' from './endpoints/auth/session/show.js';
|
||||
export * as 'auth/session/userkey' from './endpoints/auth/session/userkey.js';
|
||||
export * as 'blocking/create' from './endpoints/blocking/create.js';
|
||||
export * as 'blocking/delete' from './endpoints/blocking/delete.js';
|
||||
export * as 'blocking/list' from './endpoints/blocking/list.js';
|
||||
export * as 'bubble-game/ranking' from './endpoints/bubble-game/ranking.js';
|
||||
export * as 'bubble-game/register' from './endpoints/bubble-game/register.js';
|
||||
export * as 'channels/create' from './endpoints/channels/create.js';
|
||||
export * as 'channels/favorite' from './endpoints/channels/favorite.js';
|
||||
export * as 'channels/featured' from './endpoints/channels/featured.js';
|
||||
export * as 'channels/follow' from './endpoints/channels/follow.js';
|
||||
export * as 'channels/followed' from './endpoints/channels/followed.js';
|
||||
export * as 'channels/my-favorites' from './endpoints/channels/my-favorites.js';
|
||||
export * as 'channels/owned' from './endpoints/channels/owned.js';
|
||||
export * as 'channels/search' from './endpoints/channels/search.js';
|
||||
export * as 'channels/show' from './endpoints/channels/show.js';
|
||||
export * as 'channels/timeline' from './endpoints/channels/timeline.js';
|
||||
export * as 'channels/unfavorite' from './endpoints/channels/unfavorite.js';
|
||||
export * as 'channels/unfollow' from './endpoints/channels/unfollow.js';
|
||||
export * as 'channels/update' from './endpoints/channels/update.js';
|
||||
export * as 'charts/active-users' from './endpoints/charts/active-users.js';
|
||||
export * as 'charts/ap-request' from './endpoints/charts/ap-request.js';
|
||||
export * as 'charts/drive' from './endpoints/charts/drive.js';
|
||||
export * as 'charts/federation' from './endpoints/charts/federation.js';
|
||||
export * as 'charts/instance' from './endpoints/charts/instance.js';
|
||||
export * as 'charts/notes' from './endpoints/charts/notes.js';
|
||||
export * as 'charts/user/drive' from './endpoints/charts/user/drive.js';
|
||||
export * as 'charts/user/following' from './endpoints/charts/user/following.js';
|
||||
export * as 'charts/user/notes' from './endpoints/charts/user/notes.js';
|
||||
export * as 'charts/user/pv' from './endpoints/charts/user/pv.js';
|
||||
export * as 'charts/user/reactions' from './endpoints/charts/user/reactions.js';
|
||||
export * as 'charts/users' from './endpoints/charts/users.js';
|
||||
export * as 'clips/add-note' from './endpoints/clips/add-note.js';
|
||||
export * as 'clips/create' from './endpoints/clips/create.js';
|
||||
export * as 'clips/delete' from './endpoints/clips/delete.js';
|
||||
export * as 'clips/favorite' from './endpoints/clips/favorite.js';
|
||||
export * as 'clips/list' from './endpoints/clips/list.js';
|
||||
export * as 'clips/my-favorites' from './endpoints/clips/my-favorites.js';
|
||||
export * as 'clips/notes' from './endpoints/clips/notes.js';
|
||||
export * as 'clips/remove-note' from './endpoints/clips/remove-note.js';
|
||||
export * as 'clips/show' from './endpoints/clips/show.js';
|
||||
export * as 'clips/unfavorite' from './endpoints/clips/unfavorite.js';
|
||||
export * as 'clips/update' from './endpoints/clips/update.js';
|
||||
export * as 'drive' from './endpoints/drive.js';
|
||||
export * as 'drive/files' from './endpoints/drive/files.js';
|
||||
export * as 'drive/files/attached-notes' from './endpoints/drive/files/attached-notes.js';
|
||||
export * as 'drive/files/check-existence' from './endpoints/drive/files/check-existence.js';
|
||||
export * as 'drive/files/create' from './endpoints/drive/files/create.js';
|
||||
export * as 'drive/files/delete' from './endpoints/drive/files/delete.js';
|
||||
export * as 'drive/files/find' from './endpoints/drive/files/find.js';
|
||||
export * as 'drive/files/find-by-hash' from './endpoints/drive/files/find-by-hash.js';
|
||||
export * as 'drive/files/show' from './endpoints/drive/files/show.js';
|
||||
export * as 'drive/files/update' from './endpoints/drive/files/update.js';
|
||||
export * as 'drive/files/upload-from-url' from './endpoints/drive/files/upload-from-url.js';
|
||||
export * as 'drive/folders' from './endpoints/drive/folders.js';
|
||||
export * as 'drive/folders/create' from './endpoints/drive/folders/create.js';
|
||||
export * as 'drive/folders/delete' from './endpoints/drive/folders/delete.js';
|
||||
export * as 'drive/folders/find' from './endpoints/drive/folders/find.js';
|
||||
export * as 'drive/folders/show' from './endpoints/drive/folders/show.js';
|
||||
export * as 'drive/folders/update' from './endpoints/drive/folders/update.js';
|
||||
export * as 'drive/stream' from './endpoints/drive/stream.js';
|
||||
export * as 'email-address/available' from './endpoints/email-address/available.js';
|
||||
export * as 'emoji' from './endpoints/emoji.js';
|
||||
export * as 'emojis' from './endpoints/emojis.js';
|
||||
export * as 'endpoint' from './endpoints/endpoint.js';
|
||||
export * as 'endpoints' from './endpoints/endpoints.js';
|
||||
export * as 'export-custom-emojis' from './endpoints/export-custom-emojis.js';
|
||||
export * as 'federation/followers' from './endpoints/federation/followers.js';
|
||||
export * as 'federation/following' from './endpoints/federation/following.js';
|
||||
export * as 'federation/instances' from './endpoints/federation/instances.js';
|
||||
export * as 'federation/show-instance' from './endpoints/federation/show-instance.js';
|
||||
export * as 'federation/stats' from './endpoints/federation/stats.js';
|
||||
export * as 'federation/update-remote-user' from './endpoints/federation/update-remote-user.js';
|
||||
export * as 'federation/users' from './endpoints/federation/users.js';
|
||||
export * as 'fetch-external-resources' from './endpoints/fetch-external-resources.js';
|
||||
export * as 'fetch-rss' from './endpoints/fetch-rss.js';
|
||||
export * as 'flash/create' from './endpoints/flash/create.js';
|
||||
export * as 'flash/delete' from './endpoints/flash/delete.js';
|
||||
export * as 'flash/featured' from './endpoints/flash/featured.js';
|
||||
export * as 'flash/like' from './endpoints/flash/like.js';
|
||||
export * as 'flash/my' from './endpoints/flash/my.js';
|
||||
export * as 'flash/my-likes' from './endpoints/flash/my-likes.js';
|
||||
export * as 'flash/show' from './endpoints/flash/show.js';
|
||||
export * as 'flash/unlike' from './endpoints/flash/unlike.js';
|
||||
export * as 'flash/update' from './endpoints/flash/update.js';
|
||||
export * as 'following/create' from './endpoints/following/create.js';
|
||||
export * as 'following/delete' from './endpoints/following/delete.js';
|
||||
export * as 'following/invalidate' from './endpoints/following/invalidate.js';
|
||||
export * as 'following/requests/accept' from './endpoints/following/requests/accept.js';
|
||||
export * as 'following/requests/cancel' from './endpoints/following/requests/cancel.js';
|
||||
export * as 'following/requests/list' from './endpoints/following/requests/list.js';
|
||||
export * as 'following/requests/reject' from './endpoints/following/requests/reject.js';
|
||||
export * as 'following/requests/sent' from './endpoints/following/requests/sent.js';
|
||||
export * as 'following/update' from './endpoints/following/update.js';
|
||||
export * as 'following/update-all' from './endpoints/following/update-all.js';
|
||||
export * as 'gallery/featured' from './endpoints/gallery/featured.js';
|
||||
export * as 'gallery/popular' from './endpoints/gallery/popular.js';
|
||||
export * as 'gallery/posts' from './endpoints/gallery/posts.js';
|
||||
export * as 'gallery/posts/create' from './endpoints/gallery/posts/create.js';
|
||||
export * as 'gallery/posts/delete' from './endpoints/gallery/posts/delete.js';
|
||||
export * as 'gallery/posts/like' from './endpoints/gallery/posts/like.js';
|
||||
export * as 'gallery/posts/show' from './endpoints/gallery/posts/show.js';
|
||||
export * as 'gallery/posts/unlike' from './endpoints/gallery/posts/unlike.js';
|
||||
export * as 'gallery/posts/update' from './endpoints/gallery/posts/update.js';
|
||||
export * as 'get-avatar-decorations' from './endpoints/get-avatar-decorations.js';
|
||||
export * as 'get-online-users-count' from './endpoints/get-online-users-count.js';
|
||||
export * as 'hashtags/list' from './endpoints/hashtags/list.js';
|
||||
export * as 'hashtags/search' from './endpoints/hashtags/search.js';
|
||||
export * as 'hashtags/show' from './endpoints/hashtags/show.js';
|
||||
export * as 'hashtags/trend' from './endpoints/hashtags/trend.js';
|
||||
export * as 'hashtags/users' from './endpoints/hashtags/users.js';
|
||||
export * as 'i' from './endpoints/i.js';
|
||||
export * as 'i/2fa/done' from './endpoints/i/2fa/done.js';
|
||||
export * as 'i/2fa/key-done' from './endpoints/i/2fa/key-done.js';
|
||||
export * as 'i/2fa/password-less' from './endpoints/i/2fa/password-less.js';
|
||||
export * as 'i/2fa/register' from './endpoints/i/2fa/register.js';
|
||||
export * as 'i/2fa/register-key' from './endpoints/i/2fa/register-key.js';
|
||||
export * as 'i/2fa/remove-key' from './endpoints/i/2fa/remove-key.js';
|
||||
export * as 'i/2fa/unregister' from './endpoints/i/2fa/unregister.js';
|
||||
export * as 'i/2fa/update-key' from './endpoints/i/2fa/update-key.js';
|
||||
export * as 'i/apps' from './endpoints/i/apps.js';
|
||||
export * as 'i/authorized-apps' from './endpoints/i/authorized-apps.js';
|
||||
export * as 'i/change-password' from './endpoints/i/change-password.js';
|
||||
export * as 'i/claim-achievement' from './endpoints/i/claim-achievement.js';
|
||||
export * as 'i/delete-account' from './endpoints/i/delete-account.js';
|
||||
export * as 'i/export-antennas' from './endpoints/i/export-antennas.js';
|
||||
export * as 'i/export-blocking' from './endpoints/i/export-blocking.js';
|
||||
export * as 'i/export-clips' from './endpoints/i/export-clips.js';
|
||||
export * as 'i/export-data' from './endpoints/i/export-data.js';
|
||||
export * as 'i/export-favorites' from './endpoints/i/export-favorites.js';
|
||||
export * as 'i/export-following' from './endpoints/i/export-following.js';
|
||||
export * as 'i/export-mute' from './endpoints/i/export-mute.js';
|
||||
export * as 'i/export-notes' from './endpoints/i/export-notes.js';
|
||||
export * as 'i/export-user-lists' from './endpoints/i/export-user-lists.js';
|
||||
export * as 'i/favorites' from './endpoints/i/favorites.js';
|
||||
export * as 'i/gallery/likes' from './endpoints/i/gallery/likes.js';
|
||||
export * as 'i/gallery/posts' from './endpoints/i/gallery/posts.js';
|
||||
export * as 'i/import-antennas' from './endpoints/i/import-antennas.js';
|
||||
export * as 'i/import-blocking' from './endpoints/i/import-blocking.js';
|
||||
export * as 'i/import-following' from './endpoints/i/import-following.js';
|
||||
export * as 'i/import-muting' from './endpoints/i/import-muting.js';
|
||||
export * as 'i/import-notes' from './endpoints/i/import-notes.js';
|
||||
export * as 'i/import-user-lists' from './endpoints/i/import-user-lists.js';
|
||||
export * as 'i/move' from './endpoints/i/move.js';
|
||||
export * as 'i/notifications' from './endpoints/i/notifications.js';
|
||||
export * as 'i/notifications-grouped' from './endpoints/i/notifications-grouped.js';
|
||||
export * as 'i/page-likes' from './endpoints/i/page-likes.js';
|
||||
export * as 'i/pages' from './endpoints/i/pages.js';
|
||||
export * as 'i/pin' from './endpoints/i/pin.js';
|
||||
export * as 'i/read-all-unread-notes' from './endpoints/i/read-all-unread-notes.js';
|
||||
export * as 'i/read-announcement' from './endpoints/i/read-announcement.js';
|
||||
export * as 'i/regenerate-token' from './endpoints/i/regenerate-token.js';
|
||||
export * as 'i/registry/get' from './endpoints/i/registry/get.js';
|
||||
export * as 'i/registry/get-all' from './endpoints/i/registry/get-all.js';
|
||||
export * as 'i/registry/get-detail' from './endpoints/i/registry/get-detail.js';
|
||||
export * as 'i/registry/get-unsecure' from './endpoints/i/registry/get-unsecure.js';
|
||||
export * as 'i/registry/keys' from './endpoints/i/registry/keys.js';
|
||||
export * as 'i/registry/keys-with-type' from './endpoints/i/registry/keys-with-type.js';
|
||||
export * as 'i/registry/remove' from './endpoints/i/registry/remove.js';
|
||||
export * as 'i/registry/scopes-with-domain' from './endpoints/i/registry/scopes-with-domain.js';
|
||||
export * as 'i/registry/set' from './endpoints/i/registry/set.js';
|
||||
export * as 'i/revoke-token' from './endpoints/i/revoke-token.js';
|
||||
export * as 'i/signin-history' from './endpoints/i/signin-history.js';
|
||||
export * as 'i/unpin' from './endpoints/i/unpin.js';
|
||||
export * as 'i/update' from './endpoints/i/update.js';
|
||||
export * as 'i/update-email' from './endpoints/i/update-email.js';
|
||||
export * as 'i/webhooks/create' from './endpoints/i/webhooks/create.js';
|
||||
export * as 'i/webhooks/delete' from './endpoints/i/webhooks/delete.js';
|
||||
export * as 'i/webhooks/list' from './endpoints/i/webhooks/list.js';
|
||||
export * as 'i/webhooks/show' from './endpoints/i/webhooks/show.js';
|
||||
export * as 'i/webhooks/test' from './endpoints/i/webhooks/test.js';
|
||||
export * as 'i/webhooks/update' from './endpoints/i/webhooks/update.js';
|
||||
export * as 'invite/create' from './endpoints/invite/create.js';
|
||||
export * as 'invite/delete' from './endpoints/invite/delete.js';
|
||||
export * as 'invite/limit' from './endpoints/invite/limit.js';
|
||||
export * as 'invite/list' from './endpoints/invite/list.js';
|
||||
export * as 'meta' from './endpoints/meta.js';
|
||||
export * as 'miauth/gen-token' from './endpoints/miauth/gen-token.js';
|
||||
export * as 'mute/create' from './endpoints/mute/create.js';
|
||||
export * as 'mute/delete' from './endpoints/mute/delete.js';
|
||||
export * as 'mute/list' from './endpoints/mute/list.js';
|
||||
export * as 'my/apps' from './endpoints/my/apps.js';
|
||||
export * as 'notes' from './endpoints/notes.js';
|
||||
export * as 'notes/bubble-timeline' from './endpoints/notes/bubble-timeline.js';
|
||||
export * as 'notes/children' from './endpoints/notes/children.js';
|
||||
export * as 'notes/clips' from './endpoints/notes/clips.js';
|
||||
export * as 'notes/conversation' from './endpoints/notes/conversation.js';
|
||||
export * as 'notes/create' from './endpoints/notes/create.js';
|
||||
export * as 'notes/delete' from './endpoints/notes/delete.js';
|
||||
export * as 'notes/edit' from './endpoints/notes/edit.js';
|
||||
export * as 'notes/favorites/create' from './endpoints/notes/favorites/create.js';
|
||||
export * as 'notes/favorites/delete' from './endpoints/notes/favorites/delete.js';
|
||||
export * as 'notes/featured' from './endpoints/notes/featured.js';
|
||||
export * as 'notes/following' from './endpoints/notes/following.js';
|
||||
export * as 'notes/global-timeline' from './endpoints/notes/global-timeline.js';
|
||||
export * as 'notes/hybrid-timeline' from './endpoints/notes/hybrid-timeline.js';
|
||||
export * as 'notes/like' from './endpoints/notes/like.js';
|
||||
export * as 'notes/local-timeline' from './endpoints/notes/local-timeline.js';
|
||||
export * as 'notes/mentions' from './endpoints/notes/mentions.js';
|
||||
export * as 'notes/polls/recommendation' from './endpoints/notes/polls/recommendation.js';
|
||||
export * as 'notes/polls/refresh' from './endpoints/notes/polls/refresh.js';
|
||||
export * as 'notes/polls/vote' from './endpoints/notes/polls/vote.js';
|
||||
export * as 'notes/reactions' from './endpoints/notes/reactions.js';
|
||||
export * as 'notes/reactions/create' from './endpoints/notes/reactions/create.js';
|
||||
export * as 'notes/reactions/delete' from './endpoints/notes/reactions/delete.js';
|
||||
export * as 'notes/renotes' from './endpoints/notes/renotes.js';
|
||||
export * as 'notes/replies' from './endpoints/notes/replies.js';
|
||||
export * as 'notes/search' from './endpoints/notes/search.js';
|
||||
export * as 'notes/schedule/create' from './endpoints/notes/schedule/create.js';
|
||||
export * as 'notes/schedule/delete' from './endpoints/notes/schedule/delete.js';
|
||||
export * as 'notes/schedule/list' from './endpoints/notes/schedule/list.js';
|
||||
export * as 'notes/search-by-tag' from './endpoints/notes/search-by-tag.js';
|
||||
export * as 'notes/show' from './endpoints/notes/show.js';
|
||||
export * as 'notes/state' from './endpoints/notes/state.js';
|
||||
export * as 'notes/thread-muting/create' from './endpoints/notes/thread-muting/create.js';
|
||||
export * as 'notes/thread-muting/delete' from './endpoints/notes/thread-muting/delete.js';
|
||||
export * as 'notes/timeline' from './endpoints/notes/timeline.js';
|
||||
export * as 'notes/translate' from './endpoints/notes/translate.js';
|
||||
export * as 'notes/unrenote' from './endpoints/notes/unrenote.js';
|
||||
export * as 'notes/user-list-timeline' from './endpoints/notes/user-list-timeline.js';
|
||||
export * as 'notes/versions' from './endpoints/notes/versions.js';
|
||||
export * as 'notifications/create' from './endpoints/notifications/create.js';
|
||||
export * as 'notifications/flush' from './endpoints/notifications/flush.js';
|
||||
export * as 'notifications/mark-all-as-read' from './endpoints/notifications/mark-all-as-read.js';
|
||||
export * as 'notifications/test-notification' from './endpoints/notifications/test-notification.js';
|
||||
export * as 'page-push' from './endpoints/page-push.js';
|
||||
export * as 'pages/create' from './endpoints/pages/create.js';
|
||||
export * as 'pages/delete' from './endpoints/pages/delete.js';
|
||||
export * as 'pages/featured' from './endpoints/pages/featured.js';
|
||||
export * as 'pages/like' from './endpoints/pages/like.js';
|
||||
export * as 'pages/show' from './endpoints/pages/show.js';
|
||||
export * as 'pages/unlike' from './endpoints/pages/unlike.js';
|
||||
export * as 'pages/update' from './endpoints/pages/update.js';
|
||||
export * as 'ping' from './endpoints/ping.js';
|
||||
export * as 'pinned-users' from './endpoints/pinned-users.js';
|
||||
export * as 'promo/read' from './endpoints/promo/read.js';
|
||||
export * as 'renote-mute/create' from './endpoints/renote-mute/create.js';
|
||||
export * as 'renote-mute/delete' from './endpoints/renote-mute/delete.js';
|
||||
export * as 'renote-mute/list' from './endpoints/renote-mute/list.js';
|
||||
export * as 'request-reset-password' from './endpoints/request-reset-password.js';
|
||||
export * as 'reset-db' from './endpoints/reset-db.js';
|
||||
export * as 'reset-password' from './endpoints/reset-password.js';
|
||||
export * as 'retention' from './endpoints/retention.js';
|
||||
export * as 'reversi/cancel-match' from './endpoints/reversi/cancel-match.js';
|
||||
export * as 'reversi/games' from './endpoints/reversi/games.js';
|
||||
export * as 'reversi/invitations' from './endpoints/reversi/invitations.js';
|
||||
export * as 'reversi/match' from './endpoints/reversi/match.js';
|
||||
export * as 'reversi/show-game' from './endpoints/reversi/show-game.js';
|
||||
export * as 'reversi/surrender' from './endpoints/reversi/surrender.js';
|
||||
export * as 'reversi/verify' from './endpoints/reversi/verify.js';
|
||||
export * as 'roles/list' from './endpoints/roles/list.js';
|
||||
export * as 'roles/notes' from './endpoints/roles/notes.js';
|
||||
export * as 'roles/show' from './endpoints/roles/show.js';
|
||||
export * as 'roles/users' from './endpoints/roles/users.js';
|
||||
export * as 'server-info' from './endpoints/server-info.js';
|
||||
export * as 'sponsors' from './endpoints/sponsors.js';
|
||||
export * as 'stats' from './endpoints/stats.js';
|
||||
export * as 'sw/register' from './endpoints/sw/register.js';
|
||||
export * as 'sw/show-registration' from './endpoints/sw/show-registration.js';
|
||||
export * as 'sw/unregister' from './endpoints/sw/unregister.js';
|
||||
export * as 'sw/update-registration' from './endpoints/sw/update-registration.js';
|
||||
export * as 'test' from './endpoints/test.js';
|
||||
export * as 'username/available' from './endpoints/username/available.js';
|
||||
export * as 'users' from './endpoints/users.js';
|
||||
export * as 'users/achievements' from './endpoints/users/achievements.js';
|
||||
export * as 'users/clips' from './endpoints/users/clips.js';
|
||||
export * as 'users/featured-notes' from './endpoints/users/featured-notes.js';
|
||||
export * as 'users/flashs' from './endpoints/users/flashs.js';
|
||||
export * as 'users/followers' from './endpoints/users/followers.js';
|
||||
export * as 'users/following' from './endpoints/users/following.js';
|
||||
export * as 'users/gallery/posts' from './endpoints/users/gallery/posts.js';
|
||||
export * as 'users/get-frequently-replied-users' from './endpoints/users/get-frequently-replied-users.js';
|
||||
export * as 'users/lists/create' from './endpoints/users/lists/create.js';
|
||||
export * as 'users/lists/create-from-public' from './endpoints/users/lists/create-from-public.js';
|
||||
export * as 'users/lists/delete' from './endpoints/users/lists/delete.js';
|
||||
export * as 'users/lists/favorite' from './endpoints/users/lists/favorite.js';
|
||||
export * as 'users/lists/get-memberships' from './endpoints/users/lists/get-memberships.js';
|
||||
export * as 'users/lists/list' from './endpoints/users/lists/list.js';
|
||||
export * as 'users/lists/pull' from './endpoints/users/lists/pull.js';
|
||||
export * as 'users/lists/push' from './endpoints/users/lists/push.js';
|
||||
export * as 'users/lists/show' from './endpoints/users/lists/show.js';
|
||||
export * as 'users/lists/unfavorite' from './endpoints/users/lists/unfavorite.js';
|
||||
export * as 'users/lists/update' from './endpoints/users/lists/update.js';
|
||||
export * as 'users/lists/update-membership' from './endpoints/users/lists/update-membership.js';
|
||||
export * as 'users/notes' from './endpoints/users/notes.js';
|
||||
export * as 'users/pages' from './endpoints/users/pages.js';
|
||||
export * as 'users/reactions' from './endpoints/users/reactions.js';
|
||||
export * as 'users/recommendation' from './endpoints/users/recommendation.js';
|
||||
export * as 'users/relation' from './endpoints/users/relation.js';
|
||||
export * as 'users/report-abuse' from './endpoints/users/report-abuse.js';
|
||||
export * as 'users/search' from './endpoints/users/search.js';
|
||||
export * as 'users/search-by-username-and-host' from './endpoints/users/search-by-username-and-host.js';
|
||||
export * as 'users/show' from './endpoints/users/show.js';
|
||||
export * as 'users/update-memo' from './endpoints/users/update-memo.js';
|
||||
export * as 'v2/admin/emoji/list' from './endpoints/v2/admin/emoji/list.js';
|
||||
|
|
@ -7,821 +7,7 @@ import { permissions } from 'misskey-js';
|
|||
import type { KeyOf, Schema } from '@/misc/json-schema.js';
|
||||
import type { RateLimit } from '@/misc/rate-limit-utils.js';
|
||||
|
||||
import * as ep___admin_abuseReport_notificationRecipient_list
|
||||
from '@/server/api/endpoints/admin/abuse-report/notification-recipient/list.js';
|
||||
import * as ep___admin_abuseReport_notificationRecipient_show
|
||||
from '@/server/api/endpoints/admin/abuse-report/notification-recipient/show.js';
|
||||
import * as ep___admin_abuseReport_notificationRecipient_create
|
||||
from '@/server/api/endpoints/admin/abuse-report/notification-recipient/create.js';
|
||||
import * as ep___admin_abuseReport_notificationRecipient_update
|
||||
from '@/server/api/endpoints/admin/abuse-report/notification-recipient/update.js';
|
||||
import * as ep___admin_abuseReport_notificationRecipient_delete
|
||||
from '@/server/api/endpoints/admin/abuse-report/notification-recipient/delete.js';
|
||||
import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js';
|
||||
import * as ep___admin_meta from './endpoints/admin/meta.js';
|
||||
import * as ep___admin_accounts_create from './endpoints/admin/accounts/create.js';
|
||||
import * as ep___admin_accounts_delete from './endpoints/admin/accounts/delete.js';
|
||||
import * as ep___admin_accounts_findByEmail from './endpoints/admin/accounts/find-by-email.js';
|
||||
import * as ep___admin_ad_create from './endpoints/admin/ad/create.js';
|
||||
import * as ep___admin_ad_delete from './endpoints/admin/ad/delete.js';
|
||||
import * as ep___admin_ad_list from './endpoints/admin/ad/list.js';
|
||||
import * as ep___admin_ad_update from './endpoints/admin/ad/update.js';
|
||||
import * as ep___admin_announcements_create from './endpoints/admin/announcements/create.js';
|
||||
import * as ep___admin_announcements_delete from './endpoints/admin/announcements/delete.js';
|
||||
import * as ep___admin_announcements_list from './endpoints/admin/announcements/list.js';
|
||||
import * as ep___admin_announcements_update from './endpoints/admin/announcements/update.js';
|
||||
import * as ep___admin_avatarDecorations_create from './endpoints/admin/avatar-decorations/create.js';
|
||||
import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js';
|
||||
import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js';
|
||||
import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js';
|
||||
import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js';
|
||||
import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js';
|
||||
import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js';
|
||||
import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js';
|
||||
import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js';
|
||||
import * as ep___admin_drive_files from './endpoints/admin/drive/files.js';
|
||||
import * as ep___admin_drive_showFile from './endpoints/admin/drive/show-file.js';
|
||||
import * as ep___admin_emoji_addAliasesBulk from './endpoints/admin/emoji/add-aliases-bulk.js';
|
||||
import * as ep___admin_emoji_add from './endpoints/admin/emoji/add.js';
|
||||
import * as ep___admin_emoji_copy from './endpoints/admin/emoji/copy.js';
|
||||
import * as ep___admin_emoji_deleteBulk from './endpoints/admin/emoji/delete-bulk.js';
|
||||
import * as ep___admin_emoji_delete from './endpoints/admin/emoji/delete.js';
|
||||
import * as ep___admin_emoji_importZip from './endpoints/admin/emoji/import-zip.js';
|
||||
import * as ep___admin_emoji_listRemote from './endpoints/admin/emoji/list-remote.js';
|
||||
import * as ep___admin_emoji_list from './endpoints/admin/emoji/list.js';
|
||||
import * as ep___admin_emoji_removeAliasesBulk from './endpoints/admin/emoji/remove-aliases-bulk.js';
|
||||
import * as ep___admin_emoji_setAliasesBulk from './endpoints/admin/emoji/set-aliases-bulk.js';
|
||||
import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js';
|
||||
import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js';
|
||||
import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js';
|
||||
import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js';
|
||||
import * as ep___admin_federation_refreshRemoteInstanceMetadata
|
||||
from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
|
||||
import * as ep___admin_federation_removeAllFollowing from './endpoints/admin/federation/remove-all-following.js';
|
||||
import * as ep___admin_federation_updateInstance from './endpoints/admin/federation/update-instance.js';
|
||||
import * as ep___admin_getIndexStats from './endpoints/admin/get-index-stats.js';
|
||||
import * as ep___admin_getTableStats from './endpoints/admin/get-table-stats.js';
|
||||
import * as ep___admin_getUserIps from './endpoints/admin/get-user-ips.js';
|
||||
import * as ep___admin_invite_create from './endpoints/admin/invite/create.js';
|
||||
import * as ep___admin_invite_list from './endpoints/admin/invite/list.js';
|
||||
import * as ep___admin_promo_create from './endpoints/admin/promo/create.js';
|
||||
import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js';
|
||||
import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js';
|
||||
import * as ep___admin_queue_inboxDelayed from './endpoints/admin/queue/inbox-delayed.js';
|
||||
import * as ep___admin_queue_promote from './endpoints/admin/queue/promote.js';
|
||||
import * as ep___admin_queue_stats from './endpoints/admin/queue/stats.js';
|
||||
import * as ep___admin_relays_add from './endpoints/admin/relays/add.js';
|
||||
import * as ep___admin_relays_list from './endpoints/admin/relays/list.js';
|
||||
import * as ep___admin_relays_remove from './endpoints/admin/relays/remove.js';
|
||||
import * as ep___admin_resetPassword from './endpoints/admin/reset-password.js';
|
||||
import * as ep___admin_resolveAbuseUserReport from './endpoints/admin/resolve-abuse-user-report.js';
|
||||
import * as ep___admin_forwardAbuseUserReport from './endpoints/admin/forward-abuse-user-report.js';
|
||||
import * as ep___admin_updateAbuseUserReport from './endpoints/admin/update-abuse-user-report.js';
|
||||
import * as ep___admin_sendEmail from './endpoints/admin/send-email.js';
|
||||
import * as ep___admin_serverInfo from './endpoints/admin/server-info.js';
|
||||
import * as ep___admin_showModerationLogs from './endpoints/admin/show-moderation-logs.js';
|
||||
import * as ep___admin_showUser from './endpoints/admin/show-user.js';
|
||||
import * as ep___admin_showUsers from './endpoints/admin/show-users.js';
|
||||
import * as ep___admin_nsfwUser from './endpoints/admin/nsfw-user.js';
|
||||
import * as ep___admin_unnsfwUser from './endpoints/admin/unnsfw-user.js';
|
||||
import * as ep___admin_silenceUser from './endpoints/admin/silence-user.js';
|
||||
import * as ep___admin_unsilenceUser from './endpoints/admin/unsilence-user.js';
|
||||
import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js';
|
||||
import * as ep___admin_approveUser from './endpoints/admin/approve-user.js';
|
||||
import * as ep___admin_declineUser from './endpoints/admin/decline-user.js';
|
||||
import * as ep___admin_unsuspendUser from './endpoints/admin/unsuspend-user.js';
|
||||
import * as ep___admin_updateMeta from './endpoints/admin/update-meta.js';
|
||||
import * as ep___admin_deleteAccount from './endpoints/admin/delete-account.js';
|
||||
import * as ep___admin_updateUserNote from './endpoints/admin/update-user-note.js';
|
||||
import * as ep___admin_roles_create from './endpoints/admin/roles/create.js';
|
||||
import * as ep___admin_roles_delete from './endpoints/admin/roles/delete.js';
|
||||
import * as ep___admin_roles_list from './endpoints/admin/roles/list.js';
|
||||
import * as ep___admin_roles_show from './endpoints/admin/roles/show.js';
|
||||
import * as ep___admin_roles_update from './endpoints/admin/roles/update.js';
|
||||
import * as ep___admin_roles_assign from './endpoints/admin/roles/assign.js';
|
||||
import * as ep___admin_roles_unassign from './endpoints/admin/roles/unassign.js';
|
||||
import * as ep___admin_roles_updateDefaultPolicies from './endpoints/admin/roles/update-default-policies.js';
|
||||
import * as ep___admin_roles_users from './endpoints/admin/roles/users.js';
|
||||
import * as ep___admin_systemWebhook_create from './endpoints/admin/system-webhook/create.js';
|
||||
import * as ep___admin_systemWebhook_delete from './endpoints/admin/system-webhook/delete.js';
|
||||
import * as ep___admin_systemWebhook_list from './endpoints/admin/system-webhook/list.js';
|
||||
import * as ep___admin_systemWebhook_show from './endpoints/admin/system-webhook/show.js';
|
||||
import * as ep___admin_systemWebhook_update from './endpoints/admin/system-webhook/update.js';
|
||||
import * as ep___admin_systemWebhook_test from './endpoints/admin/system-webhook/test.js';
|
||||
import * as ep___announcements from './endpoints/announcements.js';
|
||||
import * as ep___announcements_show from './endpoints/announcements/show.js';
|
||||
import * as ep___antennas_create from './endpoints/antennas/create.js';
|
||||
import * as ep___antennas_delete from './endpoints/antennas/delete.js';
|
||||
import * as ep___antennas_list from './endpoints/antennas/list.js';
|
||||
import * as ep___antennas_notes from './endpoints/antennas/notes.js';
|
||||
import * as ep___antennas_show from './endpoints/antennas/show.js';
|
||||
import * as ep___antennas_update from './endpoints/antennas/update.js';
|
||||
import * as ep___ap_get from './endpoints/ap/get.js';
|
||||
import * as ep___ap_show from './endpoints/ap/show.js';
|
||||
import * as ep___app_create from './endpoints/app/create.js';
|
||||
import * as ep___app_show from './endpoints/app/show.js';
|
||||
import * as ep___auth_accept from './endpoints/auth/accept.js';
|
||||
import * as ep___auth_session_generate from './endpoints/auth/session/generate.js';
|
||||
import * as ep___auth_session_show from './endpoints/auth/session/show.js';
|
||||
import * as ep___auth_session_userkey from './endpoints/auth/session/userkey.js';
|
||||
import * as ep___blocking_create from './endpoints/blocking/create.js';
|
||||
import * as ep___blocking_delete from './endpoints/blocking/delete.js';
|
||||
import * as ep___blocking_list from './endpoints/blocking/list.js';
|
||||
import * as ep___channels_create from './endpoints/channels/create.js';
|
||||
import * as ep___channels_featured from './endpoints/channels/featured.js';
|
||||
import * as ep___channels_follow from './endpoints/channels/follow.js';
|
||||
import * as ep___channels_followed from './endpoints/channels/followed.js';
|
||||
import * as ep___channels_owned from './endpoints/channels/owned.js';
|
||||
import * as ep___channels_show from './endpoints/channels/show.js';
|
||||
import * as ep___channels_timeline from './endpoints/channels/timeline.js';
|
||||
import * as ep___channels_unfollow from './endpoints/channels/unfollow.js';
|
||||
import * as ep___channels_update from './endpoints/channels/update.js';
|
||||
import * as ep___channels_favorite from './endpoints/channels/favorite.js';
|
||||
import * as ep___channels_unfavorite from './endpoints/channels/unfavorite.js';
|
||||
import * as ep___channels_myFavorites from './endpoints/channels/my-favorites.js';
|
||||
import * as ep___channels_search from './endpoints/channels/search.js';
|
||||
import * as ep___charts_activeUsers from './endpoints/charts/active-users.js';
|
||||
import * as ep___charts_apRequest from './endpoints/charts/ap-request.js';
|
||||
import * as ep___charts_drive from './endpoints/charts/drive.js';
|
||||
import * as ep___charts_federation from './endpoints/charts/federation.js';
|
||||
import * as ep___charts_instance from './endpoints/charts/instance.js';
|
||||
import * as ep___charts_notes from './endpoints/charts/notes.js';
|
||||
import * as ep___charts_user_drive from './endpoints/charts/user/drive.js';
|
||||
import * as ep___charts_user_following from './endpoints/charts/user/following.js';
|
||||
import * as ep___charts_user_notes from './endpoints/charts/user/notes.js';
|
||||
import * as ep___charts_user_pv from './endpoints/charts/user/pv.js';
|
||||
import * as ep___charts_user_reactions from './endpoints/charts/user/reactions.js';
|
||||
import * as ep___charts_users from './endpoints/charts/users.js';
|
||||
import * as ep___clips_addNote from './endpoints/clips/add-note.js';
|
||||
import * as ep___clips_removeNote from './endpoints/clips/remove-note.js';
|
||||
import * as ep___clips_create from './endpoints/clips/create.js';
|
||||
import * as ep___clips_delete from './endpoints/clips/delete.js';
|
||||
import * as ep___clips_list from './endpoints/clips/list.js';
|
||||
import * as ep___clips_notes from './endpoints/clips/notes.js';
|
||||
import * as ep___clips_show from './endpoints/clips/show.js';
|
||||
import * as ep___clips_update from './endpoints/clips/update.js';
|
||||
import * as ep___clips_favorite from './endpoints/clips/favorite.js';
|
||||
import * as ep___clips_unfavorite from './endpoints/clips/unfavorite.js';
|
||||
import * as ep___clips_myFavorites from './endpoints/clips/my-favorites.js';
|
||||
import * as ep___drive from './endpoints/drive.js';
|
||||
import * as ep___drive_files from './endpoints/drive/files.js';
|
||||
import * as ep___drive_files_attachedNotes from './endpoints/drive/files/attached-notes.js';
|
||||
import * as ep___drive_files_checkExistence from './endpoints/drive/files/check-existence.js';
|
||||
import * as ep___drive_files_create from './endpoints/drive/files/create.js';
|
||||
import * as ep___drive_files_delete from './endpoints/drive/files/delete.js';
|
||||
import * as ep___drive_files_findByHash from './endpoints/drive/files/find-by-hash.js';
|
||||
import * as ep___drive_files_find from './endpoints/drive/files/find.js';
|
||||
import * as ep___drive_files_show from './endpoints/drive/files/show.js';
|
||||
import * as ep___drive_files_update from './endpoints/drive/files/update.js';
|
||||
import * as ep___drive_files_uploadFromUrl from './endpoints/drive/files/upload-from-url.js';
|
||||
import * as ep___drive_folders from './endpoints/drive/folders.js';
|
||||
import * as ep___drive_folders_create from './endpoints/drive/folders/create.js';
|
||||
import * as ep___drive_folders_delete from './endpoints/drive/folders/delete.js';
|
||||
import * as ep___drive_folders_find from './endpoints/drive/folders/find.js';
|
||||
import * as ep___drive_folders_show from './endpoints/drive/folders/show.js';
|
||||
import * as ep___drive_folders_update from './endpoints/drive/folders/update.js';
|
||||
import * as ep___drive_stream from './endpoints/drive/stream.js';
|
||||
import * as ep___emailAddress_available from './endpoints/email-address/available.js';
|
||||
import * as ep___endpoint from './endpoints/endpoint.js';
|
||||
import * as ep___endpoints from './endpoints/endpoints.js';
|
||||
import * as ep___exportCustomEmojis from './endpoints/export-custom-emojis.js';
|
||||
import * as ep___federation_followers from './endpoints/federation/followers.js';
|
||||
import * as ep___federation_following from './endpoints/federation/following.js';
|
||||
import * as ep___federation_instances from './endpoints/federation/instances.js';
|
||||
import * as ep___federation_showInstance from './endpoints/federation/show-instance.js';
|
||||
import * as ep___federation_updateRemoteUser from './endpoints/federation/update-remote-user.js';
|
||||
import * as ep___federation_users from './endpoints/federation/users.js';
|
||||
import * as ep___federation_stats from './endpoints/federation/stats.js';
|
||||
import * as ep___following_create from './endpoints/following/create.js';
|
||||
import * as ep___following_delete from './endpoints/following/delete.js';
|
||||
import * as ep___following_update from './endpoints/following/update.js';
|
||||
import * as ep___following_update_all from './endpoints/following/update-all.js';
|
||||
import * as ep___following_invalidate from './endpoints/following/invalidate.js';
|
||||
import * as ep___following_requests_accept from './endpoints/following/requests/accept.js';
|
||||
import * as ep___following_requests_cancel from './endpoints/following/requests/cancel.js';
|
||||
import * as ep___following_requests_list from './endpoints/following/requests/list.js';
|
||||
import * as ep___following_requests_sent from './endpoints/following/requests/sent.js';
|
||||
import * as ep___following_requests_reject from './endpoints/following/requests/reject.js';
|
||||
import * as ep___gallery_featured from './endpoints/gallery/featured.js';
|
||||
import * as ep___gallery_popular from './endpoints/gallery/popular.js';
|
||||
import * as ep___gallery_posts from './endpoints/gallery/posts.js';
|
||||
import * as ep___gallery_posts_create from './endpoints/gallery/posts/create.js';
|
||||
import * as ep___gallery_posts_delete from './endpoints/gallery/posts/delete.js';
|
||||
import * as ep___gallery_posts_like from './endpoints/gallery/posts/like.js';
|
||||
import * as ep___gallery_posts_show from './endpoints/gallery/posts/show.js';
|
||||
import * as ep___gallery_posts_unlike from './endpoints/gallery/posts/unlike.js';
|
||||
import * as ep___gallery_posts_update from './endpoints/gallery/posts/update.js';
|
||||
import * as ep___getOnlineUsersCount from './endpoints/get-online-users-count.js';
|
||||
import * as ep___getAvatarDecorations from './endpoints/get-avatar-decorations.js';
|
||||
import * as ep___hashtags_list from './endpoints/hashtags/list.js';
|
||||
import * as ep___hashtags_search from './endpoints/hashtags/search.js';
|
||||
import * as ep___hashtags_show from './endpoints/hashtags/show.js';
|
||||
import * as ep___hashtags_trend from './endpoints/hashtags/trend.js';
|
||||
import * as ep___hashtags_users from './endpoints/hashtags/users.js';
|
||||
import * as ep___i from './endpoints/i.js';
|
||||
import * as ep___i_2fa_done from './endpoints/i/2fa/done.js';
|
||||
import * as ep___i_2fa_keyDone from './endpoints/i/2fa/key-done.js';
|
||||
import * as ep___i_2fa_passwordLess from './endpoints/i/2fa/password-less.js';
|
||||
import * as ep___i_2fa_registerKey from './endpoints/i/2fa/register-key.js';
|
||||
import * as ep___i_2fa_register from './endpoints/i/2fa/register.js';
|
||||
import * as ep___i_2fa_updateKey from './endpoints/i/2fa/update-key.js';
|
||||
import * as ep___i_2fa_removeKey from './endpoints/i/2fa/remove-key.js';
|
||||
import * as ep___i_2fa_unregister from './endpoints/i/2fa/unregister.js';
|
||||
import * as ep___i_apps from './endpoints/i/apps.js';
|
||||
import * as ep___i_authorizedApps from './endpoints/i/authorized-apps.js';
|
||||
import * as ep___i_claimAchievement from './endpoints/i/claim-achievement.js';
|
||||
import * as ep___i_changePassword from './endpoints/i/change-password.js';
|
||||
import * as ep___i_deleteAccount from './endpoints/i/delete-account.js';
|
||||
import * as ep___i_exportData from './endpoints/i/export-data.js';
|
||||
import * as ep___i_exportBlocking from './endpoints/i/export-blocking.js';
|
||||
import * as ep___i_exportFollowing from './endpoints/i/export-following.js';
|
||||
import * as ep___i_exportMute from './endpoints/i/export-mute.js';
|
||||
import * as ep___i_exportNotes from './endpoints/i/export-notes.js';
|
||||
import * as ep___i_exportClips from './endpoints/i/export-clips.js';
|
||||
import * as ep___i_exportFavorites from './endpoints/i/export-favorites.js';
|
||||
import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js';
|
||||
import * as ep___i_exportAntennas from './endpoints/i/export-antennas.js';
|
||||
import * as ep___i_favorites from './endpoints/i/favorites.js';
|
||||
import * as ep___i_gallery_likes from './endpoints/i/gallery/likes.js';
|
||||
import * as ep___i_gallery_posts from './endpoints/i/gallery/posts.js';
|
||||
import * as ep___i_importBlocking from './endpoints/i/import-blocking.js';
|
||||
import * as ep___i_importFollowing from './endpoints/i/import-following.js';
|
||||
import * as ep___i_importNotes from './endpoints/i/import-notes.js';
|
||||
import * as ep___i_importMuting from './endpoints/i/import-muting.js';
|
||||
import * as ep___i_importUserLists from './endpoints/i/import-user-lists.js';
|
||||
import * as ep___i_importAntennas from './endpoints/i/import-antennas.js';
|
||||
import * as ep___i_notifications from './endpoints/i/notifications.js';
|
||||
import * as ep___i_notificationsGrouped from './endpoints/i/notifications-grouped.js';
|
||||
import * as ep___i_pageLikes from './endpoints/i/page-likes.js';
|
||||
import * as ep___i_pages from './endpoints/i/pages.js';
|
||||
import * as ep___i_pin from './endpoints/i/pin.js';
|
||||
import * as ep___i_readAllUnreadNotes from './endpoints/i/read-all-unread-notes.js';
|
||||
import * as ep___i_readAnnouncement from './endpoints/i/read-announcement.js';
|
||||
import * as ep___i_regenerateToken from './endpoints/i/regenerate-token.js';
|
||||
import * as ep___i_registry_getAll from './endpoints/i/registry/get-all.js';
|
||||
import * as ep___i_registry_getUnsecure from './endpoints/i/registry/get-unsecure.js';
|
||||
import * as ep___i_registry_getDetail from './endpoints/i/registry/get-detail.js';
|
||||
import * as ep___i_registry_get from './endpoints/i/registry/get.js';
|
||||
import * as ep___i_registry_keysWithType from './endpoints/i/registry/keys-with-type.js';
|
||||
import * as ep___i_registry_keys from './endpoints/i/registry/keys.js';
|
||||
import * as ep___i_registry_remove from './endpoints/i/registry/remove.js';
|
||||
import * as ep___i_registry_scopesWithDomain from './endpoints/i/registry/scopes-with-domain.js';
|
||||
import * as ep___i_registry_set from './endpoints/i/registry/set.js';
|
||||
import * as ep___i_revokeToken from './endpoints/i/revoke-token.js';
|
||||
import * as ep___i_signinHistory from './endpoints/i/signin-history.js';
|
||||
import * as ep___i_unpin from './endpoints/i/unpin.js';
|
||||
import * as ep___i_updateEmail from './endpoints/i/update-email.js';
|
||||
import * as ep___i_update from './endpoints/i/update.js';
|
||||
import * as ep___i_move from './endpoints/i/move.js';
|
||||
import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js';
|
||||
import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
|
||||
import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
|
||||
import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js';
|
||||
import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js';
|
||||
import * as ep___i_webhooks_test from './endpoints/i/webhooks/test.js';
|
||||
import * as ep___invite_create from './endpoints/invite/create.js';
|
||||
import * as ep___invite_delete from './endpoints/invite/delete.js';
|
||||
import * as ep___invite_list from './endpoints/invite/list.js';
|
||||
import * as ep___invite_limit from './endpoints/invite/limit.js';
|
||||
import * as ep___meta from './endpoints/meta.js';
|
||||
import * as ep___emojis from './endpoints/emojis.js';
|
||||
import * as ep___emoji from './endpoints/emoji.js';
|
||||
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
|
||||
import * as ep___mute_create from './endpoints/mute/create.js';
|
||||
import * as ep___mute_delete from './endpoints/mute/delete.js';
|
||||
import * as ep___mute_list from './endpoints/mute/list.js';
|
||||
import * as ep___renoteMute_create from './endpoints/renote-mute/create.js';
|
||||
import * as ep___renoteMute_delete from './endpoints/renote-mute/delete.js';
|
||||
import * as ep___renoteMute_list from './endpoints/renote-mute/list.js';
|
||||
import * as ep___my_apps from './endpoints/my/apps.js';
|
||||
import * as ep___notes from './endpoints/notes.js';
|
||||
import * as ep___notes_children from './endpoints/notes/children.js';
|
||||
import * as ep___notes_clips from './endpoints/notes/clips.js';
|
||||
import * as ep___notes_conversation from './endpoints/notes/conversation.js';
|
||||
import * as ep___notes_create from './endpoints/notes/create.js';
|
||||
import * as ep___notes_delete from './endpoints/notes/delete.js';
|
||||
import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js';
|
||||
import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js';
|
||||
import * as ep___notes_featured from './endpoints/notes/featured.js';
|
||||
import * as ep___notes_following from './endpoints/notes/following.js';
|
||||
import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js';
|
||||
import * as ep___notes_bubbleTimeline from './endpoints/notes/bubble-timeline.js';
|
||||
import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js';
|
||||
import * as ep___notes_localTimeline from './endpoints/notes/local-timeline.js';
|
||||
import * as ep___notes_mentions from './endpoints/notes/mentions.js';
|
||||
import * as ep___notes_polls_recommendation from './endpoints/notes/polls/recommendation.js';
|
||||
import * as ep___notes_polls_vote from './endpoints/notes/polls/vote.js';
|
||||
import * as ep___notes_polls_refresh from './endpoints/notes/polls/refresh.js';
|
||||
import * as ep___notes_reactions from './endpoints/notes/reactions.js';
|
||||
import * as ep___notes_reactions_create from './endpoints/notes/reactions/create.js';
|
||||
import * as ep___notes_reactions_delete from './endpoints/notes/reactions/delete.js';
|
||||
import * as ep___notes_like from './endpoints/notes/like.js';
|
||||
import * as ep___notes_renotes from './endpoints/notes/renotes.js';
|
||||
import * as ep___notes_replies from './endpoints/notes/replies.js';
|
||||
import * as ep___notes_schedule_create from './endpoints/notes/schedule/create.js';
|
||||
import * as ep___notes_schedule_delete from './endpoints/notes/schedule/delete.js';
|
||||
import * as ep___notes_schedule_list from './endpoints/notes/schedule/list.js';
|
||||
import * as ep___notes_searchByTag from './endpoints/notes/search-by-tag.js';
|
||||
import * as ep___notes_search from './endpoints/notes/search.js';
|
||||
import * as ep___notes_show from './endpoints/notes/show.js';
|
||||
import * as ep___notes_state from './endpoints/notes/state.js';
|
||||
import * as ep___notes_threadMuting_create from './endpoints/notes/thread-muting/create.js';
|
||||
import * as ep___notes_threadMuting_delete from './endpoints/notes/thread-muting/delete.js';
|
||||
import * as ep___notes_timeline from './endpoints/notes/timeline.js';
|
||||
import * as ep___notes_translate from './endpoints/notes/translate.js';
|
||||
import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
|
||||
import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
|
||||
import * as ep___notes_edit from './endpoints/notes/edit.js';
|
||||
import * as ep___notes_versions from './endpoints/notes/versions.js';
|
||||
import * as ep___notifications_create from './endpoints/notifications/create.js';
|
||||
import * as ep___notifications_flush from './endpoints/notifications/flush.js';
|
||||
import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js';
|
||||
import * as ep___notifications_testNotification from './endpoints/notifications/test-notification.js';
|
||||
import * as ep___pagePush from './endpoints/page-push.js';
|
||||
import * as ep___pages_create from './endpoints/pages/create.js';
|
||||
import * as ep___pages_delete from './endpoints/pages/delete.js';
|
||||
import * as ep___pages_featured from './endpoints/pages/featured.js';
|
||||
import * as ep___pages_like from './endpoints/pages/like.js';
|
||||
import * as ep___pages_show from './endpoints/pages/show.js';
|
||||
import * as ep___pages_unlike from './endpoints/pages/unlike.js';
|
||||
import * as ep___pages_update from './endpoints/pages/update.js';
|
||||
import * as ep___flash_create from './endpoints/flash/create.js';
|
||||
import * as ep___flash_delete from './endpoints/flash/delete.js';
|
||||
import * as ep___flash_featured from './endpoints/flash/featured.js';
|
||||
import * as ep___flash_like from './endpoints/flash/like.js';
|
||||
import * as ep___flash_show from './endpoints/flash/show.js';
|
||||
import * as ep___flash_unlike from './endpoints/flash/unlike.js';
|
||||
import * as ep___flash_update from './endpoints/flash/update.js';
|
||||
import * as ep___flash_my from './endpoints/flash/my.js';
|
||||
import * as ep___flash_myLikes from './endpoints/flash/my-likes.js';
|
||||
import * as ep___ping from './endpoints/ping.js';
|
||||
import * as ep___pinnedUsers from './endpoints/pinned-users.js';
|
||||
import * as ep___promo_read from './endpoints/promo/read.js';
|
||||
import * as ep___roles_list from './endpoints/roles/list.js';
|
||||
import * as ep___roles_show from './endpoints/roles/show.js';
|
||||
import * as ep___roles_users from './endpoints/roles/users.js';
|
||||
import * as ep___roles_notes from './endpoints/roles/notes.js';
|
||||
import * as ep___requestResetPassword from './endpoints/request-reset-password.js';
|
||||
import * as ep___resetDb from './endpoints/reset-db.js';
|
||||
import * as ep___resetPassword from './endpoints/reset-password.js';
|
||||
import * as ep___serverInfo from './endpoints/server-info.js';
|
||||
import * as ep___stats from './endpoints/stats.js';
|
||||
import * as ep___sw_show_registration from './endpoints/sw/show-registration.js';
|
||||
import * as ep___sw_update_registration from './endpoints/sw/update-registration.js';
|
||||
import * as ep___sw_register from './endpoints/sw/register.js';
|
||||
import * as ep___sw_unregister from './endpoints/sw/unregister.js';
|
||||
import * as ep___test from './endpoints/test.js';
|
||||
import * as ep___username_available from './endpoints/username/available.js';
|
||||
import * as ep___users from './endpoints/users.js';
|
||||
import * as ep___users_clips from './endpoints/users/clips.js';
|
||||
import * as ep___users_followers from './endpoints/users/followers.js';
|
||||
import * as ep___users_following from './endpoints/users/following.js';
|
||||
import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js';
|
||||
import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js';
|
||||
import * as ep___users_featuredNotes from './endpoints/users/featured-notes.js';
|
||||
import * as ep___users_lists_create from './endpoints/users/lists/create.js';
|
||||
import * as ep___users_lists_delete from './endpoints/users/lists/delete.js';
|
||||
import * as ep___users_lists_list from './endpoints/users/lists/list.js';
|
||||
import * as ep___users_lists_pull from './endpoints/users/lists/pull.js';
|
||||
import * as ep___users_lists_push from './endpoints/users/lists/push.js';
|
||||
import * as ep___users_lists_show from './endpoints/users/lists/show.js';
|
||||
import * as ep___users_lists_favorite from './endpoints/users/lists/favorite.js';
|
||||
import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite.js';
|
||||
import * as ep___users_lists_createFromPublic from './endpoints/users/lists/create-from-public.js';
|
||||
import * as ep___users_lists_update from './endpoints/users/lists/update.js';
|
||||
import * as ep___users_lists_updateMembership from './endpoints/users/lists/update-membership.js';
|
||||
import * as ep___users_lists_getMemberships from './endpoints/users/lists/get-memberships.js';
|
||||
import * as ep___users_notes from './endpoints/users/notes.js';
|
||||
import * as ep___users_pages from './endpoints/users/pages.js';
|
||||
import * as ep___users_flashs from './endpoints/users/flashs.js';
|
||||
import * as ep___users_reactions from './endpoints/users/reactions.js';
|
||||
import * as ep___users_recommendation from './endpoints/users/recommendation.js';
|
||||
import * as ep___users_relation from './endpoints/users/relation.js';
|
||||
import * as ep___users_reportAbuse from './endpoints/users/report-abuse.js';
|
||||
import * as ep___users_searchByUsernameAndHost from './endpoints/users/search-by-username-and-host.js';
|
||||
import * as ep___users_search from './endpoints/users/search.js';
|
||||
import * as ep___users_show from './endpoints/users/show.js';
|
||||
import * as ep___users_achievements from './endpoints/users/achievements.js';
|
||||
import * as ep___users_updateMemo from './endpoints/users/update-memo.js';
|
||||
import * as ep___fetchRss from './endpoints/fetch-rss.js';
|
||||
import * as ep___fetchExternalResources from './endpoints/fetch-external-resources.js';
|
||||
import * as ep___retention from './endpoints/retention.js';
|
||||
import * as ep___sponsors from './endpoints/sponsors.js';
|
||||
import * as ep___bubbleGame_register from './endpoints/bubble-game/register.js';
|
||||
import * as ep___bubbleGame_ranking from './endpoints/bubble-game/ranking.js';
|
||||
import * as ep___reversi_cancelMatch from './endpoints/reversi/cancel-match.js';
|
||||
import * as ep___reversi_games from './endpoints/reversi/games.js';
|
||||
import * as ep___reversi_match from './endpoints/reversi/match.js';
|
||||
import * as ep___reversi_invitations from './endpoints/reversi/invitations.js';
|
||||
import * as ep___reversi_showGame from './endpoints/reversi/show-game.js';
|
||||
import * as ep___reversi_surrender from './endpoints/reversi/surrender.js';
|
||||
import * as ep___reversi_verify from './endpoints/reversi/verify.js';
|
||||
|
||||
const eps = [
|
||||
['admin/meta', ep___admin_meta],
|
||||
['admin/abuse-user-reports', ep___admin_abuseUserReports],
|
||||
['admin/abuse-report/notification-recipient/list', ep___admin_abuseReport_notificationRecipient_list],
|
||||
['admin/abuse-report/notification-recipient/show', ep___admin_abuseReport_notificationRecipient_show],
|
||||
['admin/abuse-report/notification-recipient/create', ep___admin_abuseReport_notificationRecipient_create],
|
||||
['admin/abuse-report/notification-recipient/update', ep___admin_abuseReport_notificationRecipient_update],
|
||||
['admin/abuse-report/notification-recipient/delete', ep___admin_abuseReport_notificationRecipient_delete],
|
||||
['admin/accounts/create', ep___admin_accounts_create],
|
||||
['admin/accounts/delete', ep___admin_accounts_delete],
|
||||
['admin/accounts/find-by-email', ep___admin_accounts_findByEmail],
|
||||
['admin/ad/create', ep___admin_ad_create],
|
||||
['admin/ad/delete', ep___admin_ad_delete],
|
||||
['admin/ad/list', ep___admin_ad_list],
|
||||
['admin/ad/update', ep___admin_ad_update],
|
||||
['admin/announcements/create', ep___admin_announcements_create],
|
||||
['admin/announcements/delete', ep___admin_announcements_delete],
|
||||
['admin/announcements/list', ep___admin_announcements_list],
|
||||
['admin/announcements/update', ep___admin_announcements_update],
|
||||
['admin/avatar-decorations/create', ep___admin_avatarDecorations_create],
|
||||
['admin/avatar-decorations/delete', ep___admin_avatarDecorations_delete],
|
||||
['admin/avatar-decorations/list', ep___admin_avatarDecorations_list],
|
||||
['admin/avatar-decorations/update', ep___admin_avatarDecorations_update],
|
||||
['admin/delete-all-files-of-a-user', ep___admin_deleteAllFilesOfAUser],
|
||||
['admin/unset-user-avatar', ep___admin_unsetUserAvatar],
|
||||
['admin/unset-user-banner', ep___admin_unsetUserBanner],
|
||||
['admin/drive/clean-remote-files', ep___admin_drive_cleanRemoteFiles],
|
||||
['admin/drive/cleanup', ep___admin_drive_cleanup],
|
||||
['admin/drive/files', ep___admin_drive_files],
|
||||
['admin/drive/show-file', ep___admin_drive_showFile],
|
||||
['admin/emoji/add-aliases-bulk', ep___admin_emoji_addAliasesBulk],
|
||||
['admin/emoji/add', ep___admin_emoji_add],
|
||||
['admin/emoji/copy', ep___admin_emoji_copy],
|
||||
['admin/emoji/delete-bulk', ep___admin_emoji_deleteBulk],
|
||||
['admin/emoji/delete', ep___admin_emoji_delete],
|
||||
['admin/emoji/import-zip', ep___admin_emoji_importZip],
|
||||
['admin/emoji/list-remote', ep___admin_emoji_listRemote],
|
||||
['admin/emoji/list', ep___admin_emoji_list],
|
||||
['admin/emoji/remove-aliases-bulk', ep___admin_emoji_removeAliasesBulk],
|
||||
['admin/emoji/set-aliases-bulk', ep___admin_emoji_setAliasesBulk],
|
||||
['admin/emoji/set-category-bulk', ep___admin_emoji_setCategoryBulk],
|
||||
['admin/emoji/set-license-bulk', ep___admin_emoji_setLicenseBulk],
|
||||
['admin/emoji/update', ep___admin_emoji_update],
|
||||
['admin/federation/delete-all-files', ep___admin_federation_deleteAllFiles],
|
||||
['admin/federation/refresh-remote-instance-metadata', ep___admin_federation_refreshRemoteInstanceMetadata],
|
||||
['admin/federation/remove-all-following', ep___admin_federation_removeAllFollowing],
|
||||
['admin/federation/update-instance', ep___admin_federation_updateInstance],
|
||||
['admin/get-index-stats', ep___admin_getIndexStats],
|
||||
['admin/get-table-stats', ep___admin_getTableStats],
|
||||
['admin/get-user-ips', ep___admin_getUserIps],
|
||||
['admin/invite/create', ep___admin_invite_create],
|
||||
['admin/invite/list', ep___admin_invite_list],
|
||||
['admin/promo/create', ep___admin_promo_create],
|
||||
['admin/queue/clear', ep___admin_queue_clear],
|
||||
['admin/queue/deliver-delayed', ep___admin_queue_deliverDelayed],
|
||||
['admin/queue/inbox-delayed', ep___admin_queue_inboxDelayed],
|
||||
['admin/queue/promote', ep___admin_queue_promote],
|
||||
['admin/queue/stats', ep___admin_queue_stats],
|
||||
['admin/relays/add', ep___admin_relays_add],
|
||||
['admin/relays/list', ep___admin_relays_list],
|
||||
['admin/relays/remove', ep___admin_relays_remove],
|
||||
['admin/reset-password', ep___admin_resetPassword],
|
||||
['admin/resolve-abuse-user-report', ep___admin_resolveAbuseUserReport],
|
||||
['admin/forward-abuse-user-report', ep___admin_forwardAbuseUserReport],
|
||||
['admin/update-abuse-user-report', ep___admin_updateAbuseUserReport],
|
||||
['admin/send-email', ep___admin_sendEmail],
|
||||
['admin/server-info', ep___admin_serverInfo],
|
||||
['admin/show-moderation-logs', ep___admin_showModerationLogs],
|
||||
['admin/show-user', ep___admin_showUser],
|
||||
['admin/show-users', ep___admin_showUsers],
|
||||
['admin/nsfw-user', ep___admin_nsfwUser],
|
||||
['admin/unnsfw-user', ep___admin_unnsfwUser],
|
||||
['admin/silence-user', ep___admin_silenceUser],
|
||||
['admin/unsilence-user', ep___admin_unsilenceUser],
|
||||
['admin/suspend-user', ep___admin_suspendUser],
|
||||
['admin/approve-user', ep___admin_approveUser],
|
||||
['admin/decline-user', ep___admin_declineUser],
|
||||
['admin/unsuspend-user', ep___admin_unsuspendUser],
|
||||
['admin/update-meta', ep___admin_updateMeta],
|
||||
['admin/delete-account', ep___admin_deleteAccount],
|
||||
['admin/update-user-note', ep___admin_updateUserNote],
|
||||
['admin/roles/create', ep___admin_roles_create],
|
||||
['admin/roles/delete', ep___admin_roles_delete],
|
||||
['admin/roles/list', ep___admin_roles_list],
|
||||
['admin/roles/show', ep___admin_roles_show],
|
||||
['admin/roles/update', ep___admin_roles_update],
|
||||
['admin/roles/assign', ep___admin_roles_assign],
|
||||
['admin/roles/unassign', ep___admin_roles_unassign],
|
||||
['admin/roles/update-default-policies', ep___admin_roles_updateDefaultPolicies],
|
||||
['admin/roles/users', ep___admin_roles_users],
|
||||
['admin/system-webhook/create', ep___admin_systemWebhook_create],
|
||||
['admin/system-webhook/delete', ep___admin_systemWebhook_delete],
|
||||
['admin/system-webhook/list', ep___admin_systemWebhook_list],
|
||||
['admin/system-webhook/show', ep___admin_systemWebhook_show],
|
||||
['admin/system-webhook/update', ep___admin_systemWebhook_update],
|
||||
['admin/system-webhook/test', ep___admin_systemWebhook_test],
|
||||
['announcements', ep___announcements],
|
||||
['announcements/show', ep___announcements_show],
|
||||
['antennas/create', ep___antennas_create],
|
||||
['antennas/delete', ep___antennas_delete],
|
||||
['antennas/list', ep___antennas_list],
|
||||
['antennas/notes', ep___antennas_notes],
|
||||
['antennas/show', ep___antennas_show],
|
||||
['antennas/update', ep___antennas_update],
|
||||
['ap/get', ep___ap_get],
|
||||
['ap/show', ep___ap_show],
|
||||
['app/create', ep___app_create],
|
||||
['app/show', ep___app_show],
|
||||
['auth/accept', ep___auth_accept],
|
||||
['auth/session/generate', ep___auth_session_generate],
|
||||
['auth/session/show', ep___auth_session_show],
|
||||
['auth/session/userkey', ep___auth_session_userkey],
|
||||
['blocking/create', ep___blocking_create],
|
||||
['blocking/delete', ep___blocking_delete],
|
||||
['blocking/list', ep___blocking_list],
|
||||
['channels/create', ep___channels_create],
|
||||
['channels/featured', ep___channels_featured],
|
||||
['channels/follow', ep___channels_follow],
|
||||
['channels/followed', ep___channels_followed],
|
||||
['channels/owned', ep___channels_owned],
|
||||
['channels/show', ep___channels_show],
|
||||
['channels/timeline', ep___channels_timeline],
|
||||
['channels/unfollow', ep___channels_unfollow],
|
||||
['channels/update', ep___channels_update],
|
||||
['channels/favorite', ep___channels_favorite],
|
||||
['channels/unfavorite', ep___channels_unfavorite],
|
||||
['channels/my-favorites', ep___channels_myFavorites],
|
||||
['channels/search', ep___channels_search],
|
||||
['charts/active-users', ep___charts_activeUsers],
|
||||
['charts/ap-request', ep___charts_apRequest],
|
||||
['charts/drive', ep___charts_drive],
|
||||
['charts/federation', ep___charts_federation],
|
||||
['charts/instance', ep___charts_instance],
|
||||
['charts/notes', ep___charts_notes],
|
||||
['charts/user/drive', ep___charts_user_drive],
|
||||
['charts/user/following', ep___charts_user_following],
|
||||
['charts/user/notes', ep___charts_user_notes],
|
||||
['charts/user/pv', ep___charts_user_pv],
|
||||
['charts/user/reactions', ep___charts_user_reactions],
|
||||
['charts/users', ep___charts_users],
|
||||
['clips/add-note', ep___clips_addNote],
|
||||
['clips/remove-note', ep___clips_removeNote],
|
||||
['clips/create', ep___clips_create],
|
||||
['clips/delete', ep___clips_delete],
|
||||
['clips/list', ep___clips_list],
|
||||
['clips/notes', ep___clips_notes],
|
||||
['clips/show', ep___clips_show],
|
||||
['clips/update', ep___clips_update],
|
||||
['clips/favorite', ep___clips_favorite],
|
||||
['clips/unfavorite', ep___clips_unfavorite],
|
||||
['clips/my-favorites', ep___clips_myFavorites],
|
||||
['drive', ep___drive],
|
||||
['drive/files', ep___drive_files],
|
||||
['drive/files/attached-notes', ep___drive_files_attachedNotes],
|
||||
['drive/files/check-existence', ep___drive_files_checkExistence],
|
||||
['drive/files/create', ep___drive_files_create],
|
||||
['drive/files/delete', ep___drive_files_delete],
|
||||
['drive/files/find-by-hash', ep___drive_files_findByHash],
|
||||
['drive/files/find', ep___drive_files_find],
|
||||
['drive/files/show', ep___drive_files_show],
|
||||
['drive/files/update', ep___drive_files_update],
|
||||
['drive/files/upload-from-url', ep___drive_files_uploadFromUrl],
|
||||
['drive/folders', ep___drive_folders],
|
||||
['drive/folders/create', ep___drive_folders_create],
|
||||
['drive/folders/delete', ep___drive_folders_delete],
|
||||
['drive/folders/find', ep___drive_folders_find],
|
||||
['drive/folders/show', ep___drive_folders_show],
|
||||
['drive/folders/update', ep___drive_folders_update],
|
||||
['drive/stream', ep___drive_stream],
|
||||
['email-address/available', ep___emailAddress_available],
|
||||
['endpoint', ep___endpoint],
|
||||
['endpoints', ep___endpoints],
|
||||
['export-custom-emojis', ep___exportCustomEmojis],
|
||||
['federation/followers', ep___federation_followers],
|
||||
['federation/following', ep___federation_following],
|
||||
['federation/instances', ep___federation_instances],
|
||||
['federation/show-instance', ep___federation_showInstance],
|
||||
['federation/update-remote-user', ep___federation_updateRemoteUser],
|
||||
['federation/users', ep___federation_users],
|
||||
['federation/stats', ep___federation_stats],
|
||||
['following/create', ep___following_create],
|
||||
['following/delete', ep___following_delete],
|
||||
['following/update', ep___following_update],
|
||||
['following/update-all', ep___following_update_all],
|
||||
['following/invalidate', ep___following_invalidate],
|
||||
['following/requests/accept', ep___following_requests_accept],
|
||||
['following/requests/cancel', ep___following_requests_cancel],
|
||||
['following/requests/list', ep___following_requests_list],
|
||||
['following/requests/sent', ep___following_requests_sent],
|
||||
['following/requests/reject', ep___following_requests_reject],
|
||||
['gallery/featured', ep___gallery_featured],
|
||||
['gallery/popular', ep___gallery_popular],
|
||||
['gallery/posts', ep___gallery_posts],
|
||||
['gallery/posts/create', ep___gallery_posts_create],
|
||||
['gallery/posts/delete', ep___gallery_posts_delete],
|
||||
['gallery/posts/like', ep___gallery_posts_like],
|
||||
['gallery/posts/show', ep___gallery_posts_show],
|
||||
['gallery/posts/unlike', ep___gallery_posts_unlike],
|
||||
['gallery/posts/update', ep___gallery_posts_update],
|
||||
['get-online-users-count', ep___getOnlineUsersCount],
|
||||
['get-avatar-decorations', ep___getAvatarDecorations],
|
||||
['hashtags/list', ep___hashtags_list],
|
||||
['hashtags/search', ep___hashtags_search],
|
||||
['hashtags/show', ep___hashtags_show],
|
||||
['hashtags/trend', ep___hashtags_trend],
|
||||
['hashtags/users', ep___hashtags_users],
|
||||
['i', ep___i],
|
||||
['i/2fa/done', ep___i_2fa_done],
|
||||
['i/2fa/key-done', ep___i_2fa_keyDone],
|
||||
['i/2fa/password-less', ep___i_2fa_passwordLess],
|
||||
['i/2fa/register-key', ep___i_2fa_registerKey],
|
||||
['i/2fa/register', ep___i_2fa_register],
|
||||
['i/2fa/update-key', ep___i_2fa_updateKey],
|
||||
['i/2fa/remove-key', ep___i_2fa_removeKey],
|
||||
['i/2fa/unregister', ep___i_2fa_unregister],
|
||||
['i/apps', ep___i_apps],
|
||||
['i/authorized-apps', ep___i_authorizedApps],
|
||||
['i/claim-achievement', ep___i_claimAchievement],
|
||||
['i/change-password', ep___i_changePassword],
|
||||
['i/delete-account', ep___i_deleteAccount],
|
||||
['i/export-data', ep___i_exportData],
|
||||
['i/export-blocking', ep___i_exportBlocking],
|
||||
['i/export-following', ep___i_exportFollowing],
|
||||
['i/export-mute', ep___i_exportMute],
|
||||
['i/export-notes', ep___i_exportNotes],
|
||||
['i/export-clips', ep___i_exportClips],
|
||||
['i/export-favorites', ep___i_exportFavorites],
|
||||
['i/export-user-lists', ep___i_exportUserLists],
|
||||
['i/export-antennas', ep___i_exportAntennas],
|
||||
['i/favorites', ep___i_favorites],
|
||||
['i/gallery/likes', ep___i_gallery_likes],
|
||||
['i/gallery/posts', ep___i_gallery_posts],
|
||||
['i/import-blocking', ep___i_importBlocking],
|
||||
['i/import-following', ep___i_importFollowing],
|
||||
['i/import-notes', ep___i_importNotes],
|
||||
['i/import-muting', ep___i_importMuting],
|
||||
['i/import-user-lists', ep___i_importUserLists],
|
||||
['i/import-antennas', ep___i_importAntennas],
|
||||
['i/notifications', ep___i_notifications],
|
||||
['i/notifications-grouped', ep___i_notificationsGrouped],
|
||||
['i/page-likes', ep___i_pageLikes],
|
||||
['i/pages', ep___i_pages],
|
||||
['i/pin', ep___i_pin],
|
||||
['i/read-all-unread-notes', ep___i_readAllUnreadNotes],
|
||||
['i/read-announcement', ep___i_readAnnouncement],
|
||||
['i/regenerate-token', ep___i_regenerateToken],
|
||||
['i/registry/get-all', ep___i_registry_getAll],
|
||||
['i/registry/get-unsecure', ep___i_registry_getUnsecure],
|
||||
['i/registry/get-detail', ep___i_registry_getDetail],
|
||||
['i/registry/get', ep___i_registry_get],
|
||||
['i/registry/keys-with-type', ep___i_registry_keysWithType],
|
||||
['i/registry/keys', ep___i_registry_keys],
|
||||
['i/registry/remove', ep___i_registry_remove],
|
||||
['i/registry/scopes-with-domain', ep___i_registry_scopesWithDomain],
|
||||
['i/registry/set', ep___i_registry_set],
|
||||
['i/revoke-token', ep___i_revokeToken],
|
||||
['i/signin-history', ep___i_signinHistory],
|
||||
['i/unpin', ep___i_unpin],
|
||||
['i/update-email', ep___i_updateEmail],
|
||||
['i/update', ep___i_update],
|
||||
['i/move', ep___i_move],
|
||||
['i/webhooks/create', ep___i_webhooks_create],
|
||||
['i/webhooks/list', ep___i_webhooks_list],
|
||||
['i/webhooks/show', ep___i_webhooks_show],
|
||||
['i/webhooks/update', ep___i_webhooks_update],
|
||||
['i/webhooks/delete', ep___i_webhooks_delete],
|
||||
['i/webhooks/test', ep___i_webhooks_test],
|
||||
['invite/create', ep___invite_create],
|
||||
['invite/delete', ep___invite_delete],
|
||||
['invite/list', ep___invite_list],
|
||||
['invite/limit', ep___invite_limit],
|
||||
['meta', ep___meta],
|
||||
['emojis', ep___emojis],
|
||||
['emoji', ep___emoji],
|
||||
['miauth/gen-token', ep___miauth_genToken],
|
||||
['mute/create', ep___mute_create],
|
||||
['mute/delete', ep___mute_delete],
|
||||
['mute/list', ep___mute_list],
|
||||
['renote-mute/create', ep___renoteMute_create],
|
||||
['renote-mute/delete', ep___renoteMute_delete],
|
||||
['renote-mute/list', ep___renoteMute_list],
|
||||
['my/apps', ep___my_apps],
|
||||
['notes', ep___notes],
|
||||
['notes/children', ep___notes_children],
|
||||
['notes/clips', ep___notes_clips],
|
||||
['notes/conversation', ep___notes_conversation],
|
||||
['notes/create', ep___notes_create],
|
||||
['notes/delete', ep___notes_delete],
|
||||
['notes/favorites/create', ep___notes_favorites_create],
|
||||
['notes/favorites/delete', ep___notes_favorites_delete],
|
||||
['notes/featured', ep___notes_featured],
|
||||
['notes/following', ep___notes_following],
|
||||
['notes/global-timeline', ep___notes_globalTimeline],
|
||||
['notes/bubble-timeline', ep___notes_bubbleTimeline],
|
||||
['notes/hybrid-timeline', ep___notes_hybridTimeline],
|
||||
['notes/local-timeline', ep___notes_localTimeline],
|
||||
['notes/mentions', ep___notes_mentions],
|
||||
['notes/polls/recommendation', ep___notes_polls_recommendation],
|
||||
['notes/polls/vote', ep___notes_polls_vote],
|
||||
['notes/polls/refresh', ep___notes_polls_refresh],
|
||||
['notes/reactions', ep___notes_reactions],
|
||||
['notes/reactions/create', ep___notes_reactions_create],
|
||||
['notes/reactions/delete', ep___notes_reactions_delete],
|
||||
['notes/like', ep___notes_like],
|
||||
['notes/renotes', ep___notes_renotes],
|
||||
['notes/replies', ep___notes_replies],
|
||||
['notes/schedule/create', ep___notes_schedule_create],
|
||||
['notes/schedule/delete', ep___notes_schedule_delete],
|
||||
['notes/schedule/list', ep___notes_schedule_list],
|
||||
['notes/search-by-tag', ep___notes_searchByTag],
|
||||
['notes/search', ep___notes_search],
|
||||
['notes/show', ep___notes_show],
|
||||
['notes/state', ep___notes_state],
|
||||
['notes/thread-muting/create', ep___notes_threadMuting_create],
|
||||
['notes/thread-muting/delete', ep___notes_threadMuting_delete],
|
||||
['notes/timeline', ep___notes_timeline],
|
||||
['notes/translate', ep___notes_translate],
|
||||
['notes/unrenote', ep___notes_unrenote],
|
||||
['notes/user-list-timeline', ep___notes_userListTimeline],
|
||||
['notes/edit', ep___notes_edit],
|
||||
['notes/versions', ep___notes_versions],
|
||||
['notifications/create', ep___notifications_create],
|
||||
['notifications/flush', ep___notifications_flush],
|
||||
['notifications/mark-all-as-read', ep___notifications_markAllAsRead],
|
||||
['notifications/test-notification', ep___notifications_testNotification],
|
||||
['page-push', ep___pagePush],
|
||||
['pages/create', ep___pages_create],
|
||||
['pages/delete', ep___pages_delete],
|
||||
['pages/featured', ep___pages_featured],
|
||||
['pages/like', ep___pages_like],
|
||||
['pages/show', ep___pages_show],
|
||||
['pages/unlike', ep___pages_unlike],
|
||||
['pages/update', ep___pages_update],
|
||||
['flash/create', ep___flash_create],
|
||||
['flash/delete', ep___flash_delete],
|
||||
['flash/featured', ep___flash_featured],
|
||||
['flash/like', ep___flash_like],
|
||||
['flash/show', ep___flash_show],
|
||||
['flash/unlike', ep___flash_unlike],
|
||||
['flash/update', ep___flash_update],
|
||||
['flash/my', ep___flash_my],
|
||||
['flash/my-likes', ep___flash_myLikes],
|
||||
['ping', ep___ping],
|
||||
['pinned-users', ep___pinnedUsers],
|
||||
['promo/read', ep___promo_read],
|
||||
['roles/list', ep___roles_list],
|
||||
['roles/show', ep___roles_show],
|
||||
['roles/users', ep___roles_users],
|
||||
['roles/notes', ep___roles_notes],
|
||||
['request-reset-password', ep___requestResetPassword],
|
||||
['reset-db', ep___resetDb],
|
||||
['reset-password', ep___resetPassword],
|
||||
['server-info', ep___serverInfo],
|
||||
['stats', ep___stats],
|
||||
['sw/show-registration', ep___sw_show_registration],
|
||||
['sw/update-registration', ep___sw_update_registration],
|
||||
['sw/register', ep___sw_register],
|
||||
['sw/unregister', ep___sw_unregister],
|
||||
['test', ep___test],
|
||||
['username/available', ep___username_available],
|
||||
['users', ep___users],
|
||||
['users/clips', ep___users_clips],
|
||||
['users/followers', ep___users_followers],
|
||||
['users/following', ep___users_following],
|
||||
['users/gallery/posts', ep___users_gallery_posts],
|
||||
['users/get-frequently-replied-users', ep___users_getFrequentlyRepliedUsers],
|
||||
['users/featured-notes', ep___users_featuredNotes],
|
||||
['users/lists/create', ep___users_lists_create],
|
||||
['users/lists/delete', ep___users_lists_delete],
|
||||
['users/lists/list', ep___users_lists_list],
|
||||
['users/lists/pull', ep___users_lists_pull],
|
||||
['users/lists/push', ep___users_lists_push],
|
||||
['users/lists/show', ep___users_lists_show],
|
||||
['users/lists/favorite', ep___users_lists_favorite],
|
||||
['users/lists/unfavorite', ep___users_lists_unfavorite],
|
||||
['users/lists/update', ep___users_lists_update],
|
||||
['users/lists/create-from-public', ep___users_lists_createFromPublic],
|
||||
['users/lists/update-membership', ep___users_lists_updateMembership],
|
||||
['users/lists/get-memberships', ep___users_lists_getMemberships],
|
||||
['users/notes', ep___users_notes],
|
||||
['users/pages', ep___users_pages],
|
||||
['users/flashs', ep___users_flashs],
|
||||
['users/reactions', ep___users_reactions],
|
||||
['users/recommendation', ep___users_recommendation],
|
||||
['users/relation', ep___users_relation],
|
||||
['users/report-abuse', ep___users_reportAbuse],
|
||||
['users/search-by-username-and-host', ep___users_searchByUsernameAndHost],
|
||||
['users/search', ep___users_search],
|
||||
['users/show', ep___users_show],
|
||||
['users/achievements', ep___users_achievements],
|
||||
['users/update-memo', ep___users_updateMemo],
|
||||
['fetch-rss', ep___fetchRss],
|
||||
['fetch-external-resources', ep___fetchExternalResources],
|
||||
['retention', ep___retention],
|
||||
['sponsors', ep___sponsors],
|
||||
['bubble-game/register', ep___bubbleGame_register],
|
||||
['bubble-game/ranking', ep___bubbleGame_ranking],
|
||||
['reversi/cancel-match', ep___reversi_cancelMatch],
|
||||
['reversi/games', ep___reversi_games],
|
||||
['reversi/match', ep___reversi_match],
|
||||
['reversi/invitations', ep___reversi_invitations],
|
||||
['reversi/show-game', ep___reversi_showGame],
|
||||
['reversi/surrender', ep___reversi_surrender],
|
||||
['reversi/verify', ep___reversi_verify],
|
||||
];
|
||||
import * as endpointsObject from './endpoint-list.js';
|
||||
|
||||
interface IEndpointMetaBase {
|
||||
readonly stability?: 'deprecated' | 'experimental' | 'stable';
|
||||
|
|
@ -922,7 +108,7 @@ export interface IEndpoint {
|
|||
params: Schema;
|
||||
}
|
||||
|
||||
const endpoints: IEndpoint[] = (eps as [string, any]).map(([name, ep]) => {
|
||||
const endpoints: IEndpoint[] = Object.entries(endpointsObject).map(([name, ep]) => {
|
||||
return {
|
||||
name: name,
|
||||
get meta() {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UsersRepository } from '@/models/_.js';
|
||||
import { MiAccessToken, MiUser } from '@/models/_.js';
|
||||
import { SignupService } from '@/core/SignupService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
||||
|
|
@ -16,6 +15,7 @@ import type { Config } from '@/config.js';
|
|||
import { ApiError } from '@/server/api/error.js';
|
||||
import { Packed } from '@/misc/json-schema.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
|
@ -97,6 +97,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private userEntityService: UserEntityService,
|
||||
private signupService: SignupService,
|
||||
private instanceActorService: InstanceActorService,
|
||||
private readonly moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, _me, token) => {
|
||||
const me = _me ? await this.usersRepository.findOneByOrFail({ id: _me.id }) : null;
|
||||
|
|
@ -138,6 +139,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
approved: true,
|
||||
});
|
||||
|
||||
if (me) {
|
||||
await this.moderationLogService.log(me, 'createAccount', {
|
||||
userId: account.id,
|
||||
userUsername: account.username,
|
||||
});
|
||||
}
|
||||
|
||||
const res = await this.userEntityService.pack(account, account, {
|
||||
schema: 'MeDetailed',
|
||||
includeSecrets: true,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { CaptchaService, supportedCaptchaProviders } from '@/core/CaptchaService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin', 'captcha'],
|
||||
|
||||
requireCredential: true,
|
||||
requireAdmin: true,
|
||||
|
||||
// 実態はmetaの取得であるため
|
||||
kind: 'read:admin:meta',
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
provider: {
|
||||
type: 'string',
|
||||
enum: supportedCaptchaProviders,
|
||||
},
|
||||
hcaptcha: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
siteKey: { type: 'string', nullable: true },
|
||||
secretKey: { type: 'string', nullable: true },
|
||||
},
|
||||
},
|
||||
mcaptcha: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
siteKey: { type: 'string', nullable: true },
|
||||
secretKey: { type: 'string', nullable: true },
|
||||
instanceUrl: { type: 'string', nullable: true },
|
||||
},
|
||||
},
|
||||
recaptcha: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
siteKey: { type: 'string', nullable: true },
|
||||
secretKey: { type: 'string', nullable: true },
|
||||
},
|
||||
},
|
||||
turnstile: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
siteKey: { type: 'string', nullable: true },
|
||||
secretKey: { type: 'string', nullable: true },
|
||||
},
|
||||
},
|
||||
fc: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
siteKey: { type: 'string', nullable: true },
|
||||
secretKey: { type: 'string', nullable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private captchaService: CaptchaService,
|
||||
) {
|
||||
super(meta, paramDef, async () => {
|
||||
return this.captchaService.get();
|
||||
});
|
||||
}
|
||||
}
|
||||
129
packages/backend/src/server/api/endpoints/admin/captcha/save.ts
Normal file
129
packages/backend/src/server/api/endpoints/admin/captcha/save.ts
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { captchaErrorCodes, CaptchaService, supportedCaptchaProviders } from '@/core/CaptchaService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin', 'captcha'],
|
||||
|
||||
requireCredential: true,
|
||||
requireAdmin: true,
|
||||
|
||||
// 実態はmetaの更新であるため
|
||||
kind: 'write:admin:meta',
|
||||
|
||||
errors: {
|
||||
invalidProvider: {
|
||||
message: 'Invalid provider.',
|
||||
code: 'INVALID_PROVIDER',
|
||||
id: '14bf7ae1-80cc-4363-acb2-4fd61d086af0',
|
||||
httpStatusCode: 400,
|
||||
},
|
||||
invalidParameters: {
|
||||
message: 'Invalid parameters.',
|
||||
code: 'INVALID_PARAMETERS',
|
||||
id: '26654194-410e-44e2-b42e-460ff6f92476',
|
||||
httpStatusCode: 400,
|
||||
},
|
||||
noResponseProvided: {
|
||||
message: 'No response provided.',
|
||||
code: 'NO_RESPONSE_PROVIDED',
|
||||
id: '40acbba8-0937-41fb-bb3f-474514d40afe',
|
||||
httpStatusCode: 400,
|
||||
},
|
||||
requestFailed: {
|
||||
message: 'Request failed.',
|
||||
code: 'REQUEST_FAILED',
|
||||
id: '0f4fe2f1-2c15-4d6e-b714-efbfcde231cd',
|
||||
httpStatusCode: 500,
|
||||
},
|
||||
verificationFailed: {
|
||||
message: 'Verification failed.',
|
||||
code: 'VERIFICATION_FAILED',
|
||||
id: 'c41c067f-24f3-4150-84b2-b5a3ae8c2214',
|
||||
httpStatusCode: 400,
|
||||
},
|
||||
unknown: {
|
||||
message: 'unknown',
|
||||
code: 'UNKNOWN',
|
||||
id: 'f868d509-e257-42a9-99c1-42614b031a97',
|
||||
httpStatusCode: 500,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
provider: {
|
||||
type: 'string',
|
||||
enum: supportedCaptchaProviders,
|
||||
},
|
||||
captchaResult: {
|
||||
type: 'string', nullable: true,
|
||||
},
|
||||
sitekey: {
|
||||
type: 'string', nullable: true,
|
||||
},
|
||||
secret: {
|
||||
type: 'string', nullable: true,
|
||||
},
|
||||
instanceUrl: {
|
||||
type: 'string', nullable: true,
|
||||
},
|
||||
},
|
||||
required: ['provider'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private captchaService: CaptchaService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps) => {
|
||||
const result = await this.captchaService.save(ps.provider, {
|
||||
sitekey: ps.sitekey,
|
||||
secret: ps.secret,
|
||||
instanceUrl: ps.instanceUrl,
|
||||
captchaResult: ps.captchaResult,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
switch (result.error.code) {
|
||||
case captchaErrorCodes.invalidProvider:
|
||||
throw new ApiError({
|
||||
...meta.errors.invalidProvider,
|
||||
message: result.error.message,
|
||||
});
|
||||
case captchaErrorCodes.invalidParameters:
|
||||
throw new ApiError({
|
||||
...meta.errors.invalidParameters,
|
||||
message: result.error.message,
|
||||
});
|
||||
case captchaErrorCodes.noResponseProvided:
|
||||
throw new ApiError({
|
||||
...meta.errors.noResponseProvided,
|
||||
message: result.error.message,
|
||||
});
|
||||
case captchaErrorCodes.requestFailed:
|
||||
throw new ApiError({
|
||||
...meta.errors.requestFailed,
|
||||
message: result.error.message,
|
||||
});
|
||||
case captchaErrorCodes.verificationFailed:
|
||||
throw new ApiError({
|
||||
...meta.errors.verificationFailed,
|
||||
message: result.error.message,
|
||||
});
|
||||
default:
|
||||
throw new ApiError(meta.errors.unknown);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
67
packages/backend/src/server/api/endpoints/admin/cw-user.ts
Normal file
67
packages/backend/src/server/api/endpoints/admin/cw-user.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UsersRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:cw-user',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
cw: { type: 'string', nullable: true },
|
||||
},
|
||||
required: ['userId', 'cw'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.usersRepository)
|
||||
private readonly usersRepository: UsersRepository,
|
||||
|
||||
private readonly globalEventService: GlobalEventService,
|
||||
private readonly cacheService: CacheService,
|
||||
private readonly moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const user = await this.cacheService.findUserById(ps.userId);
|
||||
|
||||
// Skip if there's nothing to do
|
||||
if (user.mandatoryCW === ps.cw) return;
|
||||
|
||||
// Log event first.
|
||||
// This ensures that we don't "lose" the log if an error occurs
|
||||
await this.moderationLogService.log(me, 'setMandatoryCW', {
|
||||
newCW: ps.cw,
|
||||
oldCW: user.mandatoryCW,
|
||||
userId: user.id,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
});
|
||||
|
||||
await this.usersRepository.update(ps.userId, {
|
||||
// Collapse empty strings to null
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
mandatoryCW: ps.cw || null,
|
||||
});
|
||||
|
||||
// Synchronize caches and other processes
|
||||
this.globalEventService.publishInternalEvent('localUserUpdated', { id: ps.userId });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,8 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
|
|||
import type { DriveFilesRepository } from '@/models/_.js';
|
||||
import { DriveService } from '@/core/DriveService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
|
@ -30,14 +32,23 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
constructor(
|
||||
@Inject(DI.driveFilesRepository)
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
|
||||
private readonly cacheService: CacheService,
|
||||
private readonly moderationLogService: ModerationLogService,
|
||||
private driveService: DriveService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const user = await this.cacheService.findUserById(ps.userId);
|
||||
const files = await this.driveFilesRepository.findBy({
|
||||
userId: ps.userId,
|
||||
});
|
||||
|
||||
await this.moderationLogService.log(me, 'clearUserFiles', {
|
||||
userId: ps.userId,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
count: files.length,
|
||||
});
|
||||
|
||||
for (const file of files) {
|
||||
this.driveService.deleteFile(file, false, me);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
|
@ -25,9 +26,11 @@ export const paramDef = {
|
|||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private queueService: QueueService,
|
||||
private readonly moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
this.queueService.createCleanRemoteFilesJob();
|
||||
await this.moderationLogService.log(me, 'clearRemoteFiles', {});
|
||||
await this.queueService.createCleanRemoteFilesJob();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
|
|||
import type { DriveFilesRepository } from '@/models/_.js';
|
||||
import { DriveService } from '@/core/DriveService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
|
@ -29,7 +30,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
constructor(
|
||||
@Inject(DI.driveFilesRepository)
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
|
||||
private readonly moderationLogService: ModerationLogService,
|
||||
private driveService: DriveService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
|
|
@ -37,6 +38,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
userId: IsNull(),
|
||||
});
|
||||
|
||||
await this.moderationLogService.log(me, 'clearOwnerlessFiles', {
|
||||
count: files.length,
|
||||
});
|
||||
|
||||
for (const file of files) {
|
||||
this.driveService.deleteFile(file);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
|
@ -32,8 +33,13 @@ export const paramDef = {
|
|||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private customEmojiService: CustomEmojiService,
|
||||
private readonly moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
await this.moderationLogService.log(me, 'updateCustomEmojis', {
|
||||
ids: ps.ids,
|
||||
addAliases: ps.aliases,
|
||||
});
|
||||
await this.customEmojiService.addAliasesBulk(ps.ids, ps.aliases.map(a => a.normalize('NFC')));
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import type { DriveFilesRepository } from '@/models/_.js';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
|
||||
import { FILE_TYPE_IMAGE } from '@/const.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
|
|
@ -24,6 +25,11 @@ export const meta = {
|
|||
code: 'NO_SUCH_FILE',
|
||||
id: 'fc46b5a4-6b92-4c33-ac66-b806659bb5cf',
|
||||
},
|
||||
unsupportedFileType: {
|
||||
message: 'Unsupported file type.',
|
||||
code: 'UNSUPPORTED_FILE_TYPE',
|
||||
id: 'f7599d96-8750-af68-1633-9575d625c1a7',
|
||||
},
|
||||
duplicateName: {
|
||||
message: 'Duplicate name.',
|
||||
code: 'DUPLICATE_NAME',
|
||||
|
|
@ -47,15 +53,21 @@ export const paramDef = {
|
|||
nullable: true,
|
||||
description: 'Use `null` to reset the category.',
|
||||
},
|
||||
aliases: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
aliases: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
license: { type: 'string', nullable: true },
|
||||
isSensitive: { type: 'boolean' },
|
||||
localOnly: { type: 'boolean' },
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ['name', 'fileId'],
|
||||
} as const;
|
||||
|
|
@ -67,9 +79,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
constructor(
|
||||
@Inject(DI.driveFilesRepository)
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
|
||||
private customEmojiService: CustomEmojiService,
|
||||
|
||||
private emojiEntityService: EmojiEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
|
|
@ -78,11 +88,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
|
||||
const isDuplicate = await this.customEmojiService.checkDuplicate(nameNfc);
|
||||
if (isDuplicate) throw new ApiError(meta.errors.duplicateName);
|
||||
if (!FILE_TYPE_IMAGE.includes(driveFile.type)) throw new ApiError(meta.errors.unsupportedFileType);
|
||||
|
||||
if (driveFile.user !== null) await this.driveFilesRepository.update(driveFile.id, { user: null });
|
||||
|
||||
const emoji = await this.customEmojiService.add({
|
||||
driveFile,
|
||||
originalUrl: driveFile.url,
|
||||
publicUrl: driveFile.webpublicUrl ?? driveFile.url,
|
||||
fileType: driveFile.webpublicType ?? driveFile.type,
|
||||
name: nameNfc,
|
||||
category: ps.category?.normalize('NFC') ?? null,
|
||||
aliases: ps.aliases?.map(a => a.normalize('NFC')) ?? [],
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { IsNull } from 'typeorm';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { EmojisRepository } from '@/models/_.js';
|
||||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||
|
|
@ -88,10 +87,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
if (isDuplicate) throw new ApiError(meta.errors.duplicateName);
|
||||
|
||||
const addedEmoji = await this.customEmojiService.add({
|
||||
driveFile,
|
||||
originalUrl: driveFile.url,
|
||||
publicUrl: driveFile.webpublicUrl ?? driveFile.url,
|
||||
fileType: driveFile.webpublicType ?? driveFile.type,
|
||||
name: nameNfc,
|
||||
category: emoji.category?.normalize('NFC') ?? null,
|
||||
aliases: emoji.aliases?.map(a => a.normalize('NFC')),
|
||||
aliases: emoji.aliases.map(a => a.normalize('NFC')),
|
||||
host: null,
|
||||
license: emoji.license,
|
||||
isSensitive: emoji.isSensitive,
|
||||
|
|
|
|||
|
|
@ -3,9 +3,12 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import type { DriveFilesRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
||||
export const meta = {
|
||||
secure: true,
|
||||
|
|
@ -25,9 +28,16 @@ export const paramDef = {
|
|||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private queueService: QueueService,
|
||||
private readonly moderationLogService: ModerationLogService,
|
||||
@Inject(DI.driveFilesRepository)
|
||||
private readonly driveFilesRepository: DriveFilesRepository,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
this.queueService.createImportCustomEmojisJob(me, ps.fileId);
|
||||
const file = await driveFilesRepository.findOneByOrFail({ id: ps.fileId });
|
||||
await this.moderationLogService.log(me, 'importCustomEmojis', {
|
||||
fileName: file.name,
|
||||
});
|
||||
await this.queueService.createImportCustomEmojisJob(me, ps.fileId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
|
@ -32,8 +33,13 @@ export const paramDef = {
|
|||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private customEmojiService: CustomEmojiService,
|
||||
private readonly moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
await this.moderationLogService.log(me, 'updateCustomEmojis', {
|
||||
ids: ps.ids,
|
||||
delAliases: ps.aliases,
|
||||
});
|
||||
await this.customEmojiService.removeAliasesBulk(ps.ids, ps.aliases.map(a => a.normalize('NFC')));
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
|
@ -32,8 +33,13 @@ export const paramDef = {
|
|||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private customEmojiService: CustomEmojiService,
|
||||
private readonly moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
await this.moderationLogService.log(me, 'updateCustomEmojis', {
|
||||
ids: ps.ids,
|
||||
setAliases: ps.aliases,
|
||||
});
|
||||
await this.customEmojiService.setAliasesBulk(ps.ids, ps.aliases.map(a => a.normalize('NFC')));
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
|
@ -34,8 +35,13 @@ export const paramDef = {
|
|||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private customEmojiService: CustomEmojiService,
|
||||
private readonly moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
await this.moderationLogService.log(me, 'updateCustomEmojis', {
|
||||
ids: ps.ids,
|
||||
category: ps.category,
|
||||
});
|
||||
await this.customEmojiService.setCategoryBulk(ps.ids, ps.category?.normalize('NFC') ?? null);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
|
@ -34,8 +35,13 @@ export const paramDef = {
|
|||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private customEmojiService: CustomEmojiService,
|
||||
private readonly moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
await this.moderationLogService.log(me, 'updateCustomEmojis', {
|
||||
ids: ps.ids,
|
||||
license: ps.license,
|
||||
});
|
||||
await this.customEmojiService.setLicenseBulk(ps.ids, ps.license ?? null);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,7 +86,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
const error = await this.customEmojiService.update({
|
||||
...required,
|
||||
driveFile,
|
||||
originalUrl: driveFile != null ? driveFile.url : undefined,
|
||||
publicUrl: driveFile != null ? (driveFile.webpublicUrl ?? driveFile.url) : undefined,
|
||||
fileType: driveFile != null ? (driveFile.webpublicType ?? driveFile.type) : undefined,
|
||||
category: ps.category?.normalize('NFC'),
|
||||
aliases: ps.aliases?.map(a => a.normalize('NFC')),
|
||||
license: ps.license,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
|
|||
import type { DriveFilesRepository } from '@/models/_.js';
|
||||
import { DriveService } from '@/core/DriveService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
|
@ -30,7 +31,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
constructor(
|
||||
@Inject(DI.driveFilesRepository)
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
|
||||
private readonly moderationLogService: ModerationLogService,
|
||||
private driveService: DriveService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
|
|
@ -38,6 +39,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
userHost: ps.host,
|
||||
});
|
||||
|
||||
await this.moderationLogService.log(me, 'clearInstanceFiles', {
|
||||
host: ps.host,
|
||||
count: files.length,
|
||||
});
|
||||
|
||||
for (const file of files) {
|
||||
this.driveService.deleteFile(file);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
|
|||
import type { FollowingsRepository, UsersRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
|
@ -35,6 +36,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private followingsRepository: FollowingsRepository,
|
||||
|
||||
private queueService: QueueService,
|
||||
private readonly moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const followings = await this.followingsRepository.findBy([
|
||||
|
|
@ -51,6 +53,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
this.usersRepository.findOneByOrFail({ id: f.followeeId }),
|
||||
]).then(([from, to]) => [{ id: from.id }, { id: to.id }])));
|
||||
|
||||
await this.moderationLogService.log(me, 'severFollowRelations', {
|
||||
host: ps.host,
|
||||
});
|
||||
|
||||
this.queueService.createUnfollowJob(pairs.map(p => ({ from: p[0], to: p[1], silent: true })));
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ export const paramDef = {
|
|||
isNSFW: { type: 'boolean' },
|
||||
rejectReports: { type: 'boolean' },
|
||||
moderationNote: { type: 'string' },
|
||||
rejectQuotes: { type: 'boolean' },
|
||||
},
|
||||
required: ['host'],
|
||||
} as const;
|
||||
|
|
@ -59,6 +60,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
suspensionState,
|
||||
isNSFW: ps.isNSFW,
|
||||
rejectReports: ps.rejectReports,
|
||||
rejectQuotes: ps.rejectQuotes,
|
||||
moderationNote: ps.moderationNote,
|
||||
});
|
||||
|
||||
|
|
@ -92,6 +94,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
});
|
||||
}
|
||||
|
||||
if (ps.rejectQuotes != null && instance.rejectQuotes !== ps.rejectQuotes) {
|
||||
const message = ps.rejectReports ? 'rejectQuotesInstance' : 'acceptQuotesInstance';
|
||||
this.moderationLogService.log(me, message, {
|
||||
id: instance.id,
|
||||
host: instance.host,
|
||||
});
|
||||
}
|
||||
|
||||
if (ps.moderationNote != null && instance.moderationNote !== ps.moderationNote) {
|
||||
this.moderationLogService.log(me, 'updateRemoteInstanceNote', {
|
||||
id: instance.id,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: marie and sharkey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import webpush from 'web-push';
|
||||
const { generateVAPIDKeys } = webpush;
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:meta',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const keys = await generateVAPIDKeys();
|
||||
|
||||
return { public: keys.publicKey, private: keys.privateKey };
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -391,6 +391,10 @@ export const meta = {
|
|||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
robotsTxt: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
enableIdenticonGeneration: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
|
|
@ -708,6 +712,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
enableStatsForFederatedInstances: instance.enableStatsForFederatedInstances,
|
||||
enableServerMachineStats: instance.enableServerMachineStats,
|
||||
enableAchievements: instance.enableAchievements,
|
||||
robotsTxt: instance.robotsTxt,
|
||||
enableIdenticonGeneration: instance.enableIdenticonGeneration,
|
||||
bannedEmailDomains: instance.bannedEmailDomains,
|
||||
policies: { ...DEFAULT_POLICIES, ...instance.policies },
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@
|
|||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
|
||||
import type { UserProfilesRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
|
@ -27,22 +29,25 @@ export const paramDef = {
|
|||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
@Inject(DI.userProfilesRepository)
|
||||
private userProfilesRepository: UserProfilesRepository,
|
||||
private readonly userProfilesRepository: UserProfilesRepository,
|
||||
private readonly moderationLogService: ModerationLogService,
|
||||
private readonly cacheService: CacheService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const user = await this.usersRepository.findOneBy({ id: ps.userId });
|
||||
const user = await this.cacheService.findUserById(ps.userId);
|
||||
|
||||
if (user == null) {
|
||||
throw new Error('user not found');
|
||||
}
|
||||
await this.moderationLogService.log(me, 'nsfwUser', {
|
||||
userId: ps.userId,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
});
|
||||
|
||||
await this.userProfilesRepository.update(user.id, {
|
||||
alwaysMarkNsfw: true,
|
||||
});
|
||||
|
||||
await this.cacheService.userProfileCache.refresh(ps.userId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
|
|||
import type { PromoNotesRepository } from '@/models/_.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
|
|
@ -46,7 +48,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
constructor(
|
||||
@Inject(DI.promoNotesRepository)
|
||||
private promoNotesRepository: PromoNotesRepository,
|
||||
|
||||
private readonly moderationLogService: ModerationLogService,
|
||||
private readonly cacheService: CacheService,
|
||||
private getterService: GetterService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
|
|
@ -61,6 +64,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
throw new ApiError(meta.errors.alreadyPromoted);
|
||||
}
|
||||
|
||||
const user = await this.cacheService.findUserById(note.userId);
|
||||
await this.moderationLogService.log(me, 'createPromo', {
|
||||
noteId: note.id,
|
||||
noteUserId: user.id,
|
||||
noteUserUsername: user.username,
|
||||
noteUserHost: user.host,
|
||||
});
|
||||
|
||||
await this.promoNotesRepository.insert({
|
||||
noteId: note.id,
|
||||
expiresAt: new Date(ps.expiresAt),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UsersRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:reject-quotes',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
rejectQuotes: { type: 'boolean', nullable: false },
|
||||
},
|
||||
required: ['userId', 'rejectQuotes'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.usersRepository)
|
||||
private readonly usersRepository: UsersRepository,
|
||||
|
||||
private readonly globalEventService: GlobalEventService,
|
||||
private readonly cacheService: CacheService,
|
||||
private readonly moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const user = await this.cacheService.findUserById(ps.userId);
|
||||
|
||||
// Skip if there's nothing to do
|
||||
if (user.rejectQuotes === ps.rejectQuotes) return;
|
||||
|
||||
// Log event first.
|
||||
// This ensures that we don't "lose" the log if an error occurs
|
||||
await this.moderationLogService.log(me, ps.rejectQuotes ? 'rejectQuotesUser' : 'acceptQuotesUser', {
|
||||
userId: user.id,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
});
|
||||
|
||||
await this.usersRepository.update(ps.userId, {
|
||||
rejectQuotes: ps.rejectQuotes,
|
||||
});
|
||||
|
||||
// Synchronize caches and other processes
|
||||
this.globalEventService.publishInternalEvent('localUserUpdated', { id: ps.userId });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import { Injectable } from '@nestjs/common';
|
|||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { RelayService } from '@/core/RelayService.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
|
@ -64,6 +65,7 @@ export const paramDef = {
|
|||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private relayService: RelayService,
|
||||
private readonly moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
try {
|
||||
|
|
@ -72,6 +74,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
throw new ApiError(meta.errors.invalidUrl);
|
||||
}
|
||||
|
||||
await this.moderationLogService.log(me, 'addRelay', {
|
||||
inbox: ps.inbox,
|
||||
});
|
||||
|
||||
return await this.relayService.addRelay(ps.inbox);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { RelayService } from '@/core/RelayService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
|
@ -27,9 +28,13 @@ export const paramDef = {
|
|||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private relayService: RelayService,
|
||||
private readonly moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
return await this.relayService.removeRelay(ps.inbox);
|
||||
await this.moderationLogService.log(me, 'removeRelay', {
|
||||
inbox: ps.inbox,
|
||||
});
|
||||
await this.relayService.removeRelay(ps.inbox);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
|
|||
import type { UsersRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
|
@ -29,24 +32,32 @@ export const paramDef = {
|
|||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
private roleService: RoleService,
|
||||
private readonly usersRepository: UsersRepository,
|
||||
private readonly cacheService: CacheService,
|
||||
private readonly moderationLogService: ModerationLogService,
|
||||
private readonly roleService: RoleService,
|
||||
private readonly globalEventService: GlobalEventService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const user = await this.usersRepository.findOneBy({ id: ps.userId });
|
||||
|
||||
if (user == null) {
|
||||
throw new Error('user not found');
|
||||
}
|
||||
const user = await this.cacheService.findUserById(ps.userId);
|
||||
|
||||
if (await this.roleService.isModerator(user)) {
|
||||
throw new Error('cannot silence moderator account');
|
||||
}
|
||||
|
||||
await this.moderationLogService.log(me, 'silenceUser', {
|
||||
userId: ps.userId,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
});
|
||||
|
||||
await this.usersRepository.update(user.id, {
|
||||
isSilenced: true,
|
||||
});
|
||||
|
||||
this.globalEventService.publishInternalEvent(user.host == null ? 'localUserUpdated' : 'remoteUserUpdated', {
|
||||
id: user.id,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@
|
|||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
|
||||
import type { UserProfilesRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
|
@ -27,18 +29,19 @@ export const paramDef = {
|
|||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
private readonly cacheService: CacheService,
|
||||
private readonly moderationLogService: ModerationLogService,
|
||||
@Inject(DI.userProfilesRepository)
|
||||
private userProfilesRepository: UserProfilesRepository,
|
||||
private readonly userProfilesRepository: UserProfilesRepository,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const user = await this.usersRepository.findOneBy({ id: ps.userId });
|
||||
const user = await this.cacheService.findUserById(ps.userId);
|
||||
|
||||
if (user == null) {
|
||||
throw new Error('user not found');
|
||||
}
|
||||
await this.moderationLogService.log(me, 'unNsfwUser', {
|
||||
userId: ps.userId,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
});
|
||||
|
||||
await this.userProfilesRepository.update(user.id, {
|
||||
alwaysMarkNsfw: false,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UsersRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
|
@ -28,18 +31,27 @@ export const paramDef = {
|
|||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
private readonly usersRepository: UsersRepository,
|
||||
private readonly cacheService: CacheService,
|
||||
private readonly moderationLogService: ModerationLogService,
|
||||
private readonly globalEventService: GlobalEventService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const user = await this.usersRepository.findOneBy({ id: ps.userId });
|
||||
const user = await this.cacheService.findUserById(ps.userId);
|
||||
|
||||
if (user == null) {
|
||||
throw new Error('user not found');
|
||||
}
|
||||
await this.moderationLogService.log(me, 'unSilenceUser', {
|
||||
userId: ps.userId,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
});
|
||||
|
||||
await this.usersRepository.update(user.id, {
|
||||
isSilenced: false,
|
||||
});
|
||||
|
||||
this.globalEventService.publishInternalEvent(user.host == null ? 'localUserUpdated' : 'remoteUserUpdated', {
|
||||
id: user.id,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,6 +149,7 @@ export const paramDef = {
|
|||
enableStatsForFederatedInstances: { type: 'boolean' },
|
||||
enableServerMachineStats: { type: 'boolean' },
|
||||
enableAchievements: { type: 'boolean' },
|
||||
robotsTxt: { type: 'string', nullable: true },
|
||||
enableIdenticonGeneration: { type: 'boolean' },
|
||||
serverRules: { type: 'array', items: { type: 'string' } },
|
||||
bannedEmailDomains: { type: 'array', items: { type: 'string' } },
|
||||
|
|
@ -636,6 +637,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
set.enableAchievements = ps.enableAchievements;
|
||||
}
|
||||
|
||||
if (ps.robotsTxt !== undefined) {
|
||||
set.robotsTxt = ps.robotsTxt;
|
||||
}
|
||||
|
||||
if (ps.enableIdenticonGeneration !== undefined) {
|
||||
set.enableIdenticonGeneration = ps.enableIdenticonGeneration;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,10 @@
|
|||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { MiNote } from '@/models/Note.js';
|
||||
import type { MiLocalUser, MiUser } from '@/models/User.js';
|
||||
import { isActor, isPost, getApId } from '@/core/activitypub/type.js';
|
||||
import { isActor, isPost, getApId, getNullableApId } from '@/core/activitypub/type.js';
|
||||
import type { SchemaType } from '@/misc/json-schema.js';
|
||||
import { ApResolverService } from '@/core/activitypub/ApResolverService.js';
|
||||
import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
|
||||
|
|
@ -18,7 +17,10 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
|||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { ApRequestService } from '@/core/activitypub/ApRequestService.js';
|
||||
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['federation'],
|
||||
|
|
@ -26,12 +28,38 @@ export const meta = {
|
|||
requireCredential: true,
|
||||
kind: 'read:account',
|
||||
|
||||
// Up to 30 calls, then 1 per 1/2 second
|
||||
limit: {
|
||||
duration: ms('1minute'),
|
||||
max: 30,
|
||||
dripRate: 500,
|
||||
},
|
||||
|
||||
errors: {
|
||||
federationNotAllowed: {
|
||||
message: 'Federation for this host is not allowed.',
|
||||
code: 'FEDERATION_NOT_ALLOWED',
|
||||
id: '974b799e-1a29-4889-b706-18d4dd93e266',
|
||||
},
|
||||
uriInvalid: {
|
||||
message: 'URI is invalid.',
|
||||
code: 'URI_INVALID',
|
||||
id: '1a5eab56-e47b-48c2-8d5e-217b897d70db',
|
||||
},
|
||||
requestFailed: {
|
||||
message: 'Request failed.',
|
||||
code: 'REQUEST_FAILED',
|
||||
id: '81b539cf-4f57-4b29-bc98-032c33c0792e',
|
||||
},
|
||||
responseInvalid: {
|
||||
message: 'Response from remote server is invalid.',
|
||||
code: 'RESPONSE_INVALID',
|
||||
id: '70193c39-54f3-4813-82f0-70a680f7495b',
|
||||
},
|
||||
responseInvalidIdHostNotMatch: {
|
||||
message: 'Requested URI and response URI host does not match.',
|
||||
code: 'RESPONSE_INVALID_ID_HOST_NOT_MATCH',
|
||||
id: 'a2c9c61a-cb72-43ab-a964-3ca5fddb410a',
|
||||
},
|
||||
noSuchObject: {
|
||||
message: 'No such object.',
|
||||
code: 'NO_SUCH_OBJECT',
|
||||
|
|
@ -94,6 +122,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private apDbResolverService: ApDbResolverService,
|
||||
private apPersonService: ApPersonService,
|
||||
private apNoteService: ApNoteService,
|
||||
private readonly apRequestService: ApRequestService,
|
||||
private readonly instanceActorService: InstanceActorService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const object = await this.fetchAny(ps.uri, me);
|
||||
|
|
@ -110,7 +140,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
*/
|
||||
@bindThis
|
||||
private async fetchAny(uri: string, me: MiLocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
|
||||
if (!this.utilityService.isFederationAllowedUri(uri)) return null;
|
||||
if (!this.utilityService.isFederationAllowedUri(uri)) {
|
||||
throw new ApiError(meta.errors.federationNotAllowed);
|
||||
}
|
||||
|
||||
let local = await this.mergePack(me, ...await Promise.all([
|
||||
this.apDbResolverService.getUserFromApId(uri),
|
||||
|
|
@ -118,6 +150,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
]));
|
||||
if (local != null) return local;
|
||||
|
||||
// No local object found with that uri.
|
||||
// Before we fetch, resolve the URI in case it has a cross-origin redirect or anything like that.
|
||||
// Resolver.resolve() uses strict verification, which is overly paranoid for a user-provided lookup.
|
||||
uri = await this.resolveCanonicalUri(uri); // eslint-disable-line no-param-reassign
|
||||
if (!this.utilityService.isFederationAllowedUri(uri)) {
|
||||
throw new ApiError(meta.errors.federationNotAllowed);
|
||||
}
|
||||
|
||||
const host = this.utilityService.extractDbHost(uri);
|
||||
|
||||
// local object, not found in db? fail
|
||||
|
|
@ -125,7 +165,40 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
// リモートから一旦オブジェクトフェッチ
|
||||
const resolver = this.apResolverService.createResolver();
|
||||
const object = await resolver.resolve(uri) as any;
|
||||
const object = await resolver.resolve(uri).catch((err) => {
|
||||
if (err instanceof IdentifiableError) {
|
||||
switch (err.id) {
|
||||
// resolve
|
||||
case 'b94fd5b1-0e3b-4678-9df2-dad4cd515ab2':
|
||||
throw new ApiError(meta.errors.uriInvalid);
|
||||
case '0dc86cf6-7cd6-4e56-b1e6-5903d62d7ea5':
|
||||
case 'd592da9f-822f-4d91-83d7-4ceefabcf3d2':
|
||||
throw new ApiError(meta.errors.requestFailed);
|
||||
case '09d79f9e-64f1-4316-9cfa-e75c4d091574':
|
||||
throw new ApiError(meta.errors.federationNotAllowed);
|
||||
case '72180409-793c-4973-868e-5a118eb5519b':
|
||||
case 'ad2dc287-75c1-44c4-839d-3d2e64576675':
|
||||
throw new ApiError(meta.errors.responseInvalid);
|
||||
case 'fd93c2fa-69a8-440f-880b-bf178e0ec877':
|
||||
throw new ApiError(meta.errors.responseInvalidIdHostNotMatch);
|
||||
|
||||
// resolveLocal
|
||||
case '02b40cd0-fa92-4b0c-acc9-fb2ada952ab8':
|
||||
throw new ApiError(meta.errors.uriInvalid);
|
||||
case 'a9d946e5-d276-47f8-95fb-f04230289bb0':
|
||||
case '06ae3170-1796-4d93-a697-2611ea6d83b6':
|
||||
throw new ApiError(meta.errors.noSuchObject);
|
||||
case '7a5d2fc0-94bc-4db6-b8b8-1bf24a2e23d0':
|
||||
throw new ApiError(meta.errors.responseInvalid);
|
||||
}
|
||||
}
|
||||
|
||||
throw new ApiError(meta.errors.requestFailed);
|
||||
});
|
||||
|
||||
if (object.id == null) {
|
||||
throw new ApiError(meta.errors.responseInvalid);
|
||||
}
|
||||
|
||||
// /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する
|
||||
// これはDBに存在する可能性があるため再度DB検索
|
||||
|
|
@ -167,4 +240,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves an arbitrary URI to its canonical, post-redirect form.
|
||||
*/
|
||||
private async resolveCanonicalUri(uri: string): Promise<string> {
|
||||
const user = await this.instanceActorService.getInstanceActor();
|
||||
const res = await this.apRequestService.signedGet(uri, user, true);
|
||||
return getNullableApId(res) ?? uri;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@
|
|||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { DriveFilesRepository, ChannelsRepository } from '@/models/_.js';
|
||||
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import ms from 'ms';
|
||||
|
||||
export const meta = {
|
||||
tags: ['channels'],
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@
|
|||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Brackets } from 'typeorm';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { DriveFilesRepository } from '@/models/_.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
|
||||
import { Brackets } from 'typeorm';
|
||||
|
||||
export const meta = {
|
||||
tags: ['drive'],
|
||||
|
|
@ -45,7 +45,7 @@ export const paramDef = {
|
|||
folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
|
||||
type: { type: 'string', nullable: true, pattern: /^[a-zA-Z\/\-*]+$/.toString().slice(1, -1) },
|
||||
sort: { type: 'string', nullable: true, enum: ['+createdAt', '-createdAt', '+name', '-name', '+size', '-size', null] },
|
||||
searchQuery: { type: 'string', default: '' }
|
||||
searchQuery: { type: 'string', default: '' },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
import ms from 'ms';
|
||||
|
||||
export const meta = {
|
||||
tags: ['drive'],
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import { RoleService } from '@/core/RoleService.js';
|
|||
import { DriveService } from '@/core/DriveService.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
import ms from 'ms';
|
||||
|
||||
export const meta = {
|
||||
tags: ['drive'],
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export const paramDef = {
|
|||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
|
||||
searchQuery: { type: 'string', default: '' }
|
||||
searchQuery: { type: 'string', default: '' },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityServi
|
|||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
import ms from 'ms';
|
||||
|
||||
export const meta = {
|
||||
tags: ['drive'],
|
||||
|
|
|
|||
|
|
@ -13,10 +13,11 @@ export const meta = {
|
|||
|
||||
requireCredential: false,
|
||||
|
||||
// 2 calls per second
|
||||
// Up to 10 calls, then 4 / second.
|
||||
// This allows for reliable automation.
|
||||
limit: {
|
||||
duration: 1000,
|
||||
max: 2,
|
||||
max: 10,
|
||||
dripRate: 250,
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@
|
|||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import type { FlashsRepository, UsersRepository } from '@/models/_.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import ms from 'ms';
|
||||
|
||||
export const meta = {
|
||||
tags: ['flashs'],
|
||||
|
|
@ -78,7 +78,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
flashId: flash.id,
|
||||
flashUserId: flash.userId,
|
||||
flashUserUsername: user.username,
|
||||
flash,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@
|
|||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { GalleryPostsRepository, UsersRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
import ms from 'ms';
|
||||
|
||||
export const meta = {
|
||||
tags: ['gallery'],
|
||||
|
|
@ -78,7 +78,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
postId: post.id,
|
||||
postUserId: post.userId,
|
||||
postUserUsername: user.username,
|
||||
post,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -31,10 +31,12 @@ export const meta = {
|
|||
},
|
||||
},
|
||||
|
||||
// 3 calls per second
|
||||
// up to 20 calls, then 1 per second.
|
||||
// This handles bursty traffic when all tabs reload as a group
|
||||
limit: {
|
||||
duration: 1000,
|
||||
max: 3,
|
||||
max: 20,
|
||||
dripSize: 1,
|
||||
dripRate: 1000,
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@
|
|||
|
||||
import * as OTPAuth from 'otpauth';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import type { UserProfilesRepository } from '@/models/_.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import ms from 'ms';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
|
@ -14,7 +15,6 @@ import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/model
|
|||
import { WebAuthnService } from '@/core/WebAuthnService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { UserAuthService } from '@/core/UserAuthService.js';
|
||||
import ms from 'ms';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
|
|
@ -63,8 +63,8 @@ export const paramDef = {
|
|||
required: ['password', 'name', 'credential'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.userProfilesRepository)
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@
|
|||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/_.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
import ms from 'ms';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@
|
|||
//import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UserProfilesRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { WebAuthnService } from '@/core/WebAuthnService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { UserAuthService } from '@/core/UserAuthService.js';
|
||||
import ms from 'ms';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ import * as argon2 from 'argon2';
|
|||
import * as OTPAuth from 'otpauth';
|
||||
import * as QRCode from 'qrcode';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import type { UserProfilesRepository } from '@/models/_.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { UserAuthService } from '@/core/UserAuthService.js';
|
||||
import ms from 'ms';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/_.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
|
|
@ -13,7 +14,6 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { UserAuthService } from '@/core/UserAuthService.js';
|
||||
import ms from 'ms';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import type { UserProfilesRepository } from '@/models/_.js';
|
||||
|
|
@ -13,7 +14,6 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { UserAuthService } from '@/core/UserAuthService.js';
|
||||
import ms from 'ms';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@
|
|||
|
||||
//import bcrypt from 'bcryptjs';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UserSecurityKeysRepository } from '@/models/_.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
import ms from 'ms';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
name: token.name ?? token.app?.name,
|
||||
createdAt: this.idService.parse(token.id).date.toISOString(),
|
||||
lastUsedAt: token.lastUsedAt?.toISOString(),
|
||||
permission: token.permission,
|
||||
permission: token.app ? token.app.permission : token.permission,
|
||||
})));
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@
|
|||
//import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UserProfilesRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { UserAuthService } from '@/core/UserAuthService.js';
|
||||
import ms from 'ms';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { AchievementService, ACHIEVEMENT_TYPES } from '@/core/AchievementService.js';
|
||||
import type { MiMeta } from '@/models/_.js';
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@
|
|||
//import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DeleteAccountService } from '@/core/DeleteAccountService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { UserAuthService } from '@/core/UserAuthService.js';
|
||||
import ms from 'ms';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@
|
|||
//import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
|
||||
import generateUserToken from '@/misc/generate-native-user-token.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import ms from 'ms';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
|
|
|
|||
|
|
@ -133,6 +133,12 @@ export const meta = {
|
|||
id: '0b3f9f6a-2f4d-4b1f-9fb4-49d3a2fd7191',
|
||||
httpStatusCode: 422,
|
||||
},
|
||||
|
||||
maxCwLength: {
|
||||
message: 'You tried setting a default content warning which is too long.',
|
||||
code: 'MAX_CW_LENGTH',
|
||||
id: '7004c478-bda3-4b4f-acb2-4316398c9d52',
|
||||
},
|
||||
},
|
||||
|
||||
res: {
|
||||
|
|
@ -243,6 +249,12 @@ export const paramDef = {
|
|||
uniqueItems: true,
|
||||
items: { type: 'string' },
|
||||
},
|
||||
defaultCW: { type: 'string', nullable: true },
|
||||
defaultCWPriority: {
|
||||
type: 'string',
|
||||
enum: ['default', 'parent', 'defaultParent', 'parentDefault'],
|
||||
nullable: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
|
@ -494,6 +506,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
updates.alsoKnownAs = newAlsoKnownAs.size > 0 ? Array.from(newAlsoKnownAs) : null;
|
||||
}
|
||||
|
||||
let defaultCW = ps.defaultCW;
|
||||
if (defaultCW !== undefined) {
|
||||
if (defaultCW === '') defaultCW = null;
|
||||
if (defaultCW && defaultCW.length > this.config.maxCwLength) {
|
||||
throw new ApiError(meta.errors.maxCwLength);
|
||||
}
|
||||
|
||||
profileUpdates.defaultCW = defaultCW;
|
||||
}
|
||||
if (ps.defaultCWPriority !== undefined) {
|
||||
profileUpdates.defaultCWPriority = ps.defaultCWPriority;
|
||||
}
|
||||
|
||||
//#region emojis/tags
|
||||
|
||||
let emojis = [] as string[];
|
||||
|
|
@ -592,7 +617,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
const html = await this.httpRequestService.getHtml(url);
|
||||
|
||||
const { window } = new JSDOM(html);
|
||||
const doc = window.document;
|
||||
const doc: Document = window.document;
|
||||
|
||||
const myLink = `${this.config.url}/@${user.username}`;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@
|
|||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { MutingsRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { UserMutingService } from '@/core/UserMutingService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import ms from 'ms';
|
||||
|
||||
export const meta = {
|
||||
tags: ['account'],
|
||||
|
|
|
|||
|
|
@ -143,6 +143,12 @@ export const meta = {
|
|||
code: 'CONTAINS_TOO_MANY_MENTIONS',
|
||||
id: '4de0363a-3046-481b-9b0f-feff3e211025',
|
||||
},
|
||||
|
||||
quoteDisabledForUser: {
|
||||
message: 'You do not have permission to create quote posts.',
|
||||
code: 'QUOTE_DISABLED_FOR_USER',
|
||||
id: '1c0ea108-d1e3-4e8e-aa3f-4d2487626153',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
|
@ -415,6 +421,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
throw new ApiError(meta.errors.containsProhibitedWords);
|
||||
} else if (e.id === '9f466dab-c856-48cd-9e65-ff90ff750580') {
|
||||
throw new ApiError(meta.errors.containsTooManyMentions);
|
||||
} else if (e.id === '1c0ea108-d1e3-4e8e-aa3f-4d2487626153') {
|
||||
throw new ApiError(meta.errors.quoteDisabledForUser);
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
|
|
|
|||
|
|
@ -176,6 +176,12 @@ export const meta = {
|
|||
id: '33510210-8452-094c-6227-4a6c05d99f02',
|
||||
},
|
||||
|
||||
quoteDisabledForUser: {
|
||||
message: 'You do not have permission to create quote posts.',
|
||||
code: 'QUOTE_DISABLED_FOR_USER',
|
||||
id: '1c0ea108-d1e3-4e8e-aa3f-4d2487626153',
|
||||
},
|
||||
|
||||
containsProhibitedWords: {
|
||||
message: 'Cannot post because it contains prohibited words.',
|
||||
code: 'CONTAINS_PROHIBITED_WORDS',
|
||||
|
|
@ -469,6 +475,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
throw new ApiError(meta.errors.containsProhibitedWords);
|
||||
} else if (e.id === '9f466dab-c856-48cd-9e65-ff90ff750580') {
|
||||
throw new ApiError(meta.errors.containsTooManyMentions);
|
||||
} else if (e.id === '1c0ea108-d1e3-4e8e-aa3f-4d2487626153') {
|
||||
throw new ApiError(meta.errors.quoteDisabledForUser);
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@
|
|||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { NoteFavoritesRepository } from '@/models/_.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
import ms from 'ms';
|
||||
|
||||
export const meta = {
|
||||
tags: ['notes', 'favorites'],
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
|||
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['notes'],
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { ReactionService } from '@/core/ReactionService.js';
|
||||
|
|
|
|||
|
|
@ -14,12 +14,10 @@ import type {
|
|||
NotesRepository,
|
||||
BlockingsRepository,
|
||||
DriveFilesRepository,
|
||||
ChannelsRepository,
|
||||
NoteScheduleRepository,
|
||||
} from '@/models/_.js';
|
||||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||
import type { MiNote } from '@/models/Note.js';
|
||||
import type { MiChannel } from '@/models/Channel.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
|
|
@ -210,9 +208,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
@Inject(DI.driveFilesRepository)
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
|
||||
@Inject(DI.channelsRepository)
|
||||
private channelsRepository: ChannelsRepository,
|
||||
|
||||
private queueService: QueueService,
|
||||
private roleService: RoleService,
|
||||
private idService: IdService,
|
||||
|
|
|
|||
|
|
@ -7,12 +7,14 @@ import ms from 'ms';
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MiNote, MiNoteSchedule, NoteScheduleRepository } from '@/models/_.js';
|
||||
import type { MiNote, MiUser, MiNoteSchedule, NoteScheduleRepository, NotesRepository } from '@/models/_.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { Packed } from '@/misc/json-schema.js';
|
||||
import { noteVisibilities } from '@/types.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['notes'],
|
||||
|
|
@ -81,7 +83,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
@Inject(DI.noteScheduleRepository)
|
||||
private noteScheduleRepository: NoteScheduleRepository,
|
||||
|
||||
@Inject(DI.notesRepository)
|
||||
private notesRepository: NotesRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private noteEntityService: NoteEntityService,
|
||||
private driveFileEntityService: DriveFileEntityService,
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
|
|
@ -106,6 +112,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
userId: string;
|
||||
scheduledAt: string;
|
||||
}[] = await Promise.all(scheduleNotes.map(async (item: MiNoteSchedule) => {
|
||||
const renote = await this.fetchNote(item.note.renote, me);
|
||||
const reply = await this.fetchNote(item.note.reply, me);
|
||||
|
||||
return {
|
||||
...item,
|
||||
scheduledAt: item.scheduledAt.toISOString(),
|
||||
|
|
@ -115,12 +124,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
user: user,
|
||||
visibility: item.note.visibility ?? 'public',
|
||||
reactionAcceptance: item.note.reactionAcceptance ?? null,
|
||||
visibleUsers: item.note.visibleUsers ? await userEntityService.packMany(item.note.visibleUsers.map(u => u.id), me) : [],
|
||||
visibleUsers: item.note.visibleUsers ? await this.userEntityService.packMany(item.note.visibleUsers.map(u => u.id), me) : [],
|
||||
fileIds: item.note.files ? item.note.files : [],
|
||||
files: await this.driveFileEntityService.packManyByIds(item.note.files),
|
||||
createdAt: item.scheduledAt.toISOString(),
|
||||
isSchedule: true,
|
||||
id: item.id,
|
||||
renote, reply,
|
||||
renoteId: item.note.renote,
|
||||
replyId: item.note.reply,
|
||||
},
|
||||
};
|
||||
}));
|
||||
|
|
@ -128,4 +140,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
return scheduleNotesPack;
|
||||
});
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async fetchNote(
|
||||
id: MiNote['id'] | null | undefined,
|
||||
me: MiUser,
|
||||
): Promise<Packed<'Note'> | null> {
|
||||
if (id) {
|
||||
const note = await this.notesRepository.findOneBy({ id });
|
||||
if (note) {
|
||||
note.reactionAndUserPairCache ??= [];
|
||||
return this.noteEntityService.pack(note, me);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
|
||||
.andWhere('note.visibility = \'public\'')
|
||||
.andWhere("note.visibility IN ('public', 'home')") // keep in sync with NoteCreateService call to `hashtagService.updateHashtags()`
|
||||
.innerJoinAndSelect('note.user', 'user')
|
||||
.leftJoinAndSelect('note.reply', 'reply')
|
||||
.leftJoinAndSelect('note.renote', 'renote')
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { SearchService } from '@/core/SearchService.js';
|
||||
import { fileTypeCategories, SearchService } from '@/core/SearchService.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
|
@ -52,7 +52,11 @@ export const paramDef = {
|
|||
type: 'string',
|
||||
description: 'The local host is represented with `.`.',
|
||||
},
|
||||
filetype: { type: 'string', nullable: true },
|
||||
filetype: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
enum: fileTypeCategories,
|
||||
},
|
||||
userId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
|
||||
channelId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
|
||||
order: { type: 'string' },
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import ms from 'ms';
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { DriveFilesRepository, PagesRepository } from '@/models/_.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { MiPage } from '@/models/Page.js';
|
||||
import { MiPage, pageNameSchema } from '@/models/Page.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { PageEntityService } from '@/core/entities/PageEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
|
@ -51,7 +51,7 @@ export const paramDef = {
|
|||
type: 'object',
|
||||
properties: {
|
||||
title: { type: 'string' },
|
||||
name: { type: 'string', minLength: 1 },
|
||||
name: { ...pageNameSchema, minLength: 1 },
|
||||
summary: { type: 'string', nullable: true },
|
||||
content: { type: 'array', items: {
|
||||
type: 'object', additionalProperties: true,
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@
|
|||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import type { PagesRepository, UsersRepository } from '@/models/_.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import ms from 'ms';
|
||||
|
||||
export const meta = {
|
||||
tags: ['pages'],
|
||||
|
|
@ -79,7 +79,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
pageId: page.id,
|
||||
pageUserId: page.userId,
|
||||
pageUserUsername: user.username,
|
||||
page,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import type { PagesRepository, DriveFilesRepository } from '@/models/_.js';
|
|||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { pageNameSchema } from '@/models/Page.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['pages'],
|
||||
|
|
@ -31,13 +32,11 @@ export const meta = {
|
|||
code: 'NO_SUCH_PAGE',
|
||||
id: '21149b9e-3616-4778-9592-c4ce89f5a864',
|
||||
},
|
||||
|
||||
accessDenied: {
|
||||
message: 'Access denied.',
|
||||
code: 'ACCESS_DENIED',
|
||||
id: '3c15cd52-3b4b-4274-967d-6456fc4f792b',
|
||||
},
|
||||
|
||||
noSuchFile: {
|
||||
message: 'No such file.',
|
||||
code: 'NO_SUCH_FILE',
|
||||
|
|
@ -56,7 +55,7 @@ export const paramDef = {
|
|||
properties: {
|
||||
pageId: { type: 'string', format: 'misskey:id' },
|
||||
title: { type: 'string' },
|
||||
name: { type: 'string', minLength: 1 },
|
||||
name: { ...pageNameSchema, minLength: 1 },
|
||||
summary: { type: 'string', nullable: true },
|
||||
content: { type: 'array', items: {
|
||||
type: 'object', additionalProperties: true,
|
||||
|
|
@ -102,15 +101,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
}
|
||||
}
|
||||
|
||||
await this.pagesRepository.findBy({
|
||||
id: Not(ps.pageId),
|
||||
userId: me.id,
|
||||
name: ps.name,
|
||||
}).then(result => {
|
||||
if (result.length > 0) {
|
||||
throw new ApiError(meta.errors.nameAlreadyExists);
|
||||
}
|
||||
});
|
||||
if (ps.name != null) {
|
||||
await this.pagesRepository.findBy({
|
||||
id: Not(ps.pageId),
|
||||
userId: me.id,
|
||||
name: ps.name,
|
||||
}).then(result => {
|
||||
if (result.length > 0) {
|
||||
throw new ApiError(meta.errors.nameAlreadyExists);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await this.pagesRepository.update(page.id, {
|
||||
updatedAt: new Date(),
|
||||
|
|
|
|||
|
|
@ -64,10 +64,11 @@ export const meta = {
|
|||
},
|
||||
},
|
||||
|
||||
// 2 calls per second
|
||||
// 24 calls, then 7 per second-ish (1 for each type of server info graph)
|
||||
limit: {
|
||||
duration: 1000,
|
||||
max: 2,
|
||||
max: 24,
|
||||
dripSize: 7,
|
||||
dripRate: 900,
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,14 @@ export const meta = {
|
|||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
isInstanceMuted: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
memo: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -103,6 +111,14 @@ export const meta = {
|
|||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
isInstanceMuted: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
memo: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -57,10 +57,10 @@ export const meta = {
|
|||
},
|
||||
},
|
||||
|
||||
// 5 calls per 2 seconds
|
||||
// up to 50 calls @ 4 per second
|
||||
limit: {
|
||||
duration: 1000 * 2,
|
||||
max: 5,
|
||||
max: 50,
|
||||
dripRate: 250,
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
|
|
|||
126
packages/backend/src/server/api/endpoints/v2/admin/emoji/list.ts
Normal file
126
packages/backend/src/server/api/endpoints/v2/admin/emoji/list.ts
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
|
||||
import { CustomEmojiService, fetchEmojisHostTypes, fetchEmojisSortKeys } from '@/core/CustomEmojiService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'read:admin:emoji',
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
emojis: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
ref: 'EmojiDetailedAdmin',
|
||||
},
|
||||
},
|
||||
count: { type: 'integer' },
|
||||
allCount: { type: 'integer' },
|
||||
allPages: { type: 'integer' },
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
query: {
|
||||
type: 'object',
|
||||
nullable: true,
|
||||
properties: {
|
||||
updatedAtFrom: { type: 'string' },
|
||||
updatedAtTo: { type: 'string' },
|
||||
name: { type: 'string' },
|
||||
host: { type: 'string' },
|
||||
uri: { type: 'string' },
|
||||
publicUrl: { type: 'string' },
|
||||
originalUrl: { type: 'string' },
|
||||
type: { type: 'string' },
|
||||
aliases: { type: 'string' },
|
||||
category: { type: 'string' },
|
||||
license: { type: 'string' },
|
||||
isSensitive: { type: 'boolean' },
|
||||
localOnly: { type: 'boolean' },
|
||||
hostType: {
|
||||
type: 'string',
|
||||
enum: fetchEmojisHostTypes,
|
||||
default: 'all',
|
||||
},
|
||||
roleIds: {
|
||||
type: 'array',
|
||||
items: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
},
|
||||
},
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
page: { type: 'integer' },
|
||||
sortKeys: {
|
||||
type: 'array',
|
||||
default: ['-id'],
|
||||
items: {
|
||||
type: 'string',
|
||||
enum: fetchEmojisSortKeys,
|
||||
},
|
||||
},
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private customEmojiService: CustomEmojiService,
|
||||
private emojiEntityService: EmojiEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const q = ps.query;
|
||||
const result = await this.customEmojiService.fetchEmojis(
|
||||
{
|
||||
query: {
|
||||
updatedAtFrom: q?.updatedAtFrom,
|
||||
updatedAtTo: q?.updatedAtTo,
|
||||
name: q?.name,
|
||||
host: q?.host,
|
||||
uri: q?.uri,
|
||||
publicUrl: q?.publicUrl,
|
||||
type: q?.type,
|
||||
aliases: q?.aliases,
|
||||
category: q?.category,
|
||||
license: q?.license,
|
||||
isSensitive: q?.isSensitive,
|
||||
localOnly: q?.localOnly,
|
||||
hostType: q?.hostType,
|
||||
roleIds: q?.roleIds,
|
||||
},
|
||||
sinceId: ps.sinceId,
|
||||
untilId: ps.untilId,
|
||||
},
|
||||
{
|
||||
limit: ps.limit,
|
||||
page: ps.page,
|
||||
sortKeys: ps.sortKeys,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
emojis: await this.emojiEntityService.packDetailedAdminMany(result.emojis),
|
||||
count: result.count,
|
||||
allCount: result.allCount,
|
||||
allPages: result.allPages,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { IsNull } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import type { MiNote, NotesRepository } from '@/models/_.js';
|
||||
import type { MiLocalUser } from '@/models/User.js';
|
||||
import { ApiError } from '../error.js';
|
||||
|
||||
/**
|
||||
* Utility service for accessing data with Mastodon semantics
|
||||
*/
|
||||
@Injectable()
|
||||
export class MastodonDataService {
|
||||
constructor(
|
||||
@Inject(DI.notesRepository)
|
||||
private readonly notesRepository: NotesRepository,
|
||||
|
||||
@Inject(QueryService)
|
||||
private readonly queryService: QueryService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Fetches a note in the context of the current user, and throws an exception if not found.
|
||||
*/
|
||||
public async requireNote(noteId: string, me?: MiLocalUser | null): Promise<MiNote> {
|
||||
const note = await this.getNote(noteId, me);
|
||||
|
||||
if (!note) {
|
||||
throw new ApiError({
|
||||
message: 'No such note.',
|
||||
code: 'NO_SUCH_NOTE',
|
||||
id: '24fcbfc6-2e37-42b6-8388-c29b3861a08d',
|
||||
kind: 'client',
|
||||
httpStatusCode: 404,
|
||||
});
|
||||
}
|
||||
|
||||
return note;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a note in the context of the current user.
|
||||
*/
|
||||
public async getNote(noteId: string, me?: MiLocalUser | null): Promise<MiNote | null> {
|
||||
// Root query: note + required dependencies
|
||||
const query = this.notesRepository
|
||||
.createQueryBuilder('note')
|
||||
.where('note.id = :noteId', { noteId })
|
||||
.innerJoinAndSelect('note.user', 'user');
|
||||
|
||||
// Restrict visibility
|
||||
this.queryService.generateVisibilityQuery(query, me);
|
||||
if (me) {
|
||||
this.queryService.generateBlockedUserQuery(query, me);
|
||||
}
|
||||
|
||||
return await query.getOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks where the current user has made a reblog / boost / pure renote of a given target note.
|
||||
*/
|
||||
public async hasReblog(noteId: string, me: MiLocalUser | null | undefined): Promise<boolean> {
|
||||
if (!me) return false;
|
||||
|
||||
return await this.notesRepository.existsBy({
|
||||
// Reblog of the target note by me
|
||||
userId: me.id,
|
||||
renoteId: noteId,
|
||||
|
||||
// That is pure (not a quote)
|
||||
text: IsNull(),
|
||||
cw: IsNull(),
|
||||
replyId: IsNull(),
|
||||
hasPoll: false,
|
||||
fileIds: '{}',
|
||||
});
|
||||
}
|
||||
}
|
||||
39
packages/backend/src/server/api/mastodon/MastodonLogger.ts
Normal file
39
packages/backend/src/server/api/mastodon/MastodonLogger.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import Logger, { Data } from '@/logger.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
|
||||
@Injectable()
|
||||
export class MastodonLogger {
|
||||
public readonly logger: Logger;
|
||||
|
||||
constructor(loggerService: LoggerService) {
|
||||
this.logger = loggerService.getLogger('masto-api');
|
||||
}
|
||||
|
||||
public error(endpoint: string, error: Data): void {
|
||||
this.logger.error(`Error in mastodon API endpoint ${endpoint}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
export function getErrorData(error: unknown): Data {
|
||||
if (error == null) return {};
|
||||
if (typeof(error) === 'string') return error;
|
||||
if (typeof(error) === 'object') {
|
||||
if ('response' in error) {
|
||||
if (typeof(error.response) === 'object' && error.response) {
|
||||
if ('data' in error.response) {
|
||||
if (typeof(error.response.data) === 'object' && error.response.data) {
|
||||
return error.response.data as Record<string, unknown>;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return error as Record<string, unknown>;
|
||||
}
|
||||
return { error };
|
||||
}
|
||||
|
|
@ -9,18 +9,32 @@ import mfm from '@transfem-org/sfm-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 { IMentionedRemoteUsers, MiNote } from '@/models/Note.js';
|
||||
import type { MiLocalUser, MiUser } from '@/models/User.js';
|
||||
import type { NoteEditRepository, UserProfilesRepository } from '@/models/_.js';
|
||||
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { GetterService } from '../GetterService.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { MastodonDataService } from '@/server/api/mastodon/MastodonDataService.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
|
||||
export enum IdConvertType {
|
||||
MastodonId,
|
||||
SharkeyId,
|
||||
// Missing from Megalodon apparently
|
||||
// https://docs.joinmastodon.org/entities/StatusEdit/
|
||||
export interface StatusEdit {
|
||||
content: string;
|
||||
spoiler_text: string;
|
||||
sensitive: boolean;
|
||||
created_at: string;
|
||||
account: MastodonEntity.Account;
|
||||
poll?: {
|
||||
options: {
|
||||
title: string;
|
||||
}[]
|
||||
},
|
||||
media_attachments: MastodonEntity.Attachment[],
|
||||
emojis: MastodonEntity.Emoji[],
|
||||
}
|
||||
|
||||
export const escapeMFM = (text: string): string => text
|
||||
|
|
@ -36,27 +50,25 @@ export const escapeMFM = (text: string): string => text
|
|||
export class MastoConverters {
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
private readonly config: Config,
|
||||
|
||||
@Inject(DI.userProfilesRepository)
|
||||
private userProfilesRepository: UserProfilesRepository,
|
||||
private readonly userProfilesRepository: UserProfilesRepository,
|
||||
|
||||
@Inject(DI.noteEditRepository)
|
||||
private noteEditRepository: NoteEditRepository,
|
||||
private readonly noteEditRepository: NoteEditRepository,
|
||||
|
||||
private mfmService: MfmService,
|
||||
private getterService: GetterService,
|
||||
private customEmojiService: CustomEmojiService,
|
||||
private idService: IdService,
|
||||
private driveFileEntityService: DriveFileEntityService,
|
||||
) {
|
||||
}
|
||||
private readonly mfmService: MfmService,
|
||||
private readonly getterService: GetterService,
|
||||
private readonly customEmojiService: CustomEmojiService,
|
||||
private readonly idService: IdService,
|
||||
private readonly driveFileEntityService: DriveFileEntityService,
|
||||
private readonly mastodonDataService: MastodonDataService,
|
||||
) {}
|
||||
|
||||
private encode(u: MiUser, m: IMentionedRemoteUsers): Entity.Mention {
|
||||
private encode(u: MiUser, m: IMentionedRemoteUsers): MastodonEntity.Mention {
|
||||
let acct = u.username;
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
let acctUrl = `https://${u.host || this.config.host}/@${u.username}`;
|
||||
let url: string | null = null;
|
||||
if (u.host) {
|
||||
|
|
@ -89,7 +101,11 @@ export class MastoConverters {
|
|||
return 'unknown';
|
||||
}
|
||||
|
||||
public encodeFile(f: any): Entity.Attachment {
|
||||
public encodeFile(f: Packed<'DriveFile'>): MastodonEntity.Attachment {
|
||||
const { width, height } = f.properties;
|
||||
const size = (width && height) ? `${width}x${height}` : undefined;
|
||||
const aspect = (width && height) ? (width / height) : undefined;
|
||||
|
||||
return {
|
||||
id: f.id,
|
||||
type: this.fileType(f.type),
|
||||
|
|
@ -98,11 +114,19 @@ export class MastoConverters {
|
|||
preview_url: f.thumbnailUrl,
|
||||
text_url: f.url,
|
||||
meta: {
|
||||
width: f.properties.width,
|
||||
height: f.properties.height,
|
||||
original: {
|
||||
width,
|
||||
height,
|
||||
size,
|
||||
aspect,
|
||||
},
|
||||
width,
|
||||
height,
|
||||
size,
|
||||
aspect,
|
||||
},
|
||||
description: f.comment ? f.comment : null,
|
||||
blurhash: f.blurhash ? f.blurhash : null,
|
||||
description: f.comment ?? null,
|
||||
blurhash: f.blurhash ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -112,7 +136,7 @@ export class MastoConverters {
|
|||
});
|
||||
}
|
||||
|
||||
private async encodeField(f: Entity.Field): Promise<Entity.Field> {
|
||||
private async encodeField(f: Entity.Field): Promise<MastodonEntity.Field> {
|
||||
return {
|
||||
name: f.name,
|
||||
value: await this.mfmService.toMastoApiHtml(mfm.parse(f.value), [], true) ?? escapeMFM(f.value),
|
||||
|
|
@ -120,7 +144,7 @@ export class MastoConverters {
|
|||
};
|
||||
}
|
||||
|
||||
public async convertAccount(account: Entity.Account | MiUser) {
|
||||
public async convertAccount(account: Entity.Account | MiUser): Promise<MastodonEntity.Account> {
|
||||
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);
|
||||
|
|
@ -137,6 +161,7 @@ export class MastoConverters {
|
|||
});
|
||||
const fqn = `${user.username}@${user.host ?? this.config.hostname}`;
|
||||
let acct = user.username;
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
let acctUrl = `https://${user.host || this.config.host}/@${user.username}`;
|
||||
const acctUri = `https://${this.config.host}/users/${user.id}`;
|
||||
if (user.host) {
|
||||
|
|
@ -166,19 +191,23 @@ export class MastoConverters {
|
|||
fields: Promise.all(profile?.fields.map(async p => this.encodeField(p)) ?? []),
|
||||
bot: user.isBot,
|
||||
discoverable: user.isExplorable,
|
||||
noindex: user.noindex,
|
||||
group: null,
|
||||
suspended: user.isSuspended,
|
||||
limited: user.isSilenced,
|
||||
});
|
||||
}
|
||||
|
||||
public async getEdits(id: string) {
|
||||
const note = await this.getterService.getNote(id);
|
||||
public async getEdits(id: string, me?: MiLocalUser | null) {
|
||||
const note = await this.mastodonDataService.getNote(id, me);
|
||||
if (!note) {
|
||||
return {};
|
||||
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>[] = [];
|
||||
const history: Promise<StatusEdit>[] = [];
|
||||
|
||||
// TODO this looks wrong, according to mastodon docs
|
||||
let lastDate = this.idService.parse(note.id).date;
|
||||
for (const edit of edits) {
|
||||
const files = this.driveFileEntityService.packManyByIds(edit.fileIds);
|
||||
|
|
@ -187,9 +216,8 @@ export class MastoConverters {
|
|||
content: this.mfmService.toMastoApiHtml(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),
|
||||
sensitive: edit.cw != null && edit.cw.length > 0,
|
||||
spoiler_text: edit.cw ?? '',
|
||||
poll: null,
|
||||
media_attachments: files.then(files => files.length > 0 ? files.map((f) => this.encodeFile(f)) : []),
|
||||
};
|
||||
lastDate = edit.updatedAt;
|
||||
|
|
@ -199,15 +227,16 @@ export class MastoConverters {
|
|||
return await Promise.all(history);
|
||||
}
|
||||
|
||||
private async convertReblog(status: Entity.Status | null): Promise<any> {
|
||||
private async convertReblog(status: Entity.Status | null, me?: MiLocalUser | null): Promise<MastodonEntity.Status | null> {
|
||||
if (!status) return null;
|
||||
return await this.convertStatus(status);
|
||||
return await this.convertStatus(status, me);
|
||||
}
|
||||
|
||||
public async convertStatus(status: Entity.Status) {
|
||||
public async convertStatus(status: Entity.Status, me?: MiLocalUser | null): Promise<MastodonEntity.Status> {
|
||||
const convertedAccount = this.convertAccount(status.account);
|
||||
const note = await this.getterService.getNote(status.id);
|
||||
const note = await this.mastodonDataService.requireNote(status.id, me);
|
||||
const noteUser = await this.getUser(status.account.id);
|
||||
const mentionedRemoteUsers = JSON.parse(note.mentionedRemoteUsers);
|
||||
|
||||
const emojis = await this.customEmojiService.populateEmojis(note.emojis, noteUser.host ? noteUser.host : this.config.host);
|
||||
const emoji: Entity.Emoji[] = [];
|
||||
|
|
@ -224,7 +253,7 @@ export class MastoConverters {
|
|||
|
||||
const mentions = Promise.all(note.mentions.map(p =>
|
||||
this.getUser(p)
|
||||
.then(u => this.encode(u, JSON.parse(note.mentionedRemoteUsers)))
|
||||
.then(u => this.encode(u, mentionedRemoteUsers))
|
||||
.catch(() => null)))
|
||||
.then(p => p.filter(m => m)) as Promise<Entity.Mention[]>;
|
||||
|
||||
|
|
@ -235,20 +264,26 @@ export class MastoConverters {
|
|||
} as Entity.Tag;
|
||||
});
|
||||
|
||||
const isQuote = note.renoteId && note.text ? true : false;
|
||||
// This must mirror the usual isQuote / isPureRenote logic used elsewhere.
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
const isQuote = note.renoteId && (note.text || note.cw || note.fileIds.length > 0 || note.hasPoll || note.replyId);
|
||||
|
||||
const renote = note.renoteId ? this.getterService.getNote(note.renoteId) : null;
|
||||
const renote: Promise<MiNote> | null = note.renoteId ? this.mastodonDataService.requireNote(note.renoteId, me) : 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.toMastoApiHtml(mfm.parse(note.text!), JSON.parse(note.mentionedRemoteUsers), false, quoteUri))
|
||||
.then(p => p ?? escapeMFM(note.text!))
|
||||
const text = note.text;
|
||||
const content = text !== null
|
||||
? quoteUri
|
||||
.then(quoteUri => this.mfmService.toMastoApiHtml(mfm.parse(text), mentionedRemoteUsers, false, quoteUri))
|
||||
.then(p => p ?? escapeMFM(text))
|
||||
: '';
|
||||
|
||||
const reblogged = await this.mastodonDataService.hasReblog(note.id, me);
|
||||
|
||||
// noinspection ES6MissingAwait
|
||||
return await awaitAll({
|
||||
id: note.id,
|
||||
|
|
@ -257,7 +292,7 @@ export class MastoConverters {
|
|||
account: convertedAccount,
|
||||
in_reply_to_id: note.replyId,
|
||||
in_reply_to_account_id: note.replyUserId,
|
||||
reblog: !isQuote ? await this.convertReblog(status.reblog) : null,
|
||||
reblog: !isQuote ? await this.convertReblog(status.reblog, me) : null,
|
||||
content: content,
|
||||
content_type: 'text/x.misskeymarkdown',
|
||||
text: note.text,
|
||||
|
|
@ -266,34 +301,51 @@ export class MastoConverters {
|
|||
replies_count: note.repliesCount,
|
||||
reblogs_count: note.renoteCount,
|
||||
favourites_count: status.favourites_count,
|
||||
reblogged: false,
|
||||
reblogged,
|
||||
favourited: status.favourited,
|
||||
muted: status.muted,
|
||||
sensitive: status.sensitive,
|
||||
spoiler_text: note.cw ? note.cw : '',
|
||||
spoiler_text: note.cw ?? '',
|
||||
visibility: status.visibility,
|
||||
media_attachments: status.media_attachments,
|
||||
media_attachments: status.media_attachments.map(a => convertAttachment(a)),
|
||||
mentions: mentions,
|
||||
tags: tags,
|
||||
card: null, //FIXME
|
||||
poll: status.poll ?? null,
|
||||
application: null, //FIXME
|
||||
language: null, //FIXME
|
||||
pinned: false,
|
||||
pinned: false, //FIXME
|
||||
reactions: status.emoji_reactions,
|
||||
emoji_reactions: status.emoji_reactions,
|
||||
bookmarked: false,
|
||||
quote: isQuote ? await this.convertReblog(status.reblog) : null,
|
||||
// optional chaining cannot be used, as it evaluates to undefined, not null
|
||||
edited_at: note.updatedAt ? note.updatedAt.toISOString() : null,
|
||||
bookmarked: false, //FIXME
|
||||
quote: isQuote ? await this.convertReblog(status.reblog, me) : null,
|
||||
edited_at: note.updatedAt?.toISOString() ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
public async convertConversation(conversation: Entity.Conversation, me?: MiLocalUser | null): Promise<MastodonEntity.Conversation> {
|
||||
return {
|
||||
id: conversation.id,
|
||||
accounts: await Promise.all(conversation.accounts.map(a => this.convertAccount(a))),
|
||||
last_status: conversation.last_status ? await this.convertStatus(conversation.last_status, me) : null,
|
||||
unread: conversation.unread,
|
||||
};
|
||||
}
|
||||
|
||||
public async convertNotification(notification: Entity.Notification, me?: MiLocalUser | null): Promise<MastodonEntity.Notification> {
|
||||
return {
|
||||
account: await this.convertAccount(notification.account),
|
||||
created_at: notification.created_at,
|
||||
id: notification.id,
|
||||
status: notification.status ? await this.convertStatus(notification.status, me) : undefined,
|
||||
type: notification.type,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function simpleConvert(data: any) {
|
||||
function simpleConvert<T>(data: T): T {
|
||||
// copy the object to bypass weird pass by reference bugs
|
||||
const result = Object.assign({}, data);
|
||||
return result;
|
||||
return Object.assign({}, data);
|
||||
}
|
||||
|
||||
export function convertAccount(account: Entity.Account) {
|
||||
|
|
@ -302,8 +354,30 @@ export function convertAccount(account: Entity.Account) {
|
|||
export function convertAnnouncement(announcement: Entity.Announcement) {
|
||||
return simpleConvert(announcement);
|
||||
}
|
||||
export function convertAttachment(attachment: Entity.Attachment) {
|
||||
return simpleConvert(attachment);
|
||||
export function convertAttachment(attachment: Entity.Attachment): MastodonEntity.Attachment {
|
||||
const { width, height } = attachment.meta?.original ?? attachment.meta ?? {};
|
||||
const size = (width && height) ? `${width}x${height}` : undefined;
|
||||
const aspect = (width && height) ? (width / height) : undefined;
|
||||
return {
|
||||
...attachment,
|
||||
meta: attachment.meta ? {
|
||||
...attachment.meta,
|
||||
original: {
|
||||
...attachment.meta.original,
|
||||
width,
|
||||
height,
|
||||
size,
|
||||
aspect,
|
||||
frame_rate: String(attachment.meta.fps),
|
||||
duration: attachment.meta.duration,
|
||||
bitrate: attachment.meta.audio_bitrate ? parseInt(attachment.meta.audio_bitrate) : undefined,
|
||||
},
|
||||
width,
|
||||
height,
|
||||
size,
|
||||
aspect,
|
||||
} : null,
|
||||
};
|
||||
}
|
||||
export function convertFilter(filter: Entity.Filter) {
|
||||
return simpleConvert(filter);
|
||||
|
|
@ -315,45 +389,40 @@ 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);
|
||||
}
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
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;
|
||||
|
||||
// Megalodon sometimes returns broken / stubbed relationship data
|
||||
export function convertRelationship(relationship: Partial<Entity.Relationship> & { id: string }): MastodonEntity.Relationship {
|
||||
return {
|
||||
id: relationship.id,
|
||||
following: relationship.following ?? false,
|
||||
showing_reblogs: relationship.showing_reblogs ?? true,
|
||||
notifying: relationship.notifying ?? true,
|
||||
languages: [],
|
||||
followed_by: relationship.followed_by ?? false,
|
||||
blocking: relationship.blocking ?? false,
|
||||
blocked_by: relationship.blocked_by ?? false,
|
||||
muting: relationship.muting ?? false,
|
||||
muting_notifications: relationship.muting_notifications ?? false,
|
||||
requested: relationship.requested ?? false,
|
||||
requested_by: relationship.requested_by ?? false,
|
||||
domain_blocking: relationship.domain_blocking ?? false,
|
||||
endorsed: relationship.endorsed ?? false,
|
||||
note: relationship.note ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,273 +3,149 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { parseTimelineArgs, TimelineArgs } from '@/server/api/mastodon/timelineArgs.js';
|
||||
import { MiLocalUser } from '@/models/User.js';
|
||||
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: '',
|
||||
};
|
||||
export interface ApiAccountMastodonRoute {
|
||||
Params: { id?: string },
|
||||
Querystring: TimelineArgs & { acct?: string },
|
||||
Body: { notifications?: boolean }
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
constructor(
|
||||
private readonly request: FastifyRequest<ApiAccountMastodonRoute>,
|
||||
private readonly client: MegalodonInterface,
|
||||
private readonly me: MiLocalUser | null,
|
||||
private readonly mastoConverters: MastoConverters,
|
||||
) {}
|
||||
|
||||
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;
|
||||
}
|
||||
const data = await this.client.verifyAccountCredentials();
|
||||
const acct = await this.mastoConverters.convertAccount(data.data);
|
||||
return Object.assign({}, acct, {
|
||||
source: {
|
||||
note: acct.note,
|
||||
fields: acct.fields,
|
||||
privacy: '',
|
||||
sensitive: false,
|
||||
language: '',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (!this.request.query.acct) throw new Error('Missing required property "acct"');
|
||||
const data = await this.client.search(this.request.query.acct, { type: 'accounts' });
|
||||
return this.mastoConverters.convertAccount(data.data.accounts[0]);
|
||||
}
|
||||
|
||||
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 getRelationships(reqIds: string[]) {
|
||||
const data = await this.client.getRelationships(reqIds);
|
||||
return data.data.map(relationship => convertRelationship(relationship));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (!this.request.params.id) throw new Error('Missing required parameter "id"');
|
||||
const data = await this.client.getAccountStatuses(this.request.params.id, parseTimelineArgs(this.request.query));
|
||||
return await Promise.all(data.data.map(async (status) => await this.mastoConverters.convertStatus(status, this.me)));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (!this.request.params.id) throw new Error('Missing required parameter "id"');
|
||||
const data = await this.client.getAccountFollowers(
|
||||
this.request.params.id,
|
||||
parseTimelineArgs(this.request.query),
|
||||
);
|
||||
return await Promise.all(data.data.map(async (account) => await this.mastoConverters.convertAccount(account)));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (!this.request.params.id) throw new Error('Missing required parameter "id"');
|
||||
const data = await this.client.getAccountFollowing(
|
||||
this.request.params.id,
|
||||
parseTimelineArgs(this.request.query),
|
||||
);
|
||||
return await Promise.all(data.data.map(async (account) => await this.mastoConverters.convertAccount(account)));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (!this.request.params.id) throw new Error('Missing required parameter "id"');
|
||||
const data = await this.client.followAccount(this.request.params.id);
|
||||
const acct = convertRelationship(data.data);
|
||||
acct.following = true;
|
||||
return acct;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (!this.request.params.id) throw new Error('Missing required parameter "id"');
|
||||
const data = await this.client.unfollowAccount(this.request.params.id);
|
||||
const acct = convertRelationship(data.data);
|
||||
acct.following = false;
|
||||
return acct;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (!this.request.params.id) throw new Error('Missing required parameter "id"');
|
||||
const data = await this.client.blockAccount(this.request.params.id);
|
||||
return convertRelationship(data.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;
|
||||
}
|
||||
if (!this.request.params.id) throw new Error('Missing required parameter "id"');
|
||||
const data = await this.client.unblockAccount(this.request.params.id);
|
||||
return convertRelationship(data.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;
|
||||
}
|
||||
if (!this.request.params.id) throw new Error('Missing required parameter "id"');
|
||||
const data = await this.client.muteAccount(
|
||||
this.request.params.id,
|
||||
this.request.body.notifications ?? true,
|
||||
);
|
||||
return convertRelationship(data.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;
|
||||
}
|
||||
if (!this.request.params.id) throw new Error('Missing required parameter "id"');
|
||||
const data = await this.client.unmuteAccount(this.request.params.id);
|
||||
return convertRelationship(data.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;
|
||||
}
|
||||
const data = await this.client.getBookmarks(parseTimelineArgs(this.request.query));
|
||||
return Promise.all(data.data.map((status) => this.mastoConverters.convertStatus(status, this.me)));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
const data = await this.client.getFavourites(parseTimelineArgs(this.request.query));
|
||||
return Promise.all(data.data.map((status) => this.mastoConverters.convertStatus(status, this.me)));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
const data = await this.client.getMutes(parseTimelineArgs(this.request.query));
|
||||
return Promise.all(data.data.map((account) => this.mastoConverters.convertAccount(account)));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
const data = await this.client.getBlocks(parseTimelineArgs(this.request.query));
|
||||
return Promise.all(data.data.map((account) => this.mastoConverters.convertAccount(account)));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (!this.request.params.id) throw new Error('Missing required parameter "id"');
|
||||
const data = await this.client.acceptFollowRequest(this.request.params.id);
|
||||
return convertRelationship(data.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;
|
||||
}
|
||||
if (!this.request.params.id) throw new Error('Missing required parameter "id"');
|
||||
const data = await this.client.rejectFollowRequest(this.request.params.id);
|
||||
return convertRelationship(data.data);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,36 +44,54 @@ const writeScope = [
|
|||
'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;
|
||||
}
|
||||
export interface AuthPayload {
|
||||
scopes?: string | string[],
|
||||
redirect_uris?: string,
|
||||
client_name?: string,
|
||||
website?: string,
|
||||
}
|
||||
|
||||
// Not entirely right, but it gets TypeScript to work so *shrug*
|
||||
export type AuthMastodonRoute = { Body?: AuthPayload, Querystring: AuthPayload };
|
||||
|
||||
export async function ApiAuthMastodon(request: FastifyRequest<AuthMastodonRoute>, client: MegalodonInterface) {
|
||||
const body = request.body ?? request.query;
|
||||
if (!body.scopes) throw new Error('Missing required payload "scopes"');
|
||||
if (!body.redirect_uris) throw new Error('Missing required payload "redirect_uris"');
|
||||
if (!body.client_name) throw new Error('Missing required payload "client_name"');
|
||||
|
||||
let scope = body.scopes;
|
||||
if (typeof scope === 'string') {
|
||||
scope = scope.split(/[ +]/g);
|
||||
}
|
||||
|
||||
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 red = body.redirect_uris;
|
||||
const appData = await client.registerApp(body.client_name, {
|
||||
scopes: Array.from(pushScope),
|
||||
redirect_uris: red,
|
||||
website: body.website,
|
||||
});
|
||||
|
||||
return {
|
||||
id: Math.floor(Math.random() * 100).toString(),
|
||||
name: appData.name,
|
||||
website: body.website,
|
||||
redirect_uri: red,
|
||||
client_id: Buffer.from(appData.url || '').toString('base64'), // eslint-disable-line @typescript-eslint/prefer-nullish-coalescing
|
||||
client_secret: appData.clientSecret,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,68 +3,73 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { toBoolean } from '@/server/api/mastodon/timelineArgs.js';
|
||||
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;
|
||||
export interface ApiFilterMastodonRoute {
|
||||
Params: {
|
||||
id?: string,
|
||||
},
|
||||
Body: {
|
||||
phrase?: string,
|
||||
context?: string[],
|
||||
irreversible?: string,
|
||||
whole_word?: string,
|
||||
expires_in?: string,
|
||||
}
|
||||
}
|
||||
|
||||
export class ApiFilterMastodon {
|
||||
constructor(
|
||||
private readonly request: FastifyRequest<ApiFilterMastodonRoute>,
|
||||
private readonly client: MegalodonInterface,
|
||||
) {}
|
||||
|
||||
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;
|
||||
}
|
||||
const data = await this.client.getFilters();
|
||||
return data.data.map((filter) => convertFilter(filter));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (!this.request.params.id) throw new Error('Missing required parameter "id"');
|
||||
const data = await this.client.getFilter(this.request.params.id);
|
||||
return convertFilter(data.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;
|
||||
}
|
||||
if (!this.request.body.phrase) throw new Error('Missing required payload "phrase"');
|
||||
if (!this.request.body.context) throw new Error('Missing required payload "context"');
|
||||
const options = {
|
||||
phrase: this.request.body.phrase,
|
||||
context: this.request.body.context,
|
||||
irreversible: toBoolean(this.request.body.irreversible),
|
||||
whole_word: toBoolean(this.request.body.whole_word),
|
||||
expires_in: this.request.body.expires_in,
|
||||
};
|
||||
const data = await this.client.createFilter(this.request.body.phrase, this.request.body.context, options);
|
||||
return convertFilter(data.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;
|
||||
}
|
||||
if (!this.request.params.id) throw new Error('Missing required parameter "id"');
|
||||
if (!this.request.body.phrase) throw new Error('Missing required payload "phrase"');
|
||||
if (!this.request.body.context) throw new Error('Missing required payload "context"');
|
||||
const options = {
|
||||
phrase: this.request.body.phrase,
|
||||
context: this.request.body.context,
|
||||
irreversible: toBoolean(this.request.body.irreversible),
|
||||
whole_word: toBoolean(this.request.body.whole_word),
|
||||
expires_in: this.request.body.expires_in,
|
||||
};
|
||||
const data = await this.client.updateFilter(this.request.params.id, this.request.body.phrase, this.request.body.context, options);
|
||||
return convertFilter(data.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;
|
||||
}
|
||||
if (!this.request.params.id) throw new Error('Missing required parameter "id"');
|
||||
const data = await this.client.deleteFilter(this.request.params.id);
|
||||
return data.data;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { FILE_TYPE_BROWSERSAFE } from '@/const.js';
|
|||
import type { Config } from '@/config.js';
|
||||
import type { MiMeta } from '@/models/Meta.js';
|
||||
|
||||
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
||||
export async function getInstance(
|
||||
response: Entity.Instance,
|
||||
contact: Entity.Account,
|
||||
|
|
@ -17,11 +18,8 @@ export async function getInstance(
|
|||
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.',
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -3,73 +3,56 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { convertNotification } from '../converters.js';
|
||||
import type { MegalodonInterface, Entity } from 'megalodon';
|
||||
import { parseTimelineArgs, TimelineArgs } from '@/server/api/mastodon/timelineArgs.js';
|
||||
import { MiLocalUser } from '@/models/User.js';
|
||||
import { MastoConverters } from '@/server/api/mastodon/converters.js';
|
||||
import type { MegalodonInterface } 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 interface ApiNotifyMastodonRoute {
|
||||
Params: {
|
||||
id?: string,
|
||||
},
|
||||
Querystring: TimelineArgs,
|
||||
}
|
||||
|
||||
export class ApiNotifyMastodon {
|
||||
private request: FastifyRequest;
|
||||
private client: MegalodonInterface;
|
||||
|
||||
constructor(request: FastifyRequest, client: MegalodonInterface) {
|
||||
this.request = request;
|
||||
this.client = client;
|
||||
}
|
||||
constructor(
|
||||
private readonly request: FastifyRequest<ApiNotifyMastodonRoute>,
|
||||
private readonly client: MegalodonInterface,
|
||||
private readonly me: MiLocalUser | null,
|
||||
private readonly mastoConverters: MastoConverters,
|
||||
) {}
|
||||
|
||||
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;
|
||||
}
|
||||
const data = await this.client.getNotifications(parseTimelineArgs(this.request.query));
|
||||
return Promise.all(data.data.map(async n => {
|
||||
const converted = await this.mastoConverters.convertNotification(n, this.me);
|
||||
if (converted.type === 'reaction') {
|
||||
converted.type = 'favourite';
|
||||
}
|
||||
return converted;
|
||||
}));
|
||||
}
|
||||
|
||||
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;
|
||||
if (!this.request.params.id) throw new Error('Missing required parameter "id"');
|
||||
const data = await this.client.getNotification(this.request.params.id);
|
||||
const converted = await this.mastoConverters.convertNotification(data.data, this.me);
|
||||
if (converted.type === 'reaction') {
|
||||
converted.type = 'favourite';
|
||||
}
|
||||
return converted;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (!this.request.params.id) throw new Error('Missing required parameter "id"');
|
||||
const data = await this.client.dismissNotification(this.request.params.id);
|
||||
return data.data;
|
||||
}
|
||||
|
||||
public async rmNotifications() {
|
||||
try {
|
||||
const data = await this.client.dismissNotifications();
|
||||
return data.data;
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
return e.response.data;
|
||||
}
|
||||
const data = await this.client.dismissNotifications();
|
||||
return data.data;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,88 +3,92 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { MiLocalUser } from '@/models/User.js';
|
||||
import { MastoConverters } from '../converters.js';
|
||||
import { limitToInt } from './timeline.js';
|
||||
import { parseTimelineArgs, TimelineArgs } from '../timelineArgs.js';
|
||||
import Account = Entity.Account;
|
||||
import Status = Entity.Status;
|
||||
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;
|
||||
export interface ApiSearchMastodonRoute {
|
||||
Querystring: TimelineArgs & {
|
||||
type?: 'accounts' | 'hashtags' | 'statuses';
|
||||
q?: string;
|
||||
}
|
||||
}
|
||||
|
||||
export class ApiSearchMastodon {
|
||||
constructor(
|
||||
private readonly request: FastifyRequest<ApiSearchMastodonRoute>,
|
||||
private readonly client: MegalodonInterface,
|
||||
private readonly me: MiLocalUser | null,
|
||||
private readonly BASE_URL: string,
|
||||
private readonly mastoConverters: MastoConverters,
|
||||
) {}
|
||||
|
||||
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;
|
||||
}
|
||||
if (!this.request.query.q) throw new Error('Missing required property "q"');
|
||||
const query = parseTimelineArgs(this.request.query);
|
||||
const data = await this.client.search(this.request.query.q, { type: this.request.query.type, ...query });
|
||||
return data.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;
|
||||
}
|
||||
if (!this.request.query.q) throw new Error('Missing required property "q"');
|
||||
const query = parseTimelineArgs(this.request.query);
|
||||
const type = this.request.query.type;
|
||||
const acct = !type || type === 'accounts' ? await this.client.search(this.request.query.q, { type: 'accounts', ...query }) : null;
|
||||
const stat = !type || type === 'statuses' ? await this.client.search(this.request.query.q, { type: 'statuses', ...query }) : null;
|
||||
const tags = !type || type === 'hashtags' ? await this.client.search(this.request.query.q, { type: 'hashtags', ...query }) : null;
|
||||
return {
|
||||
accounts: await Promise.all(acct?.data.accounts.map(async (account: Account) => await this.mastoConverters.convertAccount(account)) ?? []),
|
||||
statuses: await Promise.all(stat?.data.statuses.map(async (status: Status) => await this.mastoConverters.convertStatus(status, this.me)) ?? []),
|
||||
hashtags: tags?.data.hashtags ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
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 [];
|
||||
}
|
||||
const data = await fetch(`${this.BASE_URL}/api/notes/featured`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
i: this.request.headers.authorization?.replace('Bearer ', ''),
|
||||
}),
|
||||
})
|
||||
.then(res => res.json() as Promise<Status[]>)
|
||||
.then(data => data.map(status => this.mastoConverters.convertStatus(status, this.me)));
|
||||
return Promise.all(data);
|
||||
}
|
||||
|
||||
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 [];
|
||||
}
|
||||
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: parseTimelineArgs(this.request.query).limit ?? 20,
|
||||
origin: 'local',
|
||||
sort: '+follower',
|
||||
state: 'alive',
|
||||
}),
|
||||
})
|
||||
.then(res => res.json() as Promise<Account[]>)
|
||||
.then(data => data.map((entry => ({
|
||||
source: 'global',
|
||||
account: entry,
|
||||
}))));
|
||||
return Promise.all(data.map(async suggestion => {
|
||||
suggestion.account = await this.mastoConverters.convertAccount(suggestion.account);
|
||||
return suggestion;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,181 +3,212 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import querystring from 'querystring';
|
||||
import querystring, { ParsedUrlQueryInput } 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 { getErrorData, MastodonLogger } from '@/server/api/mastodon/MastodonLogger.js';
|
||||
import { parseTimelineArgs, TimelineArgs, toBoolean, toInt } from '@/server/api/mastodon/timelineArgs.js';
|
||||
import { AuthenticateService } from '@/server/api/AuthenticateService.js';
|
||||
import { convertAttachment, convertPoll, MastoConverters } from '../converters.js';
|
||||
import { getAccessToken, getClient, MastodonApiServerService } 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';
|
||||
|
||||
function normalizeQuery(data: any) {
|
||||
const str = querystring.stringify(data);
|
||||
function normalizeQuery(data: Record<string, unknown>) {
|
||||
const str = querystring.stringify(data as ParsedUrlQueryInput);
|
||||
return querystring.parse(str);
|
||||
}
|
||||
|
||||
export class ApiStatusMastodon {
|
||||
private fastify: FastifyInstance;
|
||||
private mastoconverter: MastoConverters;
|
||||
constructor(
|
||||
private readonly fastify: FastifyInstance,
|
||||
private readonly mastoConverters: MastoConverters,
|
||||
private readonly logger: MastodonLogger,
|
||||
private readonly authenticateService: AuthenticateService,
|
||||
private readonly mastodon: MastodonApiServerService,
|
||||
) {}
|
||||
|
||||
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);
|
||||
public getStatus() {
|
||||
this.fastify.get<{ Params: { id?: string } }>('/v1/statuses/:id', async (_request, reply) => {
|
||||
try {
|
||||
const { client, me } = await this.mastodon.getAuthClient(_request);
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
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);
|
||||
reply.send(await this.mastoConverters.convertStatus(data.data, me));
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`GET /v1/statuses/${_request.params.id}`, data);
|
||||
reply.code(_request.is404 ? 404 : 401).send(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async getStatusSource() {
|
||||
this.fastify.get<{ Params: { id: string } }>('/v1/statuses/:id/source', async (_request, reply) => {
|
||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
||||
public getStatusSource() {
|
||||
this.fastify.get<{ Params: { id?: string } }>('/v1/statuses/:id/source', async (_request, reply) => {
|
||||
const BASE_URL = `${_request.protocol}://${_request.host}`;
|
||||
const accessTokens = _request.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
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);
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`GET /v1/statuses/${_request.params.id}/source`, data);
|
||||
reply.code(_request.is404 ? 404 : 401).send(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;
|
||||
public getContext() {
|
||||
this.fastify.get<{ Params: { id?: string }, Querystring: TimelineArgs }>('/v1/statuses/:id/context', async (_request, reply) => {
|
||||
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);
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
const { client, me } = await this.mastodon.getAuthClient(_request);
|
||||
const { data } = await client.getStatusContext(_request.params.id, parseTimelineArgs(_request.query));
|
||||
const ancestors = await Promise.all(data.ancestors.map(async status => await this.mastoConverters.convertStatus(status, me)));
|
||||
const descendants = await Promise.all(data.descendants.map(async status => await this.mastoConverters.convertStatus(status, me)));
|
||||
reply.send({ ancestors, descendants });
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`GET /v1/statuses/${_request.params.id}/context`, data);
|
||||
reply.code(_request.is404 ? 404 : 401).send(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async getHistory() {
|
||||
this.fastify.get<{ Params: { id: string } }>('/v1/statuses/:id/history', async (_request, reply) => {
|
||||
public getHistory() {
|
||||
this.fastify.get<{ Params: { id?: string } }>('/v1/statuses/:id/history', async (_request, reply) => {
|
||||
try {
|
||||
const edits = await this.mastoconverter.getEdits(_request.params.id);
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
const [user] = await this.authenticateService.authenticate(getAccessToken(_request.headers.authorization));
|
||||
const edits = await this.mastoConverters.getEdits(_request.params.id, user);
|
||||
reply.send(edits);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
reply.code(401).send(e.response.data);
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`GET /v1/statuses/${_request.params.id}/history`, data);
|
||||
reply.code(401).send(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}`;
|
||||
public getReblogged() {
|
||||
this.fastify.get<{ Params: { id?: string } }>('/v1/statuses/:id/reblogged_by', async (_request, reply) => {
|
||||
const BASE_URL = `${_request.protocol}://${_request.host}`;
|
||||
const accessTokens = _request.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
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);
|
||||
reply.send(await Promise.all(data.data.map(async (account: Entity.Account) => await this.mastoConverters.convertAccount(account))));
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`GET /v1/statuses/${_request.params.id}/reblogged_by`, data);
|
||||
reply.code(401).send(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}`;
|
||||
public getFavourites() {
|
||||
this.fastify.get<{ Params: { id?: string } }>('/v1/statuses/:id/favourited_by', async (_request, reply) => {
|
||||
const BASE_URL = `${_request.protocol}://${_request.host}`;
|
||||
const accessTokens = _request.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
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);
|
||||
reply.send(await Promise.all(data.data.map(async (account: Entity.Account) => await this.mastoConverters.convertAccount(account))));
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`GET /v1/statuses/${_request.params.id}/favourited_by`, data);
|
||||
reply.code(401).send(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async getMedia() {
|
||||
this.fastify.get<{ Params: { id: string } }>('/v1/media/:id', async (_request, reply) => {
|
||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
||||
public getMedia() {
|
||||
this.fastify.get<{ Params: { id?: string } }>('/v1/media/:id', async (_request, reply) => {
|
||||
const BASE_URL = `${_request.protocol}://${_request.host}`;
|
||||
const accessTokens = _request.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
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);
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`GET /v1/media/${_request.params.id}`, data);
|
||||
reply.code(401).send(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async getPoll() {
|
||||
this.fastify.get<{ Params: { id: string } }>('/v1/polls/:id', async (_request, reply) => {
|
||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
||||
public getPoll() {
|
||||
this.fastify.get<{ Params: { id?: string } }>('/v1/polls/:id', async (_request, reply) => {
|
||||
const BASE_URL = `${_request.protocol}://${_request.host}`;
|
||||
const accessTokens = _request.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
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);
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`GET /v1/polls/${_request.params.id}`, data);
|
||||
reply.code(401).send(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async votePoll() {
|
||||
this.fastify.post<{ Params: { id: string } }>('/v1/polls/:id/votes', async (_request, reply) => {
|
||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
||||
public votePoll() {
|
||||
this.fastify.post<{ Params: { id?: string }, Body: { choices?: number[] } }>('/v1/polls/:id/votes', async (_request, reply) => {
|
||||
const BASE_URL = `${_request.protocol}://${_request.host}`;
|
||||
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);
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
if (!_request.body.choices) return reply.code(400).send({ error: 'Missing required payload "choices"' });
|
||||
const data = await client.votePoll(_request.params.id, _request.body.choices);
|
||||
reply.send(convertPoll(data.data));
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
reply.code(401).send(e.response.data);
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`GET /v1/polls/${_request.params.id}/votes`, data);
|
||||
reply.code(401).send(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;
|
||||
public postStatus() {
|
||||
this.fastify.post<{
|
||||
Body: {
|
||||
media_ids?: string[],
|
||||
poll?: {
|
||||
options?: string[],
|
||||
expires_in?: string,
|
||||
multiple?: string,
|
||||
hide_totals?: string,
|
||||
},
|
||||
in_reply_to_id?: string,
|
||||
sensitive?: string,
|
||||
spoiler_text?: string,
|
||||
visibility?: 'public' | 'unlisted' | 'private' | 'direct',
|
||||
scheduled_at?: string,
|
||||
language?: string,
|
||||
quote_id?: string,
|
||||
status?: string,
|
||||
|
||||
// Broken clients
|
||||
'poll[options][]'?: string[],
|
||||
'media_ids[]'?: string[],
|
||||
}
|
||||
}>('/v1/statuses', async (_request, reply) => {
|
||||
let body = _request.body;
|
||||
try {
|
||||
if (
|
||||
(!body.poll && body['poll[options][]']) ||
|
||||
(!body.media_ids && body['media_ids[]'])
|
||||
const { client, me } = await this.mastodon.getAuthClient(_request);
|
||||
if ((!body.poll && body['poll[options][]']) || (!body.media_ids && body['media_ids[]'])
|
||||
) {
|
||||
body = normalizeQuery(body);
|
||||
}
|
||||
const text = body.status ? body.status : ' ';
|
||||
const text = body.status ??= ' ';
|
||||
const removed = text.replace(/@\S+/g, '').replace(/\s|/g, '');
|
||||
const isDefaultEmoji = emojiRegexAtStartToEnd.test(removed);
|
||||
const isCustomEmoji = /^:[a-zA-Z0-9@_]+:$/.test(removed);
|
||||
|
|
@ -189,226 +220,253 @@ export class ApiStatusMastodon {
|
|||
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);
|
||||
}
|
||||
const id = body.in_reply_to_id;
|
||||
const post = await client.getStatus(id);
|
||||
const react = post.data.emoji_reactions.filter(e => e.me)[0].name;
|
||||
const data = await client.deleteEmojiReaction(id, react);
|
||||
reply.send(data.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';
|
||||
if (body.poll && !body.poll.options) {
|
||||
return reply.code(400).send({ error: 'Missing required payload "poll.options"' });
|
||||
}
|
||||
if (body.poll && !body.poll.expires_in) {
|
||||
return reply.code(400).send({ error: 'Missing required payload "poll.expires_in"' });
|
||||
}
|
||||
|
||||
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);
|
||||
const options = {
|
||||
...body,
|
||||
sensitive: toBoolean(body.sensitive),
|
||||
poll: body.poll ? {
|
||||
options: body.poll.options!, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
||||
expires_in: toInt(body.poll.expires_in)!, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
||||
multiple: toBoolean(body.poll.multiple),
|
||||
hide_totals: toBoolean(body.poll.hide_totals),
|
||||
} : undefined,
|
||||
};
|
||||
|
||||
const data = await client.postStatus(text, options);
|
||||
reply.send(await this.mastoConverters.convertStatus(data.data as Entity.Status, me));
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error('POST /v1/statuses', data);
|
||||
reply.code(401).send(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;
|
||||
public updateStatus() {
|
||||
this.fastify.put<{
|
||||
Params: { id: string },
|
||||
Body: {
|
||||
status?: string,
|
||||
spoiler_text?: string,
|
||||
sensitive?: string,
|
||||
media_ids?: string[],
|
||||
poll?: {
|
||||
options?: string[],
|
||||
expires_in?: string,
|
||||
multiple?: string,
|
||||
hide_totals?: string,
|
||||
},
|
||||
}
|
||||
}>('/v1/statuses/:id', async (_request, reply) => {
|
||||
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);
|
||||
const { client, me } = await this.mastodon.getAuthClient(_request);
|
||||
const body = _request.body;
|
||||
|
||||
if (!body.media_ids || !body.media_ids.length) {
|
||||
body.media_ids = undefined;
|
||||
}
|
||||
|
||||
const options = {
|
||||
...body,
|
||||
sensitive: toBoolean(body.sensitive),
|
||||
poll: body.poll ? {
|
||||
options: body.poll.options,
|
||||
expires_in: toInt(body.poll.expires_in),
|
||||
multiple: toBoolean(body.poll.multiple),
|
||||
hide_totals: toBoolean(body.poll.hide_totals),
|
||||
} : undefined,
|
||||
};
|
||||
|
||||
const data = await client.editStatus(_request.params.id, options);
|
||||
reply.send(await this.mastoConverters.convertStatus(data.data, me));
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`POST /v1/statuses/${_request.params.id}`, data);
|
||||
reply.code(401).send(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);
|
||||
public addFavourite() {
|
||||
this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/favourite', async (_request, reply) => {
|
||||
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);
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
const { client, me } = await this.mastodon.getAuthClient(_request);
|
||||
const data = await client.createEmojiReaction(_request.params.id, '❤');
|
||||
reply.send(await this.mastoConverters.convertStatus(data.data, me));
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`POST /v1/statuses/${_request.params.id}/favorite`, data);
|
||||
reply.code(401).send(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);
|
||||
public rmFavourite() {
|
||||
this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/unfavourite', async (_request, reply) => {
|
||||
try {
|
||||
const { client, me } = await this.mastodon.getAuthClient(_request);
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
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);
|
||||
reply.send(await this.mastoConverters.convertStatus(data.data, me));
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`GET /v1/statuses/${_request.params.id}/unfavorite`, data);
|
||||
reply.code(401).send(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);
|
||||
public reblogStatus() {
|
||||
this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/reblog', async (_request, reply) => {
|
||||
try {
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
const { client, me } = await this.mastodon.getAuthClient(_request);
|
||||
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);
|
||||
reply.send(await this.mastoConverters.convertStatus(data.data, me));
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`POST /v1/statuses/${_request.params.id}/reblog`, data);
|
||||
reply.code(401).send(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);
|
||||
public unreblogStatus() {
|
||||
this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/unreblog', async (_request, reply) => {
|
||||
try {
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
const { client, me } = await this.mastodon.getAuthClient(_request);
|
||||
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);
|
||||
reply.send(await this.mastoConverters.convertStatus(data.data, me));
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`POST /v1/statuses/${_request.params.id}/unreblog`, data);
|
||||
reply.code(401).send(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);
|
||||
public bookmarkStatus() {
|
||||
this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/bookmark', async (_request, reply) => {
|
||||
try {
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
const { client, me } = await this.mastodon.getAuthClient(_request);
|
||||
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);
|
||||
reply.send(await this.mastoConverters.convertStatus(data.data, me));
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`POST /v1/statuses/${_request.params.id}/bookmark`, data);
|
||||
reply.code(401).send(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);
|
||||
public unbookmarkStatus() {
|
||||
this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/unbookmark', async (_request, reply) => {
|
||||
try {
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
const { client, me } = await this.mastodon.getAuthClient(_request);
|
||||
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);
|
||||
reply.send(await this.mastoConverters.convertStatus(data.data, me));
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`POST /v1/statuses/${_request.params.id}/unbookmark`, data);
|
||||
reply.code(401).send(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);
|
||||
public pinStatus() {
|
||||
this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/pin', async (_request, reply) => {
|
||||
try {
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
const { client, me } = await this.mastodon.getAuthClient(_request);
|
||||
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);
|
||||
reply.send(await this.mastoConverters.convertStatus(data.data, me));
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`POST /v1/statuses/${_request.params.id}/pin`, data);
|
||||
reply.code(401).send(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);
|
||||
public unpinStatus() {
|
||||
this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/unpin', async (_request, reply) => {
|
||||
try {
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
const { client, me } = await this.mastodon.getAuthClient(_request);
|
||||
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);
|
||||
reply.send(await this.mastoConverters.convertStatus(data.data, me));
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`POST /v1/statuses/${_request.params.id}/unpin`, data);
|
||||
reply.code(401).send(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);
|
||||
public reactStatus() {
|
||||
this.fastify.post<{ Params: { id?: string, name?: string } }>('/v1/statuses/:id/react/:name', async (_request, reply) => {
|
||||
try {
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
if (!_request.params.name) return reply.code(400).send({ error: 'Missing required parameter "name"' });
|
||||
const { client, me } = await this.mastodon.getAuthClient(_request);
|
||||
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);
|
||||
reply.send(await this.mastoConverters.convertStatus(data.data, me));
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`POST /v1/statuses/${_request.params.id}/react/${_request.params.name}`, data);
|
||||
reply.code(401).send(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);
|
||||
public unreactStatus() {
|
||||
this.fastify.post<{ Params: { id?: string, name?: string } }>('/v1/statuses/:id/unreact/:name', async (_request, reply) => {
|
||||
try {
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
if (!_request.params.name) return reply.code(400).send({ error: 'Missing required parameter "name"' });
|
||||
const { client, me } = await this.mastodon.getAuthClient(_request);
|
||||
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);
|
||||
reply.send(await this.mastoConverters.convertStatus(data.data, me));
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`POST /v1/statuses/${_request.params.id}/unreact/${_request.params.name}`, data);
|
||||
reply.code(401).send(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async deleteStatus() {
|
||||
this.fastify.delete<{ Params: { id: string } }>('/v1/statuses/:id', async (_request, reply) => {
|
||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
||||
public deleteStatus() {
|
||||
this.fastify.delete<{ Params: { id?: string } }>('/v1/statuses/:id', async (_request, reply) => {
|
||||
const BASE_URL = `${_request.protocol}://${_request.host}`;
|
||||
const accessTokens = _request.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
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);
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`DELETE /v1/statuses/${_request.params.id}`, data);
|
||||
reply.code(401).send(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,270 +3,231 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { ParsedUrlQuery } from 'querystring';
|
||||
import { convertConversation, convertList, MastoConverters } from '../converters.js';
|
||||
import { getClient } from '../MastodonApiServerService.js';
|
||||
import { getErrorData, MastodonLogger } from '@/server/api/mastodon/MastodonLogger.js';
|
||||
import { convertList, MastoConverters } from '../converters.js';
|
||||
import { getClient, MastodonApiServerService } from '../MastodonApiServerService.js';
|
||||
import { parseTimelineArgs, TimelineArgs, toBoolean } from '../timelineArgs.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(
|
||||
private readonly fastify: FastifyInstance,
|
||||
private readonly mastoConverters: MastoConverters,
|
||||
private readonly logger: MastodonLogger,
|
||||
private readonly mastodon: MastodonApiServerService,
|
||||
) {}
|
||||
|
||||
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);
|
||||
public getTL() {
|
||||
this.fastify.get<{ Querystring: TimelineArgs }>('/v1/timelines/public', async (_request, reply) => {
|
||||
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);
|
||||
const { client, me } = await this.mastodon.getAuthClient(_request);
|
||||
const data = toBoolean(_request.query.local)
|
||||
? await client.getLocalTimeline(parseTimelineArgs(_request.query))
|
||||
: await client.getPublicTimeline(parseTimelineArgs(_request.query));
|
||||
reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoConverters.convertStatus(status, me))));
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error('GET /v1/timelines/public', data);
|
||||
reply.code(401).send(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);
|
||||
public getHomeTl() {
|
||||
this.fastify.get<{ Querystring: TimelineArgs }>('/v1/timelines/home', async (_request, reply) => {
|
||||
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);
|
||||
const { client, me } = await this.mastodon.getAuthClient(_request);
|
||||
const data = await client.getHomeTimeline(parseTimelineArgs(_request.query));
|
||||
reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoConverters.convertStatus(status, me))));
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error('GET /v1/timelines/home', data);
|
||||
reply.code(401).send(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);
|
||||
public getTagTl() {
|
||||
this.fastify.get<{ Params: { hashtag?: string }, Querystring: TimelineArgs }>('/v1/timelines/tag/:hashtag', async (_request, reply) => {
|
||||
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);
|
||||
if (!_request.params.hashtag) return reply.code(400).send({ error: 'Missing required parameter "hashtag"' });
|
||||
const { client, me } = await this.mastodon.getAuthClient(_request);
|
||||
const data = await client.getTagTimeline(_request.params.hashtag, parseTimelineArgs(_request.query));
|
||||
reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoConverters.convertStatus(status, me))));
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`GET /v1/timelines/tag/${_request.params.hashtag}`, data);
|
||||
reply.code(401).send(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);
|
||||
public getListTL() {
|
||||
this.fastify.get<{ Params: { id?: string }, Querystring: TimelineArgs }>('/v1/timelines/list/:id', async (_request, reply) => {
|
||||
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);
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
const { client, me } = await this.mastodon.getAuthClient(_request);
|
||||
const data = await client.getListTimeline(_request.params.id, parseTimelineArgs(_request.query));
|
||||
reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoConverters.convertStatus(status, me))));
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`GET /v1/timelines/list/${_request.params.id}`, data);
|
||||
reply.code(401).send(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);
|
||||
public getConversations() {
|
||||
this.fastify.get<{ Querystring: TimelineArgs }>('/v1/conversations', async (_request, reply) => {
|
||||
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);
|
||||
const { client, me } = await this.mastodon.getAuthClient(_request);
|
||||
const data = await client.getConversationTimeline(parseTimelineArgs(_request.query));
|
||||
const conversations = await Promise.all(data.data.map(async (conversation: Entity.Conversation) => await this.mastoConverters.convertConversation(conversation, me)));
|
||||
reply.send(conversations);
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error('GET /v1/conversations', data);
|
||||
reply.code(401).send(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async getList() {
|
||||
this.fastify.get<{ Params: { id: string } }>('/v1/lists/:id', async (_request, reply) => {
|
||||
public getList() {
|
||||
this.fastify.get<{ Params: { id?: string } }>('/v1/lists/:id', async (_request, reply) => {
|
||||
try {
|
||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
const BASE_URL = `${_request.protocol}://${_request.host}`;
|
||||
const accessTokens = _request.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
const params: any = _request.params;
|
||||
const data = await client.getList(params.id);
|
||||
const data = await client.getList(_request.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);
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`GET /v1/lists/${_request.params.id}`, data);
|
||||
reply.code(401).send(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async getLists() {
|
||||
public getLists() {
|
||||
this.fastify.get('/v1/lists', async (_request, reply) => {
|
||||
try {
|
||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
||||
const BASE_URL = `${_request.protocol}://${_request.host}`;
|
||||
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;
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error('GET /v1/lists', data);
|
||||
reply.code(401).send(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async getListAccounts() {
|
||||
this.fastify.get<{ Params: { id: string } }>('/v1/lists/:id/accounts', async (_request, reply) => {
|
||||
public getListAccounts() {
|
||||
this.fastify.get<{ Params: { id?: string }, Querystring: { limit?: number, max_id?: string, since_id?: string } }>('/v1/lists/:id/accounts', async (_request, reply) => {
|
||||
try {
|
||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
const BASE_URL = `${_request.protocol}://${_request.host}`;
|
||||
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);
|
||||
const data = await client.getAccountsInList(_request.params.id, _request.query);
|
||||
const accounts = await Promise.all(data.data.map((account: Entity.Account) => this.mastoConverters.convertAccount(account)));
|
||||
reply.send(accounts);
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`GET /v1/lists/${_request.params.id}/accounts`, data);
|
||||
reply.code(401).send(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async addListAccount() {
|
||||
this.fastify.post<{ Params: { id: string } }>('/v1/lists/:id/accounts', async (_request, reply) => {
|
||||
public addListAccount() {
|
||||
this.fastify.post<{ Params: { id?: string }, Querystring: { accounts_id?: string[] } }>('/v1/lists/:id/accounts', async (_request, reply) => {
|
||||
try {
|
||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
if (!_request.query.accounts_id) return reply.code(400).send({ error: 'Missing required property "accounts_id"' });
|
||||
const BASE_URL = `${_request.protocol}://${_request.host}`;
|
||||
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);
|
||||
const data = await client.addAccountsToList(_request.params.id, _request.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);
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`POST /v1/lists/${_request.params.id}/accounts`, data);
|
||||
reply.code(401).send(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async rmListAccount() {
|
||||
this.fastify.delete<{ Params: { id: string } }>('/v1/lists/:id/accounts', async (_request, reply) => {
|
||||
public rmListAccount() {
|
||||
this.fastify.delete<{ Params: { id?: string }, Querystring: { accounts_id?: string[] } }>('/v1/lists/:id/accounts', async (_request, reply) => {
|
||||
try {
|
||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
if (!_request.query.accounts_id) return reply.code(400).send({ error: 'Missing required property "accounts_id"' });
|
||||
const BASE_URL = `${_request.protocol}://${_request.host}`;
|
||||
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);
|
||||
const data = await client.deleteAccountsFromList(_request.params.id, _request.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);
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`DELETE /v1/lists/${_request.params.id}/accounts`, data);
|
||||
reply.code(401).send(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async createList() {
|
||||
this.fastify.post('/v1/lists', async (_request, reply) => {
|
||||
public createList() {
|
||||
this.fastify.post<{ Body: { title?: string } }>('/v1/lists', async (_request, reply) => {
|
||||
try {
|
||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
||||
if (!_request.body.title) return reply.code(400).send({ error: 'Missing required payload "title"' });
|
||||
const BASE_URL = `${_request.protocol}://${_request.host}`;
|
||||
const accessTokens = _request.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
const body: any = _request.body;
|
||||
const data = await client.createList(body.title);
|
||||
const data = await client.createList(_request.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);
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error('POST /v1/lists', data);
|
||||
reply.code(401).send(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async updateList() {
|
||||
this.fastify.put<{ Params: { id: string } }>('/v1/lists/:id', async (_request, reply) => {
|
||||
public updateList() {
|
||||
this.fastify.put<{ Params: { id?: string }, Body: { title?: string } }>('/v1/lists/:id', async (_request, reply) => {
|
||||
try {
|
||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
if (!_request.body.title) return reply.code(400).send({ error: 'Missing required payload "title"' });
|
||||
const BASE_URL = `${_request.protocol}://${_request.host}`;
|
||||
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);
|
||||
const data = await client.updateList(_request.params.id, _request.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);
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`PUT /v1/lists/${_request.params.id}`, data);
|
||||
reply.code(401).send(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async deleteList() {
|
||||
this.fastify.delete<{ Params: { id: string } }>('/v1/lists/:id', async (_request, reply) => {
|
||||
public deleteList() {
|
||||
this.fastify.delete<{ Params: { id?: string } }>('/v1/lists/:id', async (_request, reply) => {
|
||||
try {
|
||||
const BASE_URL = `${_request.protocol}://${_request.hostname}`;
|
||||
if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' });
|
||||
const BASE_URL = `${_request.protocol}://${_request.host}`;
|
||||
const accessTokens = _request.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
const params: any = _request.params;
|
||||
const data = await client.deleteList(params.id);
|
||||
await client.deleteList(_request.params.id);
|
||||
reply.send({});
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
reply.code(401).send(e.response.data);
|
||||
} catch (e) {
|
||||
const data = getErrorData(e);
|
||||
this.logger.error(`DELETE /v1/lists/${_request.params.id}`, data);
|
||||
reply.code(401).send(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
47
packages/backend/src/server/api/mastodon/timelineArgs.ts
Normal file
47
packages/backend/src/server/api/mastodon/timelineArgs.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
// Keys taken from:
|
||||
// - https://docs.joinmastodon.org/methods/accounts/#statuses
|
||||
// - https://docs.joinmastodon.org/methods/timelines/#public
|
||||
// - https://docs.joinmastodon.org/methods/timelines/#tag
|
||||
export interface TimelineArgs {
|
||||
max_id?: string;
|
||||
min_id?: string;
|
||||
since_id?: string;
|
||||
limit?: string;
|
||||
offset?: string;
|
||||
local?: string;
|
||||
pinned?: string;
|
||||
exclude_reblogs?: string;
|
||||
exclude_replies?: string;
|
||||
only_media?: string;
|
||||
}
|
||||
|
||||
// Values taken from https://docs.joinmastodon.org/client/intro/#boolean
|
||||
export function toBoolean(value: string | undefined): boolean | undefined {
|
||||
if (value === undefined) return undefined;
|
||||
return !['0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].includes(value);
|
||||
}
|
||||
|
||||
export function toInt(value: string | undefined): number | undefined {
|
||||
if (value === undefined) return undefined;
|
||||
return parseInt(value);
|
||||
}
|
||||
|
||||
export function parseTimelineArgs(q: TimelineArgs) {
|
||||
return {
|
||||
max_id: q.max_id,
|
||||
min_id: q.min_id,
|
||||
since_id: q.since_id,
|
||||
limit: typeof(q.limit) === 'string' ? parseInt(q.limit, 10) : undefined,
|
||||
offset: typeof(q.offset) === 'string' ? parseInt(q.offset, 10) : undefined,
|
||||
local: typeof(q.local) === 'string' ? toBoolean(q.local) : undefined,
|
||||
pinned: typeof(q.pinned) === 'string' ? toBoolean(q.pinned) : undefined,
|
||||
exclude_reblogs: typeof(q.exclude_reblogs) === 'string' ? toBoolean(q.exclude_reblogs) : undefined,
|
||||
exclude_replies: typeof(q.exclude_replies) === 'string' ? toBoolean(q.exclude_replies) : undefined,
|
||||
only_media: typeof(q.only_media) === 'string' ? toBoolean(q.only_media) : undefined,
|
||||
};
|
||||
}
|
||||
|
|
@ -3,13 +3,15 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { deepClone } from '@/misc/clone.js';
|
||||
import type { Schema } from '@/misc/json-schema.js';
|
||||
import { refs } from '@/misc/json-schema.js';
|
||||
|
||||
export function convertSchemaToOpenApiSchema(schema: Schema, type: 'param' | 'res', includeSelfRef: boolean): any {
|
||||
// optional, nullable, refはスキーマ定義に含まれないので分離しておく
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { optional, nullable, ref, selfRef, ...res }: any = schema;
|
||||
const { optional, nullable, ref, selfRef, ..._res }: any = schema;
|
||||
const res = deepClone(_res);
|
||||
|
||||
if (schema.type === 'object' && schema.properties) {
|
||||
if (type === 'res') {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue