Merge tag '12.112.1' into develop

This commit is contained in:
2022-07-08 23:44:00 +09:00
573 changed files with 19694 additions and 14145 deletions

View File

@ -1,12 +1,25 @@
import Koa from 'koa';
import { User } from '@/models/entities/user.js';
import { UserIps } from '@/models/index.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
import { IEndpoint } from './endpoints.js';
import authenticate, { AuthenticationError } from './authenticate.js';
import call from './call.js';
import { ApiError } from './error.js';
const userIpHistories = new Map<User['id'], Set<string>>();
setInterval(() => {
userIpHistories.clear();
}, 1000 * 60 * 60);
export default (endpoint: IEndpoint, ctx: Koa.Context) => new Promise<void>((res) => {
const body = ctx.request.body;
const body = ctx.is('multipart/form-data')
? (ctx.request as any).body
: ctx.method === 'GET'
? ctx.query
: ctx.request.body;
const reply = (x?: any, y?: ApiError) => {
if (x == null) {
@ -33,10 +46,38 @@ export default (endpoint: IEndpoint, ctx: Koa.Context) => new Promise<void>((res
authenticate(body['i']).then(([user, app]) => {
// API invoking
call(endpoint.name, user, app, body, ctx).then((res: any) => {
if (ctx.method === 'GET' && endpoint.meta.cacheSec && !body['i'] && !user) {
ctx.set('Cache-Control', `public, max-age=${endpoint.meta.cacheSec}`);
}
reply(res);
}).catch((e: ApiError) => {
reply(e.httpStatusCode ? e.httpStatusCode : e.kind === 'client' ? 400 : 500, e);
});
// Log IP
if (user) {
fetchMeta().then(meta => {
if (!meta.enableIpLogging) return;
const ip = ctx.ip;
const ips = userIpHistories.get(user.id);
if (ips == null || !ips.has(ip)) {
if (ips == null) {
userIpHistories.set(user.id, new Set([ip]));
} else {
ips.add(ip);
}
try {
UserIps.insert({
createdAt: new Date(),
userId: user.id,
ip: ip,
});
} catch {
}
}
});
}
}).catch(e => {
if (e instanceof AuthenticationError) {
reply(403, new ApiError({

View File

@ -1,12 +1,12 @@
import Koa from 'koa';
import { performance } from 'perf_hooks';
import { limiter } from './limiter.js';
import Koa from 'koa';
import { CacheableLocalUser, User } from '@/models/entities/user.js';
import { AccessToken } from '@/models/entities/access-token.js';
import { getIpHash } from '@/misc/get-ip-hash.js';
import { limiter } from './limiter.js';
import endpoints, { IEndpointMeta } from './endpoints.js';
import { ApiError } from './error.js';
import { apiLogger } from './logger.js';
import { AccessToken } from '@/models/entities/access-token.js';
import { getIpHash } from '@/misc/get-ip-hash.js';
const accessDenied = {
message: 'Access denied.',
@ -33,7 +33,7 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi
throw new ApiError(accessDenied);
}
if (ep.meta.limit && !isModerator) {
if (ep.meta.limit) {
// koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app.
let limitActor: string;
if (user) {
@ -94,7 +94,7 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi
}
// Cast non JSON input
if (ep.meta.requireFile && ep.params.properties) {
if ((ep.meta.requireFile || ctx?.method === 'GET') && ep.params.properties) {
for (const k of Object.keys(ep.params.properties)) {
const param = ep.params.properties![k];
if (['boolean', 'number', 'integer'].includes(param.type ?? '') && typeof data[k] === 'string') {
@ -116,24 +116,24 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi
// API invoking
const before = performance.now();
return await ep.exec(data, user, token, ctx?.file).catch((e: Error) => {
return await ep.exec(data, user, token, ctx?.file, ctx?.ip, ctx?.headers).catch((e: Error) => {
if (e instanceof ApiError) {
throw e;
} else {
apiLogger.error(`Internal error occurred in ${ep.name}: ${e?.message}`, {
apiLogger.error(`Internal error occurred in ${ep.name}: ${e.message}`, {
ep: ep.name,
ps: data,
e: {
message: e?.message,
code: e?.name,
stack: e?.stack,
message: e.message,
code: e.name,
stack: e.stack,
},
});
throw new ApiError(null, {
e: {
message: e?.message,
code: e?.name,
stack: e?.stack,
message: e.message,
code: e.name,
stack: e.stack,
},
});
}

View File

@ -1,40 +0,0 @@
import { User } from '@/models/entities/user.js';
import { id } from '@/models/id.js';
import { UserProfiles } from '@/models/index.js';
import { SelectQueryBuilder, Brackets } from 'typeorm';
function createMutesQuery(id: string) {
return UserProfiles.createQueryBuilder('user_profile')
.select('user_profile.mutedInstances')
.where('user_profile.userId = :muterId', { muterId: id });
}
export function generateMutedInstanceQuery(q: SelectQueryBuilder<any>, me: { id: User['id'] }) {
const mutingQuery = createMutesQuery(me.id);
q
.andWhere(new Brackets(qb => { qb
.andWhere('note.userHost IS NULL')
.orWhere(`NOT((${ mutingQuery.getQuery() })::jsonb ? note.userHost)`);
}))
.andWhere(new Brackets(qb => { qb
.where(`note.replyUserHost IS NULL`)
.orWhere(`NOT ((${ mutingQuery.getQuery() })::jsonb ? note.replyUserHost)`);
}))
.andWhere(new Brackets(qb => { qb
.where(`note.renoteUserHost IS NULL`)
.orWhere(`NOT ((${ mutingQuery.getQuery() })::jsonb ? note.renoteUserHost)`);
}));
q.setParameters(mutingQuery.getParameters());
}
export function generateMutedInstanceNotificationQuery(q: SelectQueryBuilder<any>, me: { id: User['id'] }) {
const mutingQuery = createMutesQuery(me.id);
q.andWhere(new Brackets(qb => { qb
.andWhere('notifier.host IS NULL')
.orWhere(`NOT (( ${mutingQuery.getQuery()} )::jsonb ? notifier.host)`);
}));
q.setParameters(mutingQuery.getParameters());
}

View File

@ -1,6 +1,6 @@
import { User } from '@/models/entities/user.js';
import { Mutings } from '@/models/index.js';
import { SelectQueryBuilder, Brackets } from 'typeorm';
import { User } from '@/models/entities/user.js';
import { Mutings, UserProfiles } from '@/models/index.js';
export function generateMutedUserQuery(q: SelectQueryBuilder<any>, me: { id: User['id'] }, exclude?: User) {
const mutingQuery = Mutings.createQueryBuilder('muting')
@ -11,21 +11,39 @@ export function generateMutedUserQuery(q: SelectQueryBuilder<any>, me: { id: Use
mutingQuery.andWhere('muting.muteeId != :excludeId', { excludeId: exclude.id });
}
const mutingInstanceQuery = UserProfiles.createQueryBuilder('user_profile')
.select('user_profile.mutedInstances')
.where('user_profile.userId = :muterId', { muterId: me.id });
// 投稿の作者をミュートしていない かつ
// 投稿の返信先の作者をミュートしていない かつ
// 投稿の引用元の作者をミュートしていない
q
.andWhere(`note.userId NOT IN (${ mutingQuery.getQuery() })`)
.andWhere(new Brackets(qb => { qb
.where(`note.replyUserId IS NULL`)
.where('note.replyUserId IS NULL')
.orWhere(`note.replyUserId NOT IN (${ mutingQuery.getQuery() })`);
}))
.andWhere(new Brackets(qb => { qb
.where(`note.renoteUserId IS NULL`)
.where('note.renoteUserId IS NULL')
.orWhere(`note.renoteUserId NOT IN (${ mutingQuery.getQuery() })`);
}))
// mute instances
.andWhere(new Brackets(qb => { qb
.andWhere('note.userHost IS NULL')
.orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.userHost)`);
}))
.andWhere(new Brackets(qb => { qb
.where('note.replyUserHost IS NULL')
.orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.replyUserHost)`);
}))
.andWhere(new Brackets(qb => { qb
.where('note.renoteUserHost IS NULL')
.orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.renoteUserHost)`);
}));
q.setParameters(mutingQuery.getParameters());
q.setParameters(mutingInstanceQuery.getParameters());
}
export function generateMutedUserQueryForUsers(q: SelectQueryBuilder<any>, me: { id: User['id'] }) {
@ -33,8 +51,7 @@ export function generateMutedUserQueryForUsers(q: SelectQueryBuilder<any>, me: {
.select('muting.muteeId')
.where('muting.muterId = :muterId', { muterId: me.id });
q
.andWhere(`user.id NOT IN (${ mutingQuery.getQuery() })`);
q.andWhere(`user.id NOT IN (${ mutingQuery.getQuery() })`);
q.setParameters(mutingQuery.getParameters());
}

View File

@ -1,16 +1,16 @@
import * as fs from 'node:fs';
import Ajv from 'ajv';
import { CacheableLocalUser, ILocalUser } from '@/models/entities/user.js';
import { IEndpointMeta } from './endpoints.js';
import { ApiError } from './error.js';
import { Schema, SchemaType } from '@/misc/schema.js';
import { AccessToken } from '@/models/entities/access-token.js';
import { IEndpointMeta } from './endpoints.js';
import { ApiError } from './error.js';
export type Response = Record<string, any> | void;
// TODO: paramsの型をT['params']のスキーマ定義から推論する
type executor<T extends IEndpointMeta, Ps extends Schema> =
(params: SchemaType<Ps>, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, cleanup?: () => any) =>
(params: SchemaType<Ps>, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, cleanup?: () => any, ip?: string | null, headers?: Record<string, string> | null) =>
Promise<T['res'] extends undefined ? Response : SchemaType<NonNullable<T['res']>>>;
const ajv = new Ajv({
@ -20,24 +20,27 @@ const ajv = new Ajv({
ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/);
export default function <T extends IEndpointMeta, Ps extends Schema>(meta: T, paramDef: Ps, cb: executor<T, Ps>)
: (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any) => Promise<any> {
: (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, ip?: string | null, headers?: Record<string, string> | null) => Promise<any> {
const validate = ajv.compile(paramDef);
return (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any) => {
function cleanup() {
fs.unlink(file.path, () => {});
}
return (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, ip?: string | null, headers?: Record<string, string> | null) => {
let cleanup: undefined | (() => void) = undefined;
if (meta.requireFile && file == null) return Promise.reject(new ApiError({
message: 'File required.',
code: 'FILE_REQUIRED',
id: '4267801e-70d1-416a-b011-4ee502885d8b',
}));
if (meta.requireFile) {
cleanup = () => {
fs.unlink(file.path, () => {});
};
if (file == null) return Promise.reject(new ApiError({
message: 'File required.',
code: 'FILE_REQUIRED',
id: '4267801e-70d1-416a-b011-4ee502885d8b',
}));
}
const valid = validate(params);
if (!valid) {
if (file) cleanup();
if (file) cleanup!();
const errors = validate.errors!;
const err = new ApiError({
@ -51,6 +54,6 @@ export default function <T extends IEndpointMeta, Ps extends Schema>(meta: T, pa
return Promise.reject(err);
}
return cb(params as SchemaType<Ps>, user, token, file, cleanup);
return cb(params as SchemaType<Ps>, user, token, file, cleanup, ip, headers);
};
}

View File

@ -35,6 +35,7 @@ import * as ep___admin_federation_removeAllFollowing from './endpoints/admin/fed
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 from './endpoints/admin/invite.js';
import * as ep___admin_moderators_add from './endpoints/admin/moderators/add.js';
import * as ep___admin_moderators_remove from './endpoints/admin/moderators/remove.js';
@ -59,6 +60,8 @@ import * as ep___admin_unsilenceUser from './endpoints/admin/unsilence-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_vacuum from './endpoints/admin/vacuum.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___announcements from './endpoints/announcements.js';
import * as ep___antennas_create from './endpoints/antennas/create.js';
import * as ep___antennas_delete from './endpoints/antennas/delete.js';
@ -99,6 +102,7 @@ import * as ep___charts_user_notes from './endpoints/charts/user/notes.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';
@ -133,6 +137,7 @@ 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_invalidate from './endpoints/following/invalidate.js';
@ -308,6 +313,8 @@ import * as ep___users_searchByUsernameAndHost from './endpoints/users/search-by
import * as ep___users_search from './endpoints/users/search.js';
import * as ep___users_show from './endpoints/users/show.js';
import * as ep___users_stats from './endpoints/users/stats.js';
import * as ep___fetchRss from './endpoints/fetch-rss.js';
import * as ep___admin_driveCapOverride from './endpoints/admin/drive-capacity-override.js';
const eps = [
['admin/meta', ep___admin_meta],
@ -345,6 +352,7 @@ const eps = [
['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', ep___admin_invite],
['admin/moderators/add', ep___admin_moderators_add],
['admin/moderators/remove', ep___admin_moderators_remove],
@ -369,6 +377,8 @@ const eps = [
['admin/unsuspend-user', ep___admin_unsuspendUser],
['admin/update-meta', ep___admin_updateMeta],
['admin/vacuum', ep___admin_vacuum],
['admin/delete-account', ep___admin_deleteAccount],
['admin/update-user-note', ep___admin_updateUserNote],
['announcements', ep___announcements],
['antennas/create', ep___antennas_create],
['antennas/delete', ep___antennas_delete],
@ -409,6 +419,7 @@ const eps = [
['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],
@ -443,6 +454,7 @@ const eps = [
['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/invalidate', ep___following_invalidate],
@ -618,6 +630,8 @@ const eps = [
['users/search', ep___users_search],
['users/show', ep___users_show],
['users/stats', ep___users_stats],
['admin/drive-capacity-override', ep___admin_driveCapOverride],
['fetch-rss', ep___fetchRss],
];
export interface IEndpointMeta {
@ -699,6 +713,16 @@ export interface IEndpointMeta {
readonly kind?: string;
readonly description?: string;
/**
* GETでのリクエストを許容するか否か
*/
readonly allowGet?: boolean;
/**
* 正常応答をキャッシュ (Cache-Control: public) する秒数
*/
readonly cacheSec?: number;
}
export interface IEndpoint {

View File

@ -0,0 +1,31 @@
import { Users } from '@/models/index.js';
import { deleteAccount } from '@/services/delete-account.js';
import define from '../../define.js';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireAdmin: true,
res: {
},
} as const;
export const paramDef = {
type: 'object',
properties: {
userId: { type: 'string', format: 'misskey:id' },
},
required: ['userId'],
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps) => {
const user = await Users.findOneByOrFail({ id: ps.userId });
if (user.isDeleted) {
return;
}
await deleteAccount(user);
});

View File

@ -0,0 +1,47 @@
import define from '../../define.js';
import { Users } from '@/models/index.js';
import { User } from '@/models/entities/user.js';
import { insertModerationLog } from '@/services/insert-moderation-log.js';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
} as const;
export const paramDef = {
type: 'object',
properties: {
userId: { type: 'string', format: 'misskey:id' },
overrideMb: { type: 'number', nullable: true },
},
required: ['userId', 'overrideMb'],
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, me) => {
const user = await Users.findOneBy({ id: ps.userId });
if (user == null) {
throw new Error('user not found');
}
if (!Users.isLocalUser(user)) {
throw new Error('user is not local user');
}
/*if (user.isAdmin) {
throw new Error('cannot suspend admin');
}
if (user.isModerator) {
throw new Error('cannot suspend moderator');
}*/
await Users.update(user.id, {
driveCapacityOverrideMb: ps.overrideMb,
});
insertModerationLog(me, 'change-drive-capacity-override', {
targetId: user.id,
});
});

View File

@ -1,5 +1,5 @@
import define from '../../../define.js';
import { DriveFiles } from '@/models/index.js';
import define from '../../../define.js';
import { makePaginationQuery } from '../../../common/make-pagination-query.js';
export const meta = {
@ -25,8 +25,9 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
userId: { type: 'string', format: 'misskey:id', nullable: true },
type: { type: 'string', nullable: true, pattern: /^[a-zA-Z0-9\/\-*]+$/.toString().slice(1, -1) },
origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" },
origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'local' },
hostname: {
type: 'string',
nullable: true,
@ -41,14 +42,18 @@ export const paramDef = {
export default define(meta, paramDef, async (ps, me) => {
const query = makePaginationQuery(DriveFiles.createQueryBuilder('file'), ps.sinceId, ps.untilId);
if (ps.origin === 'local') {
query.andWhere('file.userHost IS NULL');
} else if (ps.origin === 'remote') {
query.andWhere('file.userHost IS NOT NULL');
}
if (ps.userId) {
query.andWhere('file.userId = :userId', { userId: ps.userId });
} else {
if (ps.origin === 'local') {
query.andWhere('file.userHost IS NULL');
} else if (ps.origin === 'remote') {
query.andWhere('file.userHost IS NOT NULL');
}
if (ps.hostname) {
query.andWhere('file.userHost = :hostname', { hostname: ps.hostname });
if (ps.hostname) {
query.andWhere('file.userHost = :hostname', { hostname: ps.hostname });
}
}
if (ps.type) {

View File

@ -1,6 +1,6 @@
import { DriveFiles } from '@/models/index.js';
import define from '../../../define.js';
import { ApiError } from '../../../error.js';
import { DriveFiles } from '@/models/index.js';
export const meta = {
tags: ['admin'],
@ -184,5 +184,10 @@ export default define(meta, paramDef, async (ps, me) => {
throw new ApiError(meta.errors.noSuchFile);
}
if (!me.isAdmin) {
delete file.requestIp;
delete file.requestHeaders;
}
return file;
});

View File

@ -0,0 +1,31 @@
import { UserIps } from '@/models/index.js';
import define from '../../define.js';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireAdmin: true,
} as const;
export const paramDef = {
type: 'object',
properties: {
userId: { type: 'string', format: 'misskey:id' },
},
required: ['userId'],
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, me) => {
const ips = await UserIps.find({
where: { userId: ps.userId },
order: { createdAt: 'DESC' },
take: 30,
});
return ips.map(x => ({
ip: x.ip,
createdAt: x.createdAt.toISOString(),
}));
});

View File

@ -1,7 +1,7 @@
import config from '@/config/index.js';
import define from '../../define.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
import define from '../../define.js';
export const meta = {
tags: ['meta'],
@ -195,6 +195,22 @@ export const meta = {
type: 'string',
optional: true, nullable: true,
},
sensitiveMediaDetection: {
type: 'string',
optional: true, nullable: false,
},
sensitiveMediaDetectionSensitivity: {
type: 'string',
optional: true, nullable: false,
},
setSensitiveFlagAutomatically: {
type: 'boolean',
optional: true, nullable: false,
},
enableSensitiveMediaDetectionForVideos: {
type: 'boolean',
optional: true, nullable: false,
},
proxyAccountId: {
type: 'string',
optional: true, nullable: true,
@ -304,6 +320,10 @@ export const meta = {
type: 'boolean',
optional: true, nullable: false,
},
enableIpLogging: {
type: 'boolean',
optional: true, nullable: false,
},
},
},
} as const;
@ -360,13 +380,16 @@ export default define(meta, paramDef, async (ps, me) => {
pinnedPages: instance.pinnedPages,
pinnedClipId: instance.pinnedClipId,
cacheRemoteFiles: instance.cacheRemoteFiles,
useStarForReactionFallback: instance.useStarForReactionFallback,
pinnedUsers: instance.pinnedUsers,
hiddenTags: instance.hiddenTags,
blockedHosts: instance.blockedHosts,
hcaptchaSecretKey: instance.hcaptchaSecretKey,
recaptchaSecretKey: instance.recaptchaSecretKey,
sensitiveMediaDetection: instance.sensitiveMediaDetection,
sensitiveMediaDetectionSensitivity: instance.sensitiveMediaDetectionSensitivity,
setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically,
enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos,
proxyAccountId: instance.proxyAccountId,
twitterConsumerKey: instance.twitterConsumerKey,
twitterConsumerSecret: instance.twitterConsumerSecret,
@ -397,5 +420,6 @@ export default define(meta, paramDef, async (ps, me) => {
objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle,
deeplAuthKey: instance.deeplAuthKey,
deeplIsPro: instance.deeplIsPro,
enableIpLogging: instance.enableIpLogging,
};
});

View File

@ -99,12 +99,16 @@ export default define(meta, paramDef, async () => {
const fsStats = await si.fsSize();
const netInterface = await si.networkInterfaceDefault();
const redisServerInfo = await redisClient.info('Server');
const m = redisServerInfo.match(new RegExp('^redis_version:(.*)', 'm'));
const redis_version = m?.[1];
return {
machine: os.hostname(),
os: os.platform(),
node: process.version,
psql: await db.query('SHOW server_version').then(x => x[0].server_version),
redis: redisClient.server_info.redis_version,
redis: redis_version,
cpu: {
model: os.cpus()[0].model,
cores: os.cpus().length,

View File

@ -25,7 +25,7 @@ export const paramDef = {
export default define(meta, paramDef, async (ps, me) => {
const [user, profile] = await Promise.all([
Users.findOneBy({ id: ps.userId }),
UserProfiles.findOneBy({ userId: ps.userId })
UserProfiles.findOneBy({ userId: ps.userId }),
]);
if (user == null || profile == null) {
@ -58,6 +58,7 @@ export default define(meta, paramDef, async (ps, me) => {
autoAcceptFollowed: profile.autoAcceptFollowed,
noCrawle: profile.noCrawle,
alwaysMarkNsfw: profile.alwaysMarkNsfw,
autoSensitive: profile.autoSensitive,
carefulBot: profile.carefulBot,
injectFeaturedNote: profile.injectFeaturedNote,
receiveAnnouncementEmail: profile.receiveAnnouncementEmail,
@ -68,6 +69,8 @@ export default define(meta, paramDef, async (ps, me) => {
isModerator: user.isModerator,
isSilenced: user.isSilenced,
isSuspended: user.isSuspended,
lastActiveDate: user.lastActiveDate,
moderationNote: profile.moderationNote,
signins,
};
});

View File

@ -25,7 +25,7 @@ export const paramDef = {
offset: { type: 'integer', default: 0 },
sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt'] },
state: { type: 'string', enum: ['all', 'alive', 'available', 'admin', 'moderator', 'adminOrModerator', 'silenced', 'suspended'], default: 'all' },
origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'local' },
origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'combined' },
username: { type: 'string', nullable: true, default: null },
hostname: {
type: 'string',
@ -61,7 +61,7 @@ export default define(meta, paramDef, async (ps, me) => {
}
if (ps.hostname) {
query.andWhere('user.host like :hostname', { hostname: '%' + ps.hostname.toLowerCase() + '%' });
query.andWhere('user.host = :hostname', { hostname: ps.hostname.toLowerCase() });
}
switch (ps.sort) {

View File

@ -1,8 +1,8 @@
import define from '../../define.js';
import { Meta } from '@/models/entities/meta.js';
import { insertModerationLog } from '@/services/insert-moderation-log.js';
import { DB_MAX_NOTE_TEXT_LENGTH } from '@/misc/hard-limits.js';
import { db } from '@/db/postgre.js';
import define from '../../define.js';
export const meta = {
tags: ['admin'],
@ -48,6 +48,10 @@ export const paramDef = {
enableRecaptcha: { type: 'boolean' },
recaptchaSiteKey: { type: 'string', nullable: true },
recaptchaSecretKey: { type: 'string', nullable: true },
sensitiveMediaDetection: { type: 'string', enum: ['none', 'all', 'local', 'remote'] },
sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] },
setSensitiveFlagAutomatically: { type: 'boolean' },
enableSensitiveMediaDetectionForVideos: { type: 'boolean' },
proxyAccountId: { type: 'string', format: 'misskey:id', nullable: true },
maintainerName: { type: 'string', nullable: true },
maintainerEmail: { type: 'string', nullable: true },
@ -96,6 +100,7 @@ export const paramDef = {
objectStorageUseProxy: { type: 'boolean' },
objectStorageSetPublicRead: { type: 'boolean' },
objectStorageS3ForcePathStyle: { type: 'boolean' },
enableIpLogging: { type: 'boolean' },
},
required: [],
} as const;
@ -212,6 +217,22 @@ export default define(meta, paramDef, async (ps, me) => {
set.recaptchaSecretKey = ps.recaptchaSecretKey;
}
if (ps.sensitiveMediaDetection !== undefined) {
set.sensitiveMediaDetection = ps.sensitiveMediaDetection;
}
if (ps.sensitiveMediaDetectionSensitivity !== undefined) {
set.sensitiveMediaDetectionSensitivity = ps.sensitiveMediaDetectionSensitivity;
}
if (ps.setSensitiveFlagAutomatically !== undefined) {
set.setSensitiveFlagAutomatically = ps.setSensitiveFlagAutomatically;
}
if (ps.enableSensitiveMediaDetectionForVideos !== undefined) {
set.enableSensitiveMediaDetectionForVideos = ps.enableSensitiveMediaDetectionForVideos;
}
if (ps.proxyAccountId !== undefined) {
set.proxyAccountId = ps.proxyAccountId;
}
@ -396,6 +417,10 @@ export default define(meta, paramDef, async (ps, me) => {
set.deeplIsPro = ps.deeplIsPro;
}
if (ps.enableIpLogging !== undefined) {
set.enableIpLogging = ps.enableIpLogging;
}
await db.transaction(async transactionalEntityManager => {
const metas = await transactionalEntityManager.find(Meta, {
order: {

View File

@ -0,0 +1,31 @@
import { UserProfiles, Users } from '@/models/index.js';
import define from '../../define.js';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
} as const;
export const paramDef = {
type: 'object',
properties: {
userId: { type: 'string', format: 'misskey:id' },
text: { type: 'string' },
},
required: ['userId', 'text'],
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, me) => {
const user = await Users.findOneBy({ id: ps.userId });
if (user == null) {
throw new Error('user not found');
}
await UserProfiles.update({ userId: user.id }, {
moderationNote: ps.text,
});
});

View File

@ -1,5 +1,5 @@
import define from '../define.js';
import { Announcements, AnnouncementReads } from '@/models/index.js';
import define from '../define.js';
import { makePaginationQuery } from '../common/make-pagination-query.js';
export const meta = {

View File

@ -2,12 +2,13 @@ import define from '../../define.js';
import config from '@/config/index.js';
import { createPerson } from '@/remote/activitypub/models/person.js';
import { createNote } from '@/remote/activitypub/models/note.js';
import DbResolver from '@/remote/activitypub/db-resolver.js';
import Resolver from '@/remote/activitypub/resolver.js';
import { ApiError } from '../../error.js';
import { extractDbHost } from '@/misc/convert-host.js';
import { Users, Notes } from '@/models/index.js';
import { Note } from '@/models/entities/note.js';
import { User } from '@/models/entities/user.js';
import { CacheableLocalUser, User } from '@/models/entities/user.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
import { isActor, isPost, getApId } from '@/remote/activitypub/type.js';
import ms from 'ms';
@ -77,8 +78,8 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps) => {
const object = await fetchAny(ps.uri);
export default define(meta, paramDef, async (ps, me) => {
const object = await fetchAny(ps.uri, me);
if (object) {
return object;
} else {
@ -89,48 +90,18 @@ export default define(meta, paramDef, async (ps) => {
/***
* URIからUserかNoteを解決する
*/
async function fetchAny(uri: string): Promise<SchemaType<typeof meta['res']> | null> {
// URIがこのサーバーを指しているなら、ローカルユーザーIDとしてDBからフェッチ
if (uri.startsWith(config.url + '/')) {
const parts = uri.split('/');
const id = parts.pop();
const type = parts.pop();
if (type === 'notes') {
const note = await Notes.findOneBy({ id });
if (note) {
return {
type: 'Note',
object: await Notes.pack(note, null, { detail: true }),
};
}
} else if (type === 'users') {
const user = await Users.findOneBy({ id });
if (user) {
return {
type: 'User',
object: await Users.pack(user, null, { detail: true }),
};
}
}
}
async function fetchAny(uri: string, me: CacheableLocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
// ブロックしてたら中断
const fetchedMeta = await fetchMeta();
if (fetchedMeta.blockedHosts.includes(extractDbHost(uri))) return null;
// URI(AP Object id)としてDB検索
{
const [user, note] = await Promise.all([
Users.findOneBy({ uri: uri }),
Notes.findOneBy({ uri: uri }),
]);
const dbResolver = new DbResolver();
const packed = await mergePack(user, note);
if (packed !== null) return packed;
}
let local = await mergePack(me, ...await Promise.all([
dbResolver.getUserFromApId(uri),
dbResolver.getNoteFromApId(uri),
]));
if (local != null) return local;
// リモートから一旦オブジェクトフェッチ
const resolver = new Resolver();
@ -139,74 +110,37 @@ async function fetchAny(uri: string): Promise<SchemaType<typeof meta['res']> | n
// /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する
// これはDBに存在する可能性があるため再度DB検索
if (uri !== object.id) {
if (object.id.startsWith(config.url + '/')) {
const parts = object.id.split('/');
const id = parts.pop();
const type = parts.pop();
if (type === 'notes') {
const note = await Notes.findOneBy({ id });
if (note) {
return {
type: 'Note',
object: await Notes.pack(note, null, { detail: true }),
};
}
} else if (type === 'users') {
const user = await Users.findOneBy({ id });
if (user) {
return {
type: 'User',
object: await Users.pack(user, null, { detail: true }),
};
}
}
}
const [user, note] = await Promise.all([
Users.findOneBy({ uri: object.id }),
Notes.findOneBy({ uri: object.id }),
]);
const packed = await mergePack(user, note);
if (packed !== null) return packed;
local = await mergePack(me, ...await Promise.all([
dbResolver.getUserFromApId(object.id),
dbResolver.getNoteFromApId(object.id),
]));
if (local != null) return local;
}
// それでもみつからなければ新規であるため登録
if (isActor(object)) {
const user = await createPerson(getApId(object));
return {
type: 'User',
object: await Users.pack(user, null, { detail: true }),
};
}
if (isPost(object)) {
const note = await createNote(getApId(object), undefined, true);
return {
type: 'Note',
object: await Notes.pack(note!, null, { detail: true }),
};
}
return null;
return await mergePack(
me,
isActor(object) ? await createPerson(getApId(object)) : null,
isPost(object) ? await createNote(getApId(object), undefined, true) : null,
);
}
async function mergePack(user: User | null | undefined, note: Note | null | undefined): Promise<SchemaType<typeof meta.res> | null> {
async function mergePack(me: CacheableLocalUser | null | undefined, user: User | null | undefined, note: Note | null | undefined): Promise<SchemaType<typeof meta.res> | null> {
if (user != null) {
return {
type: 'User',
object: await Users.pack(user, null, { detail: true }),
object: await Users.pack(user, me, { detail: true }),
};
}
} else if (note != null) {
try {
const object = await Notes.pack(note, me, { detail: true });
if (note != null) {
return {
type: 'Note',
object: await Notes.pack(note, null, { detail: true }),
};
return {
type: 'Note',
object,
};
} catch (e) {
return null;
}
}
return null;

View File

@ -1,11 +1,14 @@
import define from '../../define.js';
import { getJsonSchema } from '@/services/chart/core.js';
import { activeUsersChart } from '@/services/chart/index.js';
import define from '../../define.js';
export const meta = {
tags: ['charts', 'users'],
res: getJsonSchema(activeUsersChart.schema),
allowGet: true,
cacheSec: 60 * 60,
} as const;
export const paramDef = {

View File

@ -1,11 +1,14 @@
import define from '../../define.js';
import { getJsonSchema } from '@/services/chart/core.js';
import { apRequestChart } from '@/services/chart/index.js';
import define from '../../define.js';
export const meta = {
tags: ['charts'],
res: getJsonSchema(apRequestChart.schema),
allowGet: true,
cacheSec: 60 * 60,
} as const;
export const paramDef = {

View File

@ -1,11 +1,14 @@
import define from '../../define.js';
import { getJsonSchema } from '@/services/chart/core.js';
import { driveChart } from '@/services/chart/index.js';
import define from '../../define.js';
export const meta = {
tags: ['charts', 'drive'],
res: getJsonSchema(driveChart.schema),
allowGet: true,
cacheSec: 60 * 60,
} as const;
export const paramDef = {

View File

@ -1,11 +1,14 @@
import define from '../../define.js';
import { getJsonSchema } from '@/services/chart/core.js';
import { federationChart } from '@/services/chart/index.js';
import define from '../../define.js';
export const meta = {
tags: ['charts'],
res: getJsonSchema(federationChart.schema),
allowGet: true,
cacheSec: 60 * 60,
} as const;
export const paramDef = {

View File

@ -1,11 +1,14 @@
import define from '../../define.js';
import { getJsonSchema } from '@/services/chart/core.js';
import { hashtagChart } from '@/services/chart/index.js';
import define from '../../define.js';
export const meta = {
tags: ['charts', 'hashtags'],
res: getJsonSchema(hashtagChart.schema),
allowGet: true,
cacheSec: 60 * 60,
} as const;
export const paramDef = {

View File

@ -1,11 +1,14 @@
import define from '../../define.js';
import { getJsonSchema } from '@/services/chart/core.js';
import { instanceChart } from '@/services/chart/index.js';
import define from '../../define.js';
export const meta = {
tags: ['charts'],
res: getJsonSchema(instanceChart.schema),
allowGet: true,
cacheSec: 60 * 60,
} as const;
export const paramDef = {

View File

@ -1,11 +1,14 @@
import define from '../../define.js';
import { getJsonSchema } from '@/services/chart/core.js';
import { notesChart } from '@/services/chart/index.js';
import define from '../../define.js';
export const meta = {
tags: ['charts', 'notes'],
res: getJsonSchema(notesChart.schema),
allowGet: true,
cacheSec: 60 * 60,
} as const;
export const paramDef = {

View File

@ -1,11 +1,14 @@
import define from '../../../define.js';
import { getJsonSchema } from '@/services/chart/core.js';
import { perUserDriveChart } from '@/services/chart/index.js';
import define from '../../../define.js';
export const meta = {
tags: ['charts', 'drive', 'users'],
res: getJsonSchema(perUserDriveChart.schema),
allowGet: true,
cacheSec: 60 * 60,
} as const;
export const paramDef = {

View File

@ -6,6 +6,9 @@ export const meta = {
tags: ['charts', 'users', 'following'],
res: getJsonSchema(perUserFollowingChart.schema),
allowGet: true,
cacheSec: 60 * 60,
} as const;
export const paramDef = {

View File

@ -1,11 +1,14 @@
import define from '../../../define.js';
import { getJsonSchema } from '@/services/chart/core.js';
import { perUserNotesChart } from '@/services/chart/index.js';
import define from '../../../define.js';
export const meta = {
tags: ['charts', 'users', 'notes'],
res: getJsonSchema(perUserNotesChart.schema),
allowGet: true,
cacheSec: 60 * 60,
} as const;
export const paramDef = {

View File

@ -1,11 +1,14 @@
import define from '../../../define.js';
import { getJsonSchema } from '@/services/chart/core.js';
import { perUserReactionsChart } from '@/services/chart/index.js';
import define from '../../../define.js';
export const meta = {
tags: ['charts', 'users', 'reactions'],
res: getJsonSchema(perUserReactionsChart.schema),
allowGet: true,
cacheSec: 60 * 60,
} as const;
export const paramDef = {

View File

@ -1,11 +1,14 @@
import define from '../../define.js';
import { getJsonSchema } from '@/services/chart/core.js';
import { usersChart } from '@/services/chart/index.js';
import define from '../../define.js';
export const meta = {
tags: ['charts', 'users'],
res: getJsonSchema(usersChart.schema),
allowGet: true,
cacheSec: 60 * 60,
} as const;
export const paramDef = {

View File

@ -0,0 +1,57 @@
import define from '../../define.js';
import { ClipNotes, Clips } from '@/models/index.js';
import { ApiError } from '../../error.js';
import { getNote } from '../../common/getters.js';
export const meta = {
tags: ['account', 'notes', 'clips'],
requireCredential: true,
kind: 'write:account',
errors: {
noSuchClip: {
message: 'No such clip.',
code: 'NO_SUCH_CLIP',
id: 'b80525c6-97f7-49d7-a42d-ebccd49cfd52',
},
noSuchNote: {
message: 'No such note.',
code: 'NO_SUCH_NOTE',
id: 'aff017de-190e-434b-893e-33a9ff5049d8',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
clipId: { type: 'string', format: 'misskey:id' },
noteId: { type: 'string', format: 'misskey:id' },
},
required: ['clipId', 'noteId'],
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
const clip = await Clips.findOneBy({
id: ps.clipId,
userId: user.id,
});
if (clip == null) {
throw new ApiError(meta.errors.noSuchClip);
}
const note = await getNote(ps.noteId).catch(e => {
if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
throw e;
});
await ClipNotes.delete({
noteId: note.id,
clipId: clip.id,
});
});

View File

@ -1,6 +1,6 @@
import define from '../define.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
import { DriveFiles } from '@/models/index.js';
import define from '../define.js';
export const meta = {
tags: ['drive', 'account'],
@ -39,7 +39,7 @@ export default define(meta, paramDef, async (ps, user) => {
const usage = await DriveFiles.calcDriveUsageOf(user.id);
return {
capacity: 1024 * 1024 * instance.localDriveCapacityMb,
capacity: 1024 * 1024 * (user.driveCapacityOverrideMb || instance.localDriveCapacityMb),
usage: usage,
};
});

View File

@ -1,10 +1,12 @@
import ms from 'ms';
import { addFile } from '@/services/drive/add-file.js';
import { DriveFiles } from '@/models/index.js';
import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
import define from '../../../define.js';
import { apiLogger } from '../../../logger.js';
import { ApiError } from '../../../error.js';
import { DriveFiles } from '@/models/index.js';
import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js';
export const meta = {
tags: ['drive'],
@ -34,6 +36,18 @@ export const meta = {
code: 'INVALID_FILE_NAME',
id: 'f449b209-0c60-4e51-84d5-29486263bfd4',
},
inappropriate: {
message: 'Cannot upload the file because it has been determined that it possibly contains inappropriate content.',
code: 'INAPPROPRIATE',
id: 'bec5bd69-fba3-43c9-b4fb-2894b66ad5d2',
},
noFreeSpace: {
message: 'Cannot upload the file because you have no free space of drive.',
code: 'NO_FREE_SPACE',
id: 'd08dbc37-a6a9-463a-8c47-96c32ab5f064',
},
},
} as const;
@ -50,7 +64,7 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user, _, file, cleanup) => {
export default define(meta, paramDef, async (ps, user, _, file, cleanup, ip, headers) => {
// Get 'name' parameter
let name = ps.name || file.originalname;
if (name !== undefined && name !== null) {
@ -66,14 +80,30 @@ export default define(meta, paramDef, async (ps, user, _, file, cleanup) => {
name = null;
}
const meta = await fetchMeta();
try {
// Create file
const driveFile = await addFile({ user, path: file.path, name, comment: ps.comment, folderId: ps.folderId, force: ps.force, sensitive: ps.isSensitive });
const driveFile = await addFile({
user,
path: file.path,
name,
comment: ps.comment,
folderId: ps.folderId,
force: ps.force,
sensitive: ps.isSensitive,
requestIp: meta.enableIpLogging ? ip : null,
requestHeaders: meta.enableIpLogging ? headers : null,
});
return await DriveFiles.pack(driveFile, { self: true });
} catch (e) {
if (e instanceof Error || typeof e === 'string') {
apiLogger.error(e);
}
if (e instanceof IdentifiableError) {
if (e.id === '282f77bf-5816-4f72-9264-aa14d8261a21') throw new ApiError(meta.errors.inappropriate);
if (e.id === 'c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6') throw new ApiError(meta.errors.noFreeSpace);
}
throw new ApiError();
} finally {
cleanup!();

View File

@ -1,8 +1,8 @@
import { publishDriveStream } from '@/services/stream.js';
import define from '../../../define.js';
import { ApiError } from '../../../error.js';
import { DriveFiles, DriveFolders, Users } from '@/models/index.js';
import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js';
import define from '../../../define.js';
import { ApiError } from '../../../error.js';
export const meta = {
tags: ['drive'],

View File

@ -1,9 +1,9 @@
import ms from 'ms';
import { uploadFromUrl } from '@/services/drive/upload-from-url.js';
import define from '../../../define.js';
import { DriveFiles } from '@/models/index.js';
import { publishMainStream } from '@/services/stream.js';
import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js';
import define from '../../../define.js';
export const meta = {
tags: ['drive'],
@ -34,8 +34,8 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
uploadFromUrl({ url: ps.url, user, folderId: ps.folderId, sensitive: ps.isSensitive, force: ps.force, comment: ps.comment }).then(file => {
export default define(meta, paramDef, async (ps, user, _1, _2, _3, ip, headers) => {
uploadFromUrl({ url: ps.url, user, folderId: ps.folderId, sensitive: ps.isSensitive, force: ps.force, comment: ps.comment, requestIp: ip, requestHeaders: headers }).then(file => {
DriveFiles.pack(file, { self: true }).then(packedFile => {
publishMainStream(user.id, 'urlUploadFinished', {
marker: ps.marker,

View File

@ -1,6 +1,6 @@
import define from '../define.js';
import { createExportCustomEmojisJob } from '@/queue/index.js';
import ms from 'ms';
import { createExportCustomEmojisJob } from '@/queue/index.js';
import define from '../define.js';
export const meta = {
secure: true,

View File

@ -0,0 +1,65 @@
import { IsNull, MoreThan, Not } from 'typeorm';
import { Followings, Instances } from '@/models/index.js';
import { awaitAll } from '@/prelude/await-all.js';
import define from '../../define.js';
export const meta = {
tags: ['federation'],
requireCredential: false,
allowGet: true,
cacheSec: 60 * 60,
} as const;
export const paramDef = {
type: 'object',
properties: {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
},
required: [],
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps) => {
const [topSubInstances, topPubInstances, allSubCount, allPubCount] = await Promise.all([
Instances.find({
where: {
followersCount: MoreThan(0),
},
order: {
followersCount: 'DESC',
},
take: ps.limit,
}),
Instances.find({
where: {
followingCount: MoreThan(0),
},
order: {
followingCount: 'DESC',
},
take: ps.limit,
}),
Followings.count({
where: {
followeeHost: Not(IsNull()),
},
}),
Followings.count({
where: {
followerHost: Not(IsNull()),
},
}),
]);
const gotSubCount = topSubInstances.map(x => x.followersCount).reduce((a, b) => a + b, 0);
const gotPubCount = topPubInstances.map(x => x.followingCount).reduce((a, b) => a + b, 0);
return await awaitAll({
topSubInstances: Instances.packMany(topSubInstances),
otherFollowersCount: Math.max(0, allSubCount - gotSubCount),
topPubInstances: Instances.packMany(topPubInstances),
otherFollowingCount: Math.max(0, allPubCount - gotPubCount),
});
});

View File

@ -0,0 +1,39 @@
import Parser from 'rss-parser';
import { getResponse } from '@/misc/fetch.js';
import config from '@/config/index.js';
import define from '../define.js';
const rssParser = new Parser();
export const meta = {
tags: ['meta'],
requireCredential: false,
allowGet: true,
cacheSec: 60 * 3,
} as const;
export const paramDef = {
type: 'object',
properties: {
url: { type: 'string' },
},
required: ['url'],
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps) => {
const res = await getResponse({
url: ps.url,
method: 'GET',
headers: Object.assign({
'User-Agent': config.userAgent,
Accept: 'application/rss+xml, */*',
}),
timeout: 5000,
});
const text = await res.text();
return rssParser.parseString(text);
});

View File

@ -1,6 +1,6 @@
import { MoreThan } from 'typeorm';
import { USER_ONLINE_THRESHOLD } from '@/const.js';
import { Users } from '@/models/index.js';
import { MoreThan } from 'typeorm';
import define from '../define.js';
export const meta = {

View File

@ -1,5 +1,5 @@
import define from '../define.js';
import { Users } from '@/models/index.js';
import define from '../define.js';
export const meta = {
tags: ['account'],

View File

@ -1,9 +1,7 @@
import bcrypt from 'bcryptjs';
import define from '../../define.js';
import { UserProfiles, Users } from '@/models/index.js';
import { doPostSuspend } from '@/services/suspend-user.js';
import { publishUserEvent } from '@/services/stream.js';
import { createDeleteAccountJob } from '@/queue/index.js';
import { deleteAccount } from '@/services/delete-account.js';
import define from '../../define.js';
export const meta = {
requireCredential: true,
@ -34,17 +32,5 @@ export default define(meta, paramDef, async (ps, user) => {
throw new Error('incorrect password');
}
// 物理削除する前にDelete activityを送信する
await doPostSuspend(user).catch(e => {});
createDeleteAccountJob(user, {
soft: false,
});
await Users.update(user.id, {
isDeleted: true,
});
// Terminate streaming
publishUserEvent(user.id, 'terminate', {});
await deleteAccount(user);
});

View File

@ -1,17 +1,21 @@
import { Brackets } from 'typeorm';
import { Notifications, Followings, Mutings, Users, UserProfiles } from '@/models/index.js';
import { notificationTypes } from '@/types.js';
import read from '@/services/note/read.js';
import { readNotification } from '../../common/read-notification.js';
import define from '../../define.js';
import { makePaginationQuery } from '../../common/make-pagination-query.js';
import { generateMutedInstanceNotificationQuery } from '../../common/generate-muted-instance-query.js';
import { Notifications, Followings, Mutings, Users } from '@/models/index.js';
import { notificationTypes } from '@/types.js';
import read from '@/services/note/read.js';
import { Brackets } from 'typeorm';
export const meta = {
tags: ['account', 'notifications'],
requireCredential: true,
limit: {
duration: 60000,
max: 10,
},
kind: 'read:notifications',
res: {
@ -62,12 +66,16 @@ export default define(meta, paramDef, async (ps, user) => {
.select('muting.muteeId')
.where('muting.muterId = :muterId', { muterId: user.id });
const mutingInstanceQuery = UserProfiles.createQueryBuilder('user_profile')
.select('user_profile.mutedInstances')
.where('user_profile.userId = :muterId', { muterId: user.id });
const suspendedQuery = Users.createQueryBuilder('users')
.select('users.id')
.where('users.isSuspended = TRUE');
const query = makePaginationQuery(Notifications.createQueryBuilder('notification'), ps.sinceId, ps.untilId)
.andWhere(`notification.notifieeId = :meId`, { meId: user.id })
.andWhere('notification.notifieeId = :meId', { meId: user.id })
.leftJoinAndSelect('notification.notifier', 'notifier')
.leftJoinAndSelect('notification.note', 'note')
.leftJoinAndSelect('notifier.avatar', 'notifierAvatar')
@ -84,14 +92,21 @@ export default define(meta, paramDef, async (ps, user) => {
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
// muted users
query.andWhere(new Brackets(qb => { qb
.where(`notification.notifierId NOT IN (${ mutingQuery.getQuery() })`)
.orWhere('notification.notifierId IS NULL');
}));
query.setParameters(mutingQuery.getParameters());
generateMutedInstanceNotificationQuery(query, user);
// muted instances
query.andWhere(new Brackets(qb => { qb
.andWhere('notifier.host IS NULL')
.orWhere(`NOT (( ${mutingInstanceQuery.getQuery()} )::jsonb ? notifier.host)`);
}));
query.setParameters(mutingInstanceQuery.getParameters());
// suspended users
query.andWhere(new Brackets(qb => { qb
.where(`notification.notifierId NOT IN (${ suspendedQuery.getQuery() })`)
.orWhere('notification.notifierId IS NULL');
@ -103,13 +118,13 @@ export default define(meta, paramDef, async (ps, user) => {
}
if (ps.includeTypes && ps.includeTypes.length > 0) {
query.andWhere(`notification.type IN (:...includeTypes)`, { includeTypes: ps.includeTypes });
query.andWhere('notification.type IN (:...includeTypes)', { includeTypes: ps.includeTypes });
} else if (ps.excludeTypes && ps.excludeTypes.length > 0) {
query.andWhere(`notification.type NOT IN (:...excludeTypes)`, { excludeTypes: ps.excludeTypes });
query.andWhere('notification.type NOT IN (:...excludeTypes)', { excludeTypes: ps.excludeTypes });
}
if (ps.unreadOnly) {
query.andWhere(`notification.isRead = false`);
query.andWhere('notification.isRead = false');
}
const notifications = await query.take(ps.limit).getMany();

View File

@ -3,17 +3,17 @@ import * as mfm from 'mfm-js';
import { publishMainStream, publishUserEvent } from '@/services/stream.js';
import acceptAllFollowRequests from '@/services/following/requests/accept-all.js';
import { publishToFollowers } from '@/services/i/update.js';
import define from '../../define.js';
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
import { extractHashtags } from '@/misc/extract-hashtags.js';
import { updateUsertags } from '@/services/update-hashtag.js';
import { ApiError } from '../../error.js';
import { Users, DriveFiles, UserProfiles, Pages } from '@/models/index.js';
import { User } from '@/models/entities/user.js';
import { UserProfile } from '@/models/entities/user-profile.js';
import { notificationTypes } from '@/types.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { langmap } from '@/misc/langmap.js';
import { ApiError } from '../../error.js';
import define from '../../define.js';
export const meta = {
tags: ['account'],
@ -57,7 +57,7 @@ export const meta = {
message: 'Invalid Regular Expression.',
code: 'INVALID_REGEXP',
id: '0d786918-10df-41cd-8f33-8dec7d9a89a5',
}
},
},
res: {
@ -77,7 +77,8 @@ export const paramDef = {
lang: { type: 'string', enum: [null, ...Object.keys(langmap)], nullable: true },
avatarId: { type: 'string', format: 'misskey:id', nullable: true },
bannerId: { type: 'string', format: 'misskey:id', nullable: true },
fields: { type: 'array',
fields: {
type: 'array',
minItems: 0,
maxItems: 16,
items: {
@ -102,6 +103,7 @@ export const paramDef = {
injectFeaturedNote: { type: 'boolean' },
receiveAnnouncementEmail: { type: 'boolean' },
alwaysMarkNsfw: { type: 'boolean' },
autoSensitive: { type: 'boolean' },
ffVisibility: { type: 'string', enum: ['public', 'followers', 'private'] },
pinnedPageId: { type: 'array', items: {
type: 'string', format: 'misskey:id',
@ -168,6 +170,7 @@ export default define(meta, paramDef, async (ps, _user, token) => {
if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;
if (typeof ps.alwaysMarkNsfw === 'boolean') profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw;
if (typeof ps.autoSensitive === 'boolean') profileUpdates.autoSensitive = ps.autoSensitive;
if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes;
if (ps.avatarId) {

View File

@ -1,10 +1,10 @@
import { IsNull, MoreThan } from 'typeorm';
import config from '@/config/index.js';
import define from '../define.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
import { Ads, Emojis, Users } from '@/models/index.js';
import { DB_MAX_NOTE_TEXT_LENGTH } from '@/misc/hard-limits.js';
import { IsNull, MoreThan } from 'typeorm';
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
import define from '../define.js';
export const meta = {
tags: ['meta'],

View File

@ -1,6 +1,6 @@
import { Notes } from '@/models/index.js';
import define from '../define.js';
import { makePaginationQuery } from '../common/make-pagination-query.js';
import { Notes } from '@/models/index.js';
export const meta = {
tags: ['notes'],
@ -34,8 +34,8 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps) => {
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.andWhere(`note.visibility = 'public'`)
.andWhere(`note.localOnly = FALSE`)
.andWhere('note.visibility = \'public\'')
.andWhere('note.localOnly = FALSE')
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')
@ -61,7 +61,7 @@ export default define(meta, paramDef, async (ps) => {
}
if (ps.withFiles !== undefined) {
query.andWhere(ps.withFiles ? `note.fileIds != '{}'` : `note.fileIds = '{}'`);
query.andWhere(ps.withFiles ? 'note.fileIds != \'{}\'' : 'note.fileIds = \'{}\'');
}
if (ps.poll !== undefined) {

View File

@ -1,11 +1,10 @@
import { Brackets } from 'typeorm';
import { Notes } from '@/models/index.js';
import define from '../../define.js';
import { makePaginationQuery } from '../../common/make-pagination-query.js';
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
import { Brackets } from 'typeorm';
import { Notes } from '@/models/index.js';
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
import { generateMutedInstanceQuery } from '../../common/generate-muted-instance-query.js';
export const meta = {
tags: ['notes'],
@ -38,13 +37,13 @@ export const paramDef = {
export default define(meta, paramDef, async (ps, user) => {
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.andWhere(new Brackets(qb => { qb
.where(`note.replyId = :noteId`, { noteId: ps.noteId })
.where('note.replyId = :noteId', { noteId: ps.noteId })
.orWhere(new Brackets(qb => { qb
.where(`note.renoteId = :noteId`, { noteId: ps.noteId })
.where('note.renoteId = :noteId', { noteId: ps.noteId })
.andWhere(new Brackets(qb => { qb
.where(`note.text IS NOT NULL`)
.orWhere(`note.fileIds != '{}'`)
.orWhere(`note.hasPoll = TRUE`);
.where('note.text IS NOT NULL')
.orWhere('note.fileIds != \'{}\'')
.orWhere('note.hasPoll = TRUE');
}));
}));
}))
@ -61,9 +60,10 @@ export default define(meta, paramDef, async (ps, user) => {
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
generateVisibilityQuery(query, user);
if (user) generateMutedUserQuery(query, user);
if (user) generateBlockedUserQuery(query, user);
if (user) generateMutedInstanceQuery(query, user);
if (user) {
generateMutedUserQuery(query, user);
generateBlockedUserQuery(query, user);
}
const notes = await query.take(ps.limit).getMany();

View File

@ -1,8 +1,8 @@
import define from '../../define.js';
import { In } from 'typeorm';
import { ClipNotes, Clips } from '@/models/index.js';
import define from '../../define.js';
import { getNote } from '../../common/getters.js';
import { ApiError } from '../../error.js';
import { In } from 'typeorm';
export const meta = {
tags: ['clips', 'notes'],

View File

@ -1,8 +1,8 @@
import { Note } from '@/models/entities/note.js';
import { Notes } from '@/models/index.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
import { getNote } from '../../common/getters.js';
import { Note } from '@/models/entities/note.js';
import { Notes } from '@/models/index.js';
export const meta = {
tags: ['notes'],

View File

@ -1,9 +1,9 @@
import deleteNote from '@/services/note/delete.js';
import define from '../../define.js';
import ms from 'ms';
import deleteNote from '@/services/note/delete.js';
import { Users } from '@/models/index.js';
import define from '../../define.js';
import { getNote } from '../../common/getters.js';
import { ApiError } from '../../error.js';
import { Users } from '@/models/index.js';
export const meta = {
tags: ['notes'],

View File

@ -1,8 +1,8 @@
import { NoteFavorites } from '@/models/index.js';
import { genId } from '@/misc/gen-id.js';
import define from '../../../define.js';
import { ApiError } from '../../../error.js';
import { getNote } from '../../../common/getters.js';
import { NoteFavorites } from '@/models/index.js';
import { genId } from '@/misc/gen-id.js';
export const meta = {
tags: ['notes', 'favorites'],

View File

@ -1,7 +1,7 @@
import { NoteFavorites } from '@/models/index.js';
import define from '../../../define.js';
import { ApiError } from '../../../error.js';
import { getNote } from '../../../common/getters.js';
import { NoteFavorites } from '@/models/index.js';
export const meta = {
tags: ['notes', 'favorites'],

View File

@ -1,6 +1,6 @@
import { Notes } from '@/models/index.js';
import define from '../../define.js';
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
import { Notes } from '@/models/index.js';
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
export const meta = {
@ -36,9 +36,9 @@ export default define(meta, paramDef, async (ps, user) => {
const query = Notes.createQueryBuilder('note')
.addSelect('note.score')
.where('note.userHost IS NULL')
.andWhere(`note.score > 0`)
.andWhere(`note.createdAt > :date`, { date: new Date(Date.now() - day) })
.andWhere(`note.visibility = 'public'`)
.andWhere('note.score > 0')
.andWhere('note.createdAt > :date', { date: new Date(Date.now() - day) })
.andWhere('note.visibility = \'public\'')
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')

View File

@ -1,11 +1,10 @@
import define from '../../define.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
import { Notes } from '@/models/index.js';
import { activeUsersChart } from '@/services/chart/index.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
import { makePaginationQuery } from '../../common/make-pagination-query.js';
import { Notes, Users } from '@/models/index.js';
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
import { generateMutedInstanceQuery } from '../../common/generate-muted-instance-query.js';
import { activeUsersChart } from '@/services/chart/index.js';
import { generateRepliesQuery } from '../../common/generate-replies-query.js';
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query.js';
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
@ -60,7 +59,7 @@ export default define(meta, paramDef, async (ps, user) => {
//#region Construct query
const query = makePaginationQuery(Notes.createQueryBuilder('note'),
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('note.visibility = \'public\'')
.andWhere('note.channelId IS NULL')
.innerJoinAndSelect('note.user', 'user')
@ -76,10 +75,11 @@ export default define(meta, paramDef, async (ps, user) => {
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
generateRepliesQuery(query, user);
if (user) generateMutedUserQuery(query, user);
if (user) generateMutedNoteQuery(query, user);
if (user) generateBlockedUserQuery(query, user);
if (user) generateMutedInstanceQuery(query, user);
if (user) {
generateMutedUserQuery(query, user);
generateMutedNoteQuery(query, user);
generateBlockedUserQuery(query, user);
}
if (ps.withFiles) {
query.andWhere('note.fileIds != \'{}\'');

View File

@ -1,13 +1,12 @@
import define from '../../define.js';
import { Brackets } from 'typeorm';
import { fetchMeta } from '@/misc/fetch-meta.js';
import { Followings, Notes } from '@/models/index.js';
import { activeUsersChart } from '@/services/chart/index.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
import { makePaginationQuery } from '../../common/make-pagination-query.js';
import { Followings, Notes, Users } from '@/models/index.js';
import { Brackets } from 'typeorm';
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
import { generateMutedInstanceQuery } from '../../common/generate-muted-instance-query.js';
import { activeUsersChart } from '@/services/chart/index.js';
import { generateRepliesQuery } from '../../common/generate-replies-query.js';
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query.js';
import { generateChannelQuery } from '../../common/generate-channel-query.js';
@ -70,7 +69,7 @@ export default define(meta, paramDef, async (ps, user) => {
.where('following.followerId = :followerId', { followerId: user.id });
const query = makePaginationQuery(Notes.createQueryBuilder('note'),
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere(new Brackets(qb => {
qb.where(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: user.id })
.orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)');
@ -92,7 +91,6 @@ export default define(meta, paramDef, async (ps, user) => {
generateRepliesQuery(query, user);
generateVisibilityQuery(query, user);
generateMutedUserQuery(query, user);
generateMutedInstanceQuery(query, user);
generateMutedNoteQuery(query, user);
generateBlockedUserQuery(query, user);
@ -134,9 +132,7 @@ export default define(meta, paramDef, async (ps, user) => {
const timeline = await query.take(ps.limit).getMany();
process.nextTick(() => {
if (user) {
activeUsersChart.read(user);
}
activeUsersChart.read(user);
});
return await Notes.packMany(timeline, user);

View File

@ -1,12 +1,12 @@
import define from '../../define.js';
import { Brackets } from 'typeorm';
import { fetchMeta } from '@/misc/fetch-meta.js';
import { ApiError } from '../../error.js';
import { Notes, Users } from '@/models/index.js';
import { activeUsersChart } from '@/services/chart/index.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
import { makePaginationQuery } from '../../common/make-pagination-query.js';
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
import { activeUsersChart } from '@/services/chart/index.js';
import { Brackets } from 'typeorm';
import { generateRepliesQuery } from '../../common/generate-replies-query.js';
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query.js';
import { generateChannelQuery } from '../../common/generate-channel-query.js';
@ -66,7 +66,7 @@ export default define(meta, paramDef, async (ps, user) => {
//#region Construct query
const query = makePaginationQuery(Notes.createQueryBuilder('note'),
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)')
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')

View File

@ -1,10 +1,10 @@
import define from '../../define.js';
import { Brackets } from 'typeorm';
import read from '@/services/note/read.js';
import { Notes, Followings } from '@/models/index.js';
import define from '../../define.js';
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
import { makePaginationQuery } from '../../common/make-pagination-query.js';
import { Brackets } from 'typeorm';
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
import { generateMutedNoteThreadQuery } from '../../common/generate-muted-note-thread-query.js';

View File

@ -1,6 +1,6 @@
import define from '../../../define.js';
import { Polls, Mutings, Notes, PollVotes } from '@/models/index.js';
import { Brackets, In } from 'typeorm';
import { Polls, Mutings, Notes, PollVotes } from '@/models/index.js';
import define from '../../../define.js';
export const meta = {
tags: ['notes'],
@ -31,8 +31,8 @@ export const paramDef = {
export default define(meta, paramDef, async (ps, user) => {
const query = Polls.createQueryBuilder('poll')
.where('poll.userHost IS NULL')
.andWhere(`poll.userId != :meId`, { meId: user.id })
.andWhere(`poll.noteVisibility = 'public'`)
.andWhere('poll.userId != :meId', { meId: user.id })
.andWhere('poll.noteVisibility = \'public\'')
.andWhere(new Brackets(qb => { qb
.where('poll.expiresAt IS NULL')
.orWhere('poll.expiresAt > :now', { now: new Date() });
@ -60,12 +60,21 @@ export default define(meta, paramDef, async (ps, user) => {
query.setParameters(mutingQuery.getParameters());
//#endregion
const polls = await query.take(ps.limit).skip(ps.offset).getMany();
const polls = await query
.orderBy('poll.noteId', 'DESC')
.take(ps.limit)
.skip(ps.offset)
.getMany();
if (polls.length === 0) return [];
const notes = await Notes.findBy({
id: In(polls.map(poll => poll.noteId)),
const notes = await Notes.find({
where: {
id: In(polls.map(poll => poll.noteId)),
},
order: {
createdAt: 'DESC',
},
});
return await Notes.packMany(notes, user, {

View File

@ -1,16 +1,16 @@
import { Not } from 'typeorm';
import { publishNoteStream } from '@/services/stream.js';
import { createNotification } from '@/services/create-notification.js';
import define from '../../../define.js';
import { ApiError } from '../../../error.js';
import { getNote } from '../../../common/getters.js';
import { deliver } from '@/queue/index.js';
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
import renderVote from '@/remote/activitypub/renderer/vote.js';
import { deliverQuestionUpdate } from '@/services/note/polls/update.js';
import { PollVotes, NoteWatchings, Users, Polls, Blockings } from '@/models/index.js';
import { Not } from 'typeorm';
import { IRemoteUser } from '@/models/entities/user.js';
import { genId } from '@/misc/gen-id.js';
import { getNote } from '../../../common/getters.js';
import { ApiError } from '../../../error.js';
import define from '../../../define.js';
export const meta = {
tags: ['notes'],

View File

@ -9,6 +9,9 @@ export const meta = {
requireCredential: false,
allowGet: true,
cacheSec: 60,
res: {
type: 'array',
optional: false, nullable: false,

View File

@ -1,6 +1,6 @@
import define from '../../../define.js';
import ms from 'ms';
import deleteReaction from '@/services/note/reaction/delete.js';
import define from '../../../define.js';
import { getNote } from '../../../common/getters.js';
import { ApiError } from '../../../error.js';

View File

@ -1,10 +1,10 @@
import { Notes } from '@/models/index.js';
import define from '../../define.js';
import { getNote } from '../../common/getters.js';
import { ApiError } from '../../error.js';
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
import { makePaginationQuery } from '../../common/make-pagination-query.js';
import { Notes } from '@/models/index.js';
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
export const meta = {
@ -50,7 +50,7 @@ export default define(meta, paramDef, async (ps, user) => {
});
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.andWhere(`note.renoteId = :renoteId`, { renoteId: note.id })
.andWhere('note.renoteId = :renoteId', { renoteId: note.id })
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')

View File

@ -1,5 +1,5 @@
import define from '../../define.js';
import { Notes } from '@/models/index.js';
import define from '../../define.js';
import { makePaginationQuery } from '../../common/make-pagination-query.js';
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';

View File

@ -1,11 +1,11 @@
import define from '../../define.js';
import { makePaginationQuery } from '../../common/make-pagination-query.js';
import { Notes } from '@/models/index.js';
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
import { Brackets } from 'typeorm';
import { Notes } from '@/models/index.js';
import { safeForSql } from '@/misc/safe-for-sql.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import define from '../../define.js';
import { makePaginationQuery } from '../../common/make-pagination-query.js';
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
export const meta = {

View File

@ -1,8 +1,8 @@
import { In } from 'typeorm';
import { Notes } from '@/models/index.js';
import config from '@/config/index.js';
import es from '../../../../db/elasticsearch.js';
import define from '../../define.js';
import { Notes } from '@/models/index.js';
import { In } from 'typeorm';
import config from '@/config/index.js';
import { makePaginationQuery } from '../../common/make-pagination-query.js';
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
@ -99,7 +99,7 @@ export default define(meta, paramDef, async (ps, me) => {
userHost: ps.host,
},
}] : []
: [];
: [];
const result = await es.search({
index: config.elasticsearch.index || 'misskey_note',

View File

@ -1,7 +1,7 @@
import { Notes } from '@/models/index.js';
import define from '../../define.js';
import { getNote } from '../../common/getters.js';
import { ApiError } from '../../error.js';
import { Notes } from '@/models/index.js';
export const meta = {
tags: ['notes'],

View File

@ -1,5 +1,5 @@
import define from '../../define.js';
import { NoteFavorites, Notes, NoteThreadMutings, NoteWatchings } from '@/models/index.js';
import define from '../../define.js';
export const meta = {
tags: ['notes'],

View File

@ -1,9 +1,9 @@
import define from '../../../define.js';
import { getNote } from '../../../common/getters.js';
import { ApiError } from '../../../error.js';
import { Notes, NoteThreadMutings } from '@/models/index.js';
import { genId } from '@/misc/gen-id.js';
import readNote from '@/services/note/read.js';
import define from '../../../define.js';
import { getNote } from '../../../common/getters.js';
import { ApiError } from '../../../error.js';
export const meta = {
tags: ['notes'],

View File

@ -1,7 +1,7 @@
import { NoteThreadMutings } from '@/models/index.js';
import define from '../../../define.js';
import { getNote } from '../../../common/getters.js';
import { ApiError } from '../../../error.js';
import { NoteThreadMutings } from '@/models/index.js';
export const meta = {
tags: ['notes'],

View File

@ -1,11 +1,10 @@
import { Brackets } from 'typeorm';
import { Notes, Followings } from '@/models/index.js';
import { activeUsersChart } from '@/services/chart/index.js';
import define from '../../define.js';
import { makePaginationQuery } from '../../common/make-pagination-query.js';
import { Notes, Followings } from '@/models/index.js';
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
import { generateMutedInstanceQuery } from '../../common/generate-muted-instance-query.js';
import { activeUsersChart } from '@/services/chart/index.js';
import { Brackets } from 'typeorm';
import { generateRepliesQuery } from '../../common/generate-replies-query.js';
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query.js';
import { generateChannelQuery } from '../../common/generate-channel-query.js';
@ -62,10 +61,10 @@ export default define(meta, paramDef, async (ps, user) => {
.where('following.followerId = :followerId', { followerId: user.id });
const query = makePaginationQuery(Notes.createQueryBuilder('note'),
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere(new Brackets(qb => { qb
.where('note.userId = :meId', { meId: user.id });
if (hasFollowing) qb.orWhere(`note.userId IN (${ followingQuery.getQuery() })`);
if (hasFollowing) qb.orWhere(`note.userId IN (${ followingQuery.getQuery() })`);
}))
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
@ -84,7 +83,6 @@ export default define(meta, paramDef, async (ps, user) => {
generateRepliesQuery(query, user);
generateVisibilityQuery(query, user);
generateMutedUserQuery(query, user);
generateMutedInstanceQuery(query, user);
generateMutedNoteQuery(query, user);
generateBlockedUserQuery(query, user);
@ -126,9 +124,7 @@ export default define(meta, paramDef, async (ps, user) => {
const timeline = await query.take(ps.limit).getMany();
process.nextTick(() => {
if (user) {
activeUsersChart.read(user);
}
activeUsersChart.read(user);
});
return await Notes.packMany(timeline, user);

View File

@ -1,9 +1,9 @@
import deleteNote from '@/services/note/delete.js';
import define from '../../define.js';
import ms from 'ms';
import deleteNote from '@/services/note/delete.js';
import { Notes, Users } from '@/models/index.js';
import define from '../../define.js';
import { getNote } from '../../common/getters.js';
import { ApiError } from '../../error.js';
import { Notes, Users } from '@/models/index.js';
export const meta = {
tags: ['notes'],

View File

@ -1,10 +1,10 @@
import { Brackets } from 'typeorm';
import { UserLists, UserListJoinings, Notes } from '@/models/index.js';
import { activeUsersChart } from '@/services/chart/index.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
import { UserLists, UserListJoinings, Notes } from '@/models/index.js';
import { makePaginationQuery } from '../../common/make-pagination-query.js';
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
import { activeUsersChart } from '@/services/chart/index.js';
import { Brackets } from 'typeorm';
export const meta = {
tags: ['notes', 'lists'],

View File

@ -1,5 +1,5 @@
import define from '../../../define.js';
import watch from '@/services/note/watch.js';
import define from '../../../define.js';
import { getNote } from '../../../common/getters.js';
import { ApiError } from '../../../error.js';

View File

@ -1,5 +1,5 @@
import define from '../../../define.js';
import unwatch from '@/services/note/unwatch.js';
import define from '../../../define.js';
import { getNote } from '../../../common/getters.js';
import { ApiError } from '../../../error.js';

View File

@ -1,5 +1,5 @@
import define from '../../define.js';
import { createNotification } from '@/services/create-notification.js';
import define from '../../define.js';
export const meta = {
tags: ['notifications'],

View File

@ -1,7 +1,7 @@
import { publishMainStream } from '@/services/stream.js';
import { pushNotification } from '@/services/push-notification.js';
import define from '../../define.js';
import { Notifications } from '@/models/index.js';
import define from '../../define.js';
export const meta = {
tags: ['notifications', 'account'],

View File

@ -2,17 +2,14 @@ import define from '../../define.js';
import { readNotification } from '../../common/read-notification.js';
export const meta = {
desc: {
'ja-JP': '通知を既読にします。',
'en-US': 'Mark a notification as read.'
},
tags: ['notifications', 'account'],
requireCredential: true,
kind: 'write:notifications',
description: 'Mark a notification as read.',
errors: {
noSuchNotification: {
message: 'No such notification.',

View File

@ -1,6 +1,6 @@
import define from '../define.js';
import { publishMainStream } from '@/services/stream.js';
import { Users, Pages } from '@/models/index.js';
import define from '../define.js';
import { ApiError } from '../error.js';
export const meta = {

View File

@ -1,8 +1,8 @@
import ms from 'ms';
import define from '../../define.js';
import { Pages, DriveFiles } from '@/models/index.js';
import { genId } from '@/misc/gen-id.js';
import { Page } from '@/models/entities/page.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
export const meta = {
@ -51,7 +51,7 @@ export const paramDef = {
} },
script: { type: 'string' },
eyeCatchingImageId: { type: 'string', format: 'misskey:id', nullable: true },
font: { type: 'string', enum: ['serif', 'sans-serif'], default: "sans-serif" },
font: { type: 'string', enum: ['serif', 'sans-serif'], default: 'sans-serif' },
alignCenter: { type: 'boolean', default: false },
hideTitleWhenPinned: { type: 'boolean', default: false },
},

View File

@ -1,6 +1,6 @@
import { Pages } from '@/models/index.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
import { Pages } from '@/models/index.js';
export const meta = {
tags: ['pages'],

View File

@ -1,5 +1,5 @@
import define from '../../define.js';
import { Pages } from '@/models/index.js';
import define from '../../define.js';
export const meta = {
tags: ['pages'],

View File

@ -1,7 +1,7 @@
import define from '../../define.js';
import { ApiError } from '../../error.js';
import { Pages, PageLikes } from '@/models/index.js';
import { genId } from '@/misc/gen-id.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
export const meta = {
tags: ['pages'],

View File

@ -1,6 +1,6 @@
import { Pages, PageLikes } from '@/models/index.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
import { Pages, PageLikes } from '@/models/index.js';
export const meta = {
tags: ['pages'],

View File

@ -1,8 +1,8 @@
import ms from 'ms';
import { Not } from 'typeorm';
import { Pages, DriveFiles } from '@/models/index.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
import { Pages, DriveFiles } from '@/models/index.js';
import { Not } from 'typeorm';
export const meta = {
tags: ['pages'],

View File

@ -1,9 +1,9 @@
import define from '../define.js';
import { IsNull } from 'typeorm';
import { Users } from '@/models/index.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
import * as Acct from '@/misc/acct.js';
import { User } from '@/models/entities/user.js';
import { IsNull } from 'typeorm';
import define from '../define.js';
export const meta = {
tags: ['users'],

View File

@ -1,8 +1,8 @@
import { PromoReads } from '@/models/index.js';
import { genId } from '@/misc/gen-id.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
import { getNote } from '../../common/getters.js';
import { PromoReads } from '@/models/index.js';
import { genId } from '@/misc/gen-id.js';
export const meta = {
tags: ['notes'],

View File

@ -1,13 +1,13 @@
import { publishMainStream } from '@/services/stream.js';
import define from '../define.js';
import rndstr from 'rndstr';
import config from '@/config/index.js';
import ms from 'ms';
import { IsNull } from 'typeorm';
import { publishMainStream } from '@/services/stream.js';
import config from '@/config/index.js';
import { Users, UserProfiles, PasswordResetRequests } from '@/models/index.js';
import { sendEmail } from '@/services/send-email.js';
import { ApiError } from '../error.js';
import { genId } from '@/misc/gen-id.js';
import { IsNull } from 'typeorm';
import { ApiError } from '../error.js';
import define from '../define.js';
export const meta = {
tags: ['reset password'],

View File

@ -1,6 +1,6 @@
import { resetDb } from '@/db/postgre.js';
import define from '../define.js';
import { ApiError } from '../error.js';
import { resetDb } from '@/db/postgre.js';
export const meta = {
tags: ['non-productive'],

View File

@ -1,7 +1,7 @@
import bcrypt from 'bcryptjs';
import { publishMainStream } from '@/services/stream.js';
import define from '../define.js';
import { Users, UserProfiles, PasswordResetRequests } from '@/models/index.js';
import define from '../define.js';
import { ApiError } from '../error.js';
export const meta = {

View File

@ -1,5 +1,5 @@
import define from '../define.js';
import { Instances, NoteReactions, Notes, Users } from '@/models/index.js';
import define from '../define.js';
import { } from '@/services/chart/index.js';
import { IsNull } from 'typeorm';

View File

@ -1,7 +1,7 @@
import define from '../../define.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
import { genId } from '@/misc/gen-id.js';
import { SwSubscriptions } from '@/models/index.js';
import define from '../../define.js';
export const meta = {
tags: ['account'],

View File

@ -1,5 +1,5 @@
import define from '../../define.js';
import { SwSubscriptions } from '@/models/index.js';
import define from '../../define.js';
export const meta = {
tags: ['account'],

View File

@ -1,6 +1,6 @@
import define from '../../define.js';
import { Users, UsedUsernames } from '@/models/index.js';
import { IsNull } from 'typeorm';
import { Users, UsedUsernames } from '@/models/index.js';
import define from '../../define.js';
export const meta = {
tags: ['users'],

View File

@ -1,5 +1,5 @@
import define from '../define.js';
import { Users } from '@/models/index.js';
import define from '../define.js';
import { generateMutedUserQueryForUsers } from '../common/generate-muted-user-query.js';
import { generateBlockQueryForUsers } from '../common/generate-block-query.js';
@ -25,8 +25,14 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
offset: { type: 'integer', default: 0 },
sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt'] },
state: { type: 'string', enum: ['all', 'admin', 'moderator', 'adminOrModerator', 'alive'], default: "all" },
origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" },
state: { type: 'string', enum: ['all', 'admin', 'moderator', 'adminOrModerator', 'alive'], default: 'all' },
origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'local' },
hostname: {
type: 'string',
nullable: true,
default: null,
description: 'The local host is represented with `null`.',
},
},
required: [],
} as const;
@ -48,6 +54,10 @@ export default define(meta, paramDef, async (ps, me) => {
case 'remote': query.andWhere('user.host IS NOT NULL'); break;
}
if (ps.hostname) {
query.andWhere('user.host = :hostname', { hostname: ps.hostname.toLowerCase() });
}
switch (ps.sort) {
case '+follower': query.orderBy('user.followersCount', 'DESC'); break;
case '-follower': query.orderBy('user.followersCount', 'ASC'); break;

View File

@ -1,5 +1,5 @@
import define from '../../define.js';
import { Clips } from '@/models/index.js';
import define from '../../define.js';
import { makePaginationQuery } from '../../common/make-pagination-query.js';
export const meta = {
@ -32,7 +32,7 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
const query = makePaginationQuery(Clips.createQueryBuilder('clip'), ps.sinceId, ps.untilId)
.andWhere(`clip.userId = :userId`, { userId: ps.userId })
.andWhere('clip.userId = :userId', { userId: ps.userId })
.andWhere('clip.isPublic = true');
const clips = await query

View File

@ -1,9 +1,9 @@
import { IsNull } from 'typeorm';
import { Users, Followings, UserProfiles } from '@/models/index.js';
import { toPunyNullable } from '@/misc/convert-host.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
import { Users, Followings, UserProfiles } from '@/models/index.js';
import { makePaginationQuery } from '../../common/make-pagination-query.js';
import { toPunyNullable } from '@/misc/convert-host.js';
import { IsNull } from 'typeorm';
export const meta = {
tags: ['users'],
@ -96,7 +96,7 @@ export default define(meta, paramDef, async (ps, me) => {
}
const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId)
.andWhere(`following.followeeId = :userId`, { userId: user.id })
.andWhere('following.followeeId = :userId', { userId: user.id })
.innerJoinAndSelect('following.follower', 'follower');
const followings = await query

View File

@ -1,9 +1,9 @@
import { IsNull } from 'typeorm';
import { Users, Followings, UserProfiles } from '@/models/index.js';
import { toPunyNullable } from '@/misc/convert-host.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
import { Users, Followings, UserProfiles } from '@/models/index.js';
import { makePaginationQuery } from '../../common/make-pagination-query.js';
import { toPunyNullable } from '@/misc/convert-host.js';
import { IsNull } from 'typeorm';
export const meta = {
tags: ['users'],
@ -96,7 +96,7 @@ export default define(meta, paramDef, async (ps, me) => {
}
const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId)
.andWhere(`following.followerId = :userId`, { userId: user.id })
.andWhere('following.followerId = :userId', { userId: user.id })
.innerJoinAndSelect('following.followee', 'followee');
const followings = await query

View File

@ -1,9 +1,9 @@
import define from '../../define.js';
import { Not, In, IsNull } from 'typeorm';
import { maximum } from '@/prelude/array.js';
import { Notes, Users } from '@/models/index.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
import { getUser } from '../../common/getters.js';
import { Not, In, IsNull } from 'typeorm';
import { Notes, Users } from '@/models/index.js';
export const meta = {
tags: ['users'],

View File

@ -1,8 +1,8 @@
import define from '../../../define.js';
import { UserGroups, UserGroupJoinings } from '@/models/index.js';
import { genId } from '@/misc/gen-id.js';
import { UserGroup } from '@/models/entities/user-group.js';
import { UserGroupJoining } from '@/models/entities/user-group-joining.js';
import define from '../../../define.js';
export const meta = {
tags: ['groups'],

View File

@ -1,6 +1,6 @@
import { UserGroups } from '@/models/index.js';
import define from '../../../define.js';
import { ApiError } from '../../../error.js';
import { UserGroups } from '@/models/index.js';
export const meta = {
tags: ['groups'],

Some files were not shown because too many files have changed in this diff Show More