Merge remote-tracking branch 'mattyateaFork/develop' into develop

# Conflicts:
#	CHANGELOG.md
#	README.md
#	locales/index.d.ts
#	locales/ja-JP.yml
#	package.json
#	packages/backend/src/core/activitypub/models/ApNoteService.ts
#	packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts
#	packages/backend/src/server/api/endpoints/get-avatar-decorations.ts
#	packages/backend/test/unit/entities/UserEntityService.ts
#	packages/frontend/src/components/MkFollowButton.vue
#	packages/frontend/src/components/MkTimeline.vue
#	packages/frontend/src/pages/about.vue
#	packages/frontend/src/pages/emoji-edit-dialog.vue
#	packages/frontend/src/ui/universal.vue
This commit is contained in:
mattyatea 2024-07-15 14:59:54 +09:00
commit 71382a6f85
297 changed files with 60420 additions and 4574 deletions

View file

@ -0,0 +1,11 @@
export class AddEmojiDraftFlag1684236161625 {
name = 'AddEmojiDraftFlag1684236161625'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "emoji" ADD "draft" boolean NOT NULL DEFAULT false`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "draft"`);
}
}

View file

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class NoteEditHistory1696044626209 {
name = 'NoteEditHistory1696044626209'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" ADD "noteEditHistory" varchar(3000) array DEFAULT '{}'`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" DROP "noteEditHistory"`);
}
}

View file

@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey, cherrypick contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class NoteUpdateAtHistory1696318192428 {
name = 'NoteUpdateAtHistory1696318192428'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" ADD "updatedAtHistory" TIMESTAMP WITH TIME ZONE array`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" DROP "updatedAtHistory"`);
}
}

View file

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class revertrevertnoteeditting1696604429200 {
name = 'revertrevertnoteeditting1696604429200'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" ADD "updatedAt" TIMESTAMP WITH TIME ZONE`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "updatedAt"`);
}
}

View file

@ -0,0 +1,12 @@
export class PollVotePoll1696604572677 {
name = 'PollVotePoll1696604572677';
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "poll_vote" ADD CONSTRAINT "FK_poll_vote_poll" FOREIGN KEY ("noteId") REFERENCES "poll"("noteId") ON DELETE CASCADE`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "poll_vote" DROP CONSTRAINT "FK_poll_vote_poll"`);
}
}

View file

@ -0,0 +1,11 @@
export class DiscordWebhookUrl1697641012204 {
name = 'DiscordWebhookUrl1697641012204'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "DiscordWebhookUrl" character varying(1024)`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "DiscordWebhookUrl"`);
}
}

View file

@ -0,0 +1,11 @@
export class EmojiBotToken1697642704514 {
name = 'EmojiBotToken1697642704514'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "EmojiBotToken" character varying(1024)`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "EmojiBotToken"`);
}
}

View file

@ -0,0 +1,15 @@
export class BaseUrl1697645425687 {
name = 'BaseUrl1697645425687'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "ApiBase" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "preservedUsernames" SET DEFAULT '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }'`);
await queryRunner.query(`ALTER TABLE "flash" ALTER COLUMN "visibility" SET NOT NULL`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "flash" ALTER COLUMN "visibility" DROP NOT NULL`);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "preservedUsernames" SET DEFAULT '{admin,administrator,root,system,maintainer,host,mod,moderator,owner,superuser,staff,auth,i,me,everyone,all,mention,mentions,example,user,users,account,accounts,official,help,helps,support,supports,info,information,informations,announce,announces,announcement,announcements,notice,notification,notifications,dev,developer,developers,tech,misskey}'`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "ApiBase"`);
}
}

View file

@ -0,0 +1,13 @@
export class EmojiRequest1698131657286 {
name = 'EmojiRequest1698131657286'
async up(queryRunner) {
await queryRunner.query(`CREATE TABLE "emoji_request" ("id" character varying(32) NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE, "name" character varying(128) NOT NULL, "category" character varying(128), "originalUrl" character varying(512) NOT NULL, "publicUrl" character varying(512) NOT NULL DEFAULT '', "type" character varying(64), "aliases" character varying(128) array NOT NULL DEFAULT '{}', "license" character varying(1024), "fileId" character varying(1024) NOT NULL, "localOnly" boolean NOT NULL DEFAULT false, "isSensitive" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_3c74521e048dc744f0c7eb65f4a" PRIMARY KEY ("id"))`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ea1d771e867e9843300f09d02c" ON "emoji_request" ("name") `);
}
async down(queryRunner) {
await queryRunner.query(`DROP INDEX "public"."IDX_ea1d771e867e9843300f09d02c"`);
await queryRunner.query(`DROP TABLE "emoji_request"`);
}
}

View file

@ -0,0 +1,13 @@
export class Gorillamode1698907074200 {
name = 'Gorillamode1698907074200'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "user" ADD "isGorilla" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`COMMENT ON COLUMN "user"."isGorilla" IS 'Whether the User is a gorilla.'`);
}
async down(queryRunner) {
await queryRunner.query(`COMMENT ON COLUMN "user"."isGorilla" IS 'Whether the User is a gorilla.'`);
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "isGorilla"`);
}
}

View file

@ -0,0 +1,12 @@
export class Schedulenote1699437894737 {
name = 'Schedulenote1699437894737'
async up(queryRunner) {
await queryRunner.query(`CREATE TABLE "note_schedule" ("id" character varying(32) NOT NULL, "note" jsonb NOT NULL, "userId" character varying(260) NOT NULL, "expiresAt" TIMESTAMP WITH TIME ZONE NOT NULL, CONSTRAINT "PK_3a1ae2db41988f4994268218436" PRIMARY KEY ("id"))`);
await queryRunner.query(`CREATE INDEX "IDX_e798958c40009bf0cdef4f28b5" ON "note_schedule" ("userId") `);
}
async down(queryRunner) {
await queryRunner.query(`DROP TABLE "note_schedule"`);
}
}

View file

@ -0,0 +1,11 @@
export class Schedulenote21699949373507 {
name = 'Schedulenote21699949373507'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "note_schedule" RENAME COLUMN "expiresAt" TO "scheduledAt"`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "note_schedule" RENAME COLUMN "scheduledAt" TO "expiresAt"`);
}
}

View file

@ -0,0 +1,11 @@
export class Abusenoteselect1702149469508 {
name = 'Abusenoteselect1702149469508'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "notes" jsonb NOT NULL DEFAULT '[]'`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "notes"`);
}
}

View file

@ -0,0 +1,11 @@
export class Requestemoji1703294653915 {
name = 'Requestemoji1703294653915'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "requestEmojiAllOk" boolean NOT NULL DEFAULT false`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "requestEmojiAllOk"`);
}
}

View file

@ -0,0 +1,11 @@
export class GDPRMode1703704097603 {
name = 'GDPRMode1703704097603'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "enableGDPRMode" boolean NOT NULL DEFAULT false`);
}
async down(queryRunner) {;
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableGDPRMode"`);
}
}

View file

@ -0,0 +1,11 @@
export class AbusenoteId1704005554275 {
name = 'AbusenoteId1704005554275'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "noteIds" jsonb NOT NULL DEFAULT '[]'`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "noteIds"`);
}
}

View file

@ -0,0 +1,11 @@
export class Avatardecoration1704206095136 {
name = 'Avatardecoration1704206095136'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "avatar_decoration" ADD "category" character varying(256) NOT NULL DEFAULT ''`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "avatar_decoration" DROP COLUMN "category"`);
}
}

View file

@ -0,0 +1,13 @@
export class AvatardecorationFed1704343998612 {
name = 'AvatardecorationFed1704343998612'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "avatar_decoration" ADD "host" character varying(256)`);
await queryRunner.query(`ALTER TABLE "avatar_decoration" ALTER COLUMN "category" SET DEFAULT ''`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "avatar_decoration" ALTER COLUMN "category" DROP DEFAULT`);
await queryRunner.query(`ALTER TABLE "avatar_decoration" DROP COLUMN "host"`);
}
}

View file

@ -0,0 +1,13 @@
export class Proxycheckio1707888646527 {
name = 'Proxycheckio1707888646527'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "enableProxyCheckio" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "meta" ADD "proxyCheckioApiKey" character varying(32)`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "proxyCheckioApiKey"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableProxyCheckio"`);
}
}

View file

@ -0,0 +1,11 @@
export class Discordwebohookwordbrock1708081353629 {
name = 'Discordwebohookwordbrock1708081353629'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "DiscordWebhookUrlWordBlock" character varying(1024)`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "DiscordWebhookUrlWordBlock"`);
}
}

View file

@ -0,0 +1,17 @@
export class Outsideprismisskey1714831133155 {
name = 'Outsideprismisskey1714831133155'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "bannerDark" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "bannerLight" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "iconDark" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "iconLight" character varying(1024)`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "iconLight"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "iconDark"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "bannerLight"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "bannerDark"`);
}
}

View file

@ -0,0 +1,11 @@
export class AvatardecorationFed1714831133156 {
name = 'AvatardecorationFed1714831133156'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "avatar_decoration" DROP COLUMN "host"`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "avatar_decoration" ADD "host" character varying(256)`);
}
}

View file

@ -0,0 +1,11 @@
export class Loginbonus11715791271605 {
name = 'Loginbonus11715791271605'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "user" ADD "getPoints" integer NOT NULL DEFAULT '0'`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "user_profile" ADD "getPoints" integer NOT NULL DEFAULT '0'`);
}
}

View file

@ -0,0 +1,11 @@
export class Gapikey1716911535226 {
name = 'Gapikey1716911535226'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "googleAnalyticsId" character varying(1024)`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "googleAnalyticsId"`);
}
}

View file

@ -0,0 +1,16 @@
export class Emojimore1718857094676 {
name = 'Emojimore1718857094676'
async up(queryRunner) {
await queryRunner.query(`DROP INDEX "IDX_ad0c221b25672daf2df320a817"`);
await queryRunner.query(`CREATE INDEX "IDX_ad0c221b25672daf2df320a817" ON "note_reaction" ("userId", "noteId") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a7751b74317122d11575bff31c" ON "note_reaction" ("userId", "noteId", "reaction") `);
}
async down(queryRunner) {
await queryRunner.query(`DROP INDEX "public"."IDX_a7751b74317122d11575bff31c"`);
await queryRunner.query(`DROP INDEX "public"."IDX_ad0c221b25672daf2df320a817"`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ad0c221b25672daf2df320a817" ON "note_reaction" ("userId", "noteId") `);
}
}

View file

@ -154,6 +154,7 @@
"pkce-challenge": "4.1.0",
"probe-image-size": "7.2.3",
"promise-limit": "2.7.0",
"proxycheck-ts": "^0.0.9",
"pug": "3.0.2",
"punycode": "2.3.1",
"qrcode": "1.5.3",
@ -180,6 +181,7 @@
"typescript": "5.5.2",
"ulid": "2.3.0",
"vary": "1.1.2",
"w3c-xmlserializer": "^5.0.0",
"web-push": "3.6.7",
"ws": "8.17.0",
"xev": "3.0.2"
@ -225,6 +227,7 @@
"@types/tinycolor2": "1.4.6",
"@types/tmp": "0.2.6",
"@types/vary": "1.1.3",
"@types/w3c-xmlserializer": "^2.0.4",
"@types/web-push": "3.6.3",
"@types/ws": "8.5.10",
"@typescript-eslint/eslint-plugin": "7.7.1",

View file

@ -130,7 +130,7 @@ export class CacheService implements OnApplicationShutdown {
case 'userChangeSuspendedState':
case 'userChangeDeletedState':
case 'remoteUserUpdated':
case 'localUserUpdated': {
{
const user = await this.usersRepository.findOneBy({ id: body.id });
if (user == null) {
this.userByIdCache.delete(body.id);

View file

@ -40,6 +40,7 @@ import { MetaService } from './MetaService.js';
import { MfmService } from './MfmService.js';
import { ModerationLogService } from './ModerationLogService.js';
import { NoteCreateService } from './NoteCreateService.js';
import { NoteUpdateService } from './NoteUpdateService.js';
import { NoteDeleteService } from './NoteDeleteService.js';
import { NotePiningService } from './NotePiningService.js';
import { NoteReadService } from './NoteReadService.js';
@ -101,6 +102,7 @@ import { ClipEntityService } from './entities/ClipEntityService.js';
import { DriveFileEntityService } from './entities/DriveFileEntityService.js';
import { DriveFolderEntityService } from './entities/DriveFolderEntityService.js';
import { EmojiEntityService } from './entities/EmojiEntityService.js';
import { EmojiRequestsEntityService } from './entities/EmojiRequestsEntityService.js';
import { FollowingEntityService } from './entities/FollowingEntityService.js';
import { FollowRequestEntityService } from './entities/FollowRequestEntityService.js';
import { GalleryLikeEntityService } from './entities/GalleryLikeEntityService.js';
@ -181,6 +183,7 @@ const $MetaService: Provider = { provide: 'MetaService', useExisting: MetaServic
const $MfmService: Provider = { provide: 'MfmService', useExisting: MfmService };
const $ModerationLogService: Provider = { provide: 'ModerationLogService', useExisting: ModerationLogService };
const $NoteCreateService: Provider = { provide: 'NoteCreateService', useExisting: NoteCreateService };
const $NoteUpdateService: Provider = { provide: 'NoteUpdateService', useExisting: NoteUpdateService };
const $NoteDeleteService: Provider = { provide: 'NoteDeleteService', useExisting: NoteDeleteService };
const $NotePiningService: Provider = { provide: 'NotePiningService', useExisting: NotePiningService };
const $NoteReadService: Provider = { provide: 'NoteReadService', useExisting: NoteReadService };
@ -245,6 +248,7 @@ const $ClipEntityService: Provider = { provide: 'ClipEntityService', useExisting
const $DriveFileEntityService: Provider = { provide: 'DriveFileEntityService', useExisting: DriveFileEntityService };
const $DriveFolderEntityService: Provider = { provide: 'DriveFolderEntityService', useExisting: DriveFolderEntityService };
const $EmojiEntityService: Provider = { provide: 'EmojiEntityService', useExisting: EmojiEntityService };
const $EmojiRequestsEntityService: Provider = { provide: 'EmojiRequestsEntityService', useExisting: EmojiRequestsEntityService };
const $FollowingEntityService: Provider = { provide: 'FollowingEntityService', useExisting: FollowingEntityService };
const $FollowRequestEntityService: Provider = { provide: 'FollowRequestEntityService', useExisting: FollowRequestEntityService };
const $GalleryLikeEntityService: Provider = { provide: 'GalleryLikeEntityService', useExisting: GalleryLikeEntityService };
@ -327,6 +331,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
MfmService,
ModerationLogService,
NoteCreateService,
NoteUpdateService,
NoteDeleteService,
NotePiningService,
NoteReadService,
@ -391,6 +396,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
DriveFileEntityService,
DriveFolderEntityService,
EmojiEntityService,
EmojiRequestsEntityService,
FollowingEntityService,
FollowRequestEntityService,
GalleryLikeEntityService,
@ -469,6 +475,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$MfmService,
$ModerationLogService,
$NoteCreateService,
$NoteUpdateService,
$NoteDeleteService,
$NotePiningService,
$NoteReadService,
@ -533,6 +540,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$DriveFileEntityService,
$DriveFolderEntityService,
$EmojiEntityService,
$EmojiRequestsEntityService,
$FollowingEntityService,
$FollowRequestEntityService,
$GalleryLikeEntityService,
@ -612,6 +620,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
MfmService,
ModerationLogService,
NoteCreateService,
NoteUpdateService,
NoteDeleteService,
NotePiningService,
NoteReadService,
@ -675,6 +684,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
DriveFileEntityService,
DriveFolderEntityService,
EmojiEntityService,
EmojiRequestsEntityService,
FollowingEntityService,
FollowRequestEntityService,
GalleryLikeEntityService,
@ -753,6 +763,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$MfmService,
$ModerationLogService,
$NoteCreateService,
$NoteUpdateService,
$NoteDeleteService,
$NotePiningService,
$NoteReadService,
@ -816,6 +827,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$DriveFileEntityService,
$DriveFolderEntityService,
$EmojiEntityService,
$EmojiRequestsEntityService,
$FollowingEntityService,
$FollowRequestEntityService,
$GalleryLikeEntityService,

View file

@ -12,13 +12,13 @@ import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import type { MiDriveFile } from '@/models/DriveFile.js';
import type { MiEmoji } from '@/models/Emoji.js';
import type { EmojisRepository, MiRole, MiUser } from '@/models/_.js';
import type { EmojisRepository, EmojiRequestsRepository, MiRole, MiUser } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js';
import { UtilityService } from '@/core/UtilityService.js';
import { query } from '@/misc/prelude/url.js';
import type { Serialized } from '@/types.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { MiEmojiRequest } from '@/models/EmojiRequest.js';
const parseEmojiStrRegexp = /^([-\w]+)(?:@([\w.-]+))?$/;
@ -34,6 +34,9 @@ export class CustomEmojiService implements OnApplicationShutdown {
@Inject(DI.emojisRepository)
private emojisRepository: EmojisRepository,
@Inject(DI.emojiRequestsRepository)
private emojiRequestsRepository: EmojiRequestsRepository,
private utilityService: UtilityService,
private idService: IdService,
private emojiEntityService: EmojiEntityService,
@ -56,6 +59,41 @@ export class CustomEmojiService implements OnApplicationShutdown {
});
}
@bindThis
public async request(data: {
driveFile: MiDriveFile;
name: string;
category: string | null;
aliases: string[];
license: string | null;
isSensitive: boolean;
localOnly: boolean;
}, me?: MiUser): Promise<MiEmojiRequest> {
const emoji = await this.emojiRequestsRepository.insert({
id: this.idService.gen(),
updatedAt: new Date(),
name: data.name,
category: data.category,
aliases: data.aliases,
originalUrl: data.driveFile.url,
publicUrl: data.driveFile.webpublicUrl ?? data.driveFile.url,
type: data.driveFile.webpublicType ?? data.driveFile.type,
license: data.license,
isSensitive: data.isSensitive,
localOnly: data.localOnly,
fileId: data.driveFile.id,
}).then(x => this.emojiRequestsRepository.findOneByOrFail(x.identifiers[0]));
if (me) {
this.moderationLogService.log(me, 'addCustomEmoji', {
emojiId: emoji.id,
emoji: emoji,
});
}
return emoji;
}
@bindThis
public async add(data: {
driveFile: MiDriveFile;
@ -159,6 +197,36 @@ export class CustomEmojiService implements OnApplicationShutdown {
}
}
@bindThis
public async updateRequest(id: MiEmojiRequest['id'], data: {
driveFile?: MiDriveFile;
name?: string;
category?: string | null;
aliases?: string[];
license?: string | null;
isSensitive?: boolean;
localOnly?: boolean;
}, moderator?: MiUser): Promise<void> {
const emoji = await this.emojiRequestsRepository.findOneByOrFail({ id: id });
const sameNameEmoji = await this.emojiRequestsRepository.findOneBy({ name: data.name });
if (sameNameEmoji != null && sameNameEmoji.id !== id) throw new Error('name already exists');
await this.emojiRequestsRepository.update(emoji.id, {
updatedAt: new Date(),
name: data.name,
category: data.category,
aliases: data.aliases,
license: data.license,
isSensitive: data.isSensitive,
localOnly: data.localOnly,
originalUrl: data.driveFile != null ? data.driveFile.url : undefined,
publicUrl: data.driveFile != null ? (data.driveFile.webpublicUrl ?? data.driveFile.url) : undefined,
type: data.driveFile != null ? (data.driveFile.webpublicType ?? data.driveFile.type) : undefined,
});
this.localEmojisCache.refresh();
}
@bindThis
public async addAliasesBulk(ids: MiEmoji['id'][], aliases: string[]) {
const emojis = await this.emojisRepository.findBy({
@ -267,6 +335,13 @@ export class CustomEmojiService implements OnApplicationShutdown {
}
}
@bindThis
public async deleteRequest(id: MiEmojiRequest['id']) {
const emoji = await this.emojiRequestsRepository.findOneByOrFail({ id: id });
await this.emojiRequestsRepository.delete(emoji.id);
}
@bindThis
public async deleteBulk(ids: MiEmoji['id'][], moderator?: MiUser) {
const emojis = await this.emojisRepository.findBy({
@ -389,11 +464,21 @@ export class CustomEmojiService implements OnApplicationShutdown {
return this.emojisRepository.exists({ where: { name, host: IsNull() } });
}
@bindThis
public checkRequestDuplicate(name: string): Promise<boolean> {
return this.emojiRequestsRepository.exist({ where: { name } });
}
@bindThis
public getEmojiById(id: string): Promise<MiEmoji | null> {
return this.emojisRepository.findOneBy({ id });
}
@bindThis
public getEmojiRequestById(id: string): Promise<MiEmojiRequest | null> {
return this.emojiRequestsRepository.findOneBy({ id });
}
@bindThis
public getEmojiByName(name: string): Promise<MiEmoji | null> {
return this.emojisRepository.findOneBy({ name, host: IsNull() });

View file

@ -213,6 +213,17 @@ export class EmailService {
reason: validated.reason ? formatReason[validated.reason] ?? null : null,
};
}
if (meta.enableActiveEmailValidation) {
const dispose = await this.httpRequestService.send('https://raw.githubusercontent.com/mattyatea/disposable-email-domains/master/disposable_email_blocklist.conf', {
method: 'GET',
});
const disposableEmailDomains = (await dispose.text()).split('\n');
const domain = emailAddress.split('@')[1];
console.log(domain)
if (disposableEmailDomains.includes(domain)) {
validated = { valid: false, reason: 'disposable' };
}
}
const emailDomain: string = emailAddress.split('@')[1];
const isBanned = this.utilityService.isBlockedHost(meta.bannedEmailDomains, emailDomain);

View file

@ -96,6 +96,7 @@ export class FanoutTimelineEndpointService {
if (ps.me) {
const me = ps.me;
const [
userIdsWhoMeMuting,
userIdsWhoMeMutingRenotes,

View file

@ -119,7 +119,7 @@ export interface NoteEventTypes {
};
updated: {
cw: string | null;
text: string;
text: string | null;
};
reacted: {
reaction: string;
@ -160,6 +160,8 @@ export interface AdminEventTypes {
targetUserId: MiUser['id'],
reporterId: MiUser['id'],
comment: string;
notes: any[];
noteIds: string[];
};
}

View file

@ -6,7 +6,8 @@
import { URL } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';
import * as parse5 from 'parse5';
import { Window, XMLSerializer } from 'happy-dom';
import { JSDOM } from 'jsdom';
import serialize from 'w3c-xmlserializer';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { intersperse } from '@/misc/prelude/array.js';
@ -243,7 +244,7 @@ export class MfmService {
return null;
}
const { window } = new Window();
const { window } = new JSDOM() as unknown as { window: Window };
const doc = window.document;
@ -461,6 +462,6 @@ export class MfmService {
appendChildren(nodes, body);
return new XMLSerializer().serializeToString(body);
return serialize(body);
}
}

View file

@ -15,19 +15,16 @@ import { extractHashtags } from '@/misc/extract-hashtags.js';
import type { IMentionedRemoteUsers } from '@/models/Note.js';
import { MiNote } from '@/models/Note.js';
import type { ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
import type { MiDriveFile } from '@/models/DriveFile.js';
import type { MiApp } from '@/models/App.js';
import { concat } from '@/misc/prelude/array.js';
import { IdService } from '@/core/IdService.js';
import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js';
import type { IPoll } from '@/models/Poll.js';
import { MiPoll } from '@/models/Poll.js';
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
import { checkWordMute } from '@/misc/check-word-mute.js';
import type { MiChannel } from '@/models/Channel.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { MemorySingleCache } from '@/misc/cache.js';
import type { MiUserProfile } from '@/models/UserProfile.js';
import type { MiNoteCreateOption as Option, MiMinimumUser as MinimumUser } from '@/types.js';
import { RelayService } from '@/core/RelayService.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import { DI } from '@/di-symbols.js';
@ -128,14 +125,17 @@ type MinimumUser = {
type Option = {
createdAt?: Date | null;
updatedAt?: Date | null;
name?: string | null;
text?: string | null;
reply?: MiNote | null;
renote?: MiNote | null;
files?: MiDriveFile[] | null;
poll?: IPoll | null;
event?: IEvent | null;
localOnly?: boolean | null;
reactionAcceptance?: MiNote['reactionAcceptance'];
disableRightClick?: boolean | null;
cw?: string | null;
visibility?: string;
visibleUsers?: MinimumUser[] | null;
@ -227,6 +227,7 @@ export class NoteCreateService implements OnApplicationShutdown {
host: MiUser['host'];
isBot: MiUser['isBot'];
isCat: MiUser['isCat'];
isGorilla: MiUser['isGorilla'];
}, data: Option, silent = false): Promise<MiNote> {
// チャンネル外にリプライしたら対象のスコープに合わせる
// (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで)
@ -262,13 +263,50 @@ export class NoteCreateService implements OnApplicationShutdown {
}
}
const hasProhibitedWords = await this.checkProhibitedWordsContain({
cw: data.cw,
text: data.text,
pollChoices: data.poll?.choices,
}, meta.prohibitedWords);
if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', meta.prohibitedWords)) {
const { DiscordWebhookUrlWordBlock } = (await this.metaService.fetch());
const regexpregexp = /^\/(.+)\/(.*)$/;
let matchedString = '';
for (const filter of meta.prohibitedWords) {
// represents RegExp
const regexp = filter.match(regexpregexp);
// This should never happen due to input sanitisation.
if (!regexp) {
const words = filter.split(' ');
const foundWord = words.find(keyword => (data.cw ?? data.text ?? '').includes(keyword));
if (foundWord) {
matchedString = foundWord;
break;
}
} else {
const match = new RE2(regexp[1], regexp[2]).exec(data.cw ?? data.text ?? '');
if (match) {
matchedString = match[0];
break;
}
}
}
if (hasProhibitedWords) {
if (DiscordWebhookUrlWordBlock) {
const data_disc = { 'username': 'ノートブロックお知らせ',
'content':
'ユーザー名 :' + user.username + '\n' +
'url : ' + user.host + '\n' +
'contents : ' + data.text + '\n' +
'引っかかったワード :' + matchedString,
'allowed_mentions': {
'parse': [],
},
};
await fetch(DiscordWebhookUrlWordBlock, {
'method': 'post',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data_disc),
});
}
throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words');
}
@ -364,6 +402,15 @@ export class NoteCreateService implements OnApplicationShutdown {
mentionedUsers = data.apMentions ?? await this.extractMentionedUsers(user, combinedTokens);
}
const willCauseNotification = mentionedUsers.filter(u => u.host === null).length > 0 || data.reply?.userHost === null || data.renote?.userHost === null;
if (user.host !== null && willCauseNotification) {
const userEntity = await this.usersRepository.findOneBy({ id: user.id });
if ((userEntity?.followersCount ?? 0) === 0) {
throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words');
}
}
tags = tags.filter(tag => Array.from(tag).length <= 128).splice(0, 32);
if (data.reply && (user.id !== data.reply.userId) && !mentionedUsers.some(u => u.id === data.reply!.userId)) {
@ -962,6 +1009,9 @@ export class NoteCreateService implements OnApplicationShutdown {
this.fanoutTimelineService.push('localTimelineWithFiles', note.id, 500, r);
}
}
if (note.visibility === 'public' && note.userHost !== null) {
this.fanoutTimelineService.push(`remoteLocalTimeline:${note.userHost}`, note.id, 1000, r);
}
}
if (Math.random() < 0.1) {

View file

@ -0,0 +1,297 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey, cherrypick contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { setImmediate } from 'node:timers/promises';
import util from 'util';
import { In, DataSource } from 'typeorm';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import * as mfm from 'mfm-js';
import type { IMentionedRemoteUsers } from '@/models/Note.js';
import { MiNote } from '@/models/Note.js';
import type { NotesRepository, UsersRepository } from '@/models/_.js';
import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js';
import { RelayService } from '@/core/RelayService.js';
import { DI } from '@/di-symbols.js';
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
import { bindThis } from '@/decorators.js';
import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
import { SearchService } from '@/core/SearchService.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { MiDriveFile } from '@/models/_.js';
import { MiPoll, IPoll } from '@/models/Poll.js';
import { concat } from '@/misc/prelude/array.js';
import { extractHashtags } from '@/misc/extract-hashtags.js';
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
type MinimumUser = {
id: MiUser['id'];
host: MiUser['host'];
username: MiUser['username'];
uri: MiUser['uri'];
};
type Option = {
updatedAt?: Date | null;
files?: MiDriveFile[] | null;
name?: string | null;
text?: string | null;
cw?: string | null;
apHashtags?: string[] | null;
apEmojis?: string[] | null;
poll?: IPoll | null;
};
@Injectable()
export class NoteUpdateService implements OnApplicationShutdown {
#shutdownController = new AbortController();
constructor(
@Inject(DI.db)
private db: DataSource,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
private userEntityService: UserEntityService,
private globalEventService: GlobalEventService,
private relayService: RelayService,
private apDeliverManagerService: ApDeliverManagerService,
private apRendererService: ApRendererService,
private searchService: SearchService,
private activeUsersChart: ActiveUsersChart,
) { }
@bindThis
public async update(user: {
id: MiUser['id'];
username: MiUser['username'];
host: MiUser['host'];
isBot: MiUser['isBot'];
}, data: Option, note: MiNote, silent = false): Promise<MiNote | null> {
if (data.updatedAt == null) data.updatedAt = new Date();
if (data.text) {
if (data.text.length > DB_MAX_NOTE_TEXT_LENGTH) {
data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
}
data.text = data.text.trim();
} else {
data.text = null;
}
let tags = data.apHashtags;
let emojis = data.apEmojis;
// Parse MFM if needed
if (!tags || !emojis) {
const tokens = data.text ? mfm.parse(data.text)! : [];
const cwTokens = data.cw ? mfm.parse(data.cw)! : [];
const choiceTokens = data.poll && data.poll.choices
? concat(data.poll.choices.map(choice => mfm.parse(choice)!))
: [];
const combinedTokens = tokens.concat(cwTokens).concat(choiceTokens);
tags = data.apHashtags ?? extractHashtags(combinedTokens);
emojis = data.apEmojis ?? extractCustomEmojisFromMfm(combinedTokens);
}
tags = tags.filter(tag => Array.from(tag ?? '').length <= 128).splice(0, 32);
const updatedNote = await this.updateNote(user, note, data, tags, emojis);
if (updatedNote) {
setImmediate('post updated', { signal: this.#shutdownController.signal }).then(
() => this.postNoteUpdated(updatedNote, user, silent),
() => { /* aborted, ignore this */ },
);
}
return updatedNote;
}
@bindThis
private async updateNote(user: {
id: MiUser['id']; host: MiUser['host'];
}, note: MiNote, data: Option, tags: string[], emojis: string[]): Promise<MiNote | null> {
const updatedAtHistory = note.updatedAtHistory ? note.updatedAtHistory : [];
const values = new MiNote({
updatedAt: data.updatedAt!,
fileIds: data.files ? data.files.map(file => file.id) : [],
text: data.text,
hasPoll: data.poll != null,
cw: data.cw ?? null,
tags: tags.map(tag => normalizeForSearch(tag)),
emojis,
attachedFileTypes: data.files ? data.files.map(file => file.type) : [],
updatedAtHistory: [...updatedAtHistory, new Date()],
noteEditHistory: [...note.noteEditHistory, (note.cw ? note.cw + '\n' : '') + note.text!],
});
// 投稿を更新
try {
if (note.hasPoll && values.hasPoll) {
// Start transaction
await this.db.transaction(async transactionalEntityManager => {
await transactionalEntityManager.update(MiNote, { id: note.id }, values);
if (values.hasPoll) {
const old_poll = await transactionalEntityManager.findOneBy(MiPoll, { noteId: note.id });
if (old_poll!.choices.toString() !== data.poll!.choices.toString() || old_poll!.multiple !== data.poll!.multiple) {
await transactionalEntityManager.delete(MiPoll, { noteId: note.id });
const poll = new MiPoll({
noteId: note.id,
choices: data.poll!.choices,
expiresAt: data.poll!.expiresAt,
multiple: data.poll!.multiple,
votes: new Array(data.poll!.choices.length).fill(0),
noteVisibility: note.visibility,
userId: user.id,
userHost: user.host,
});
await transactionalEntityManager.insert(MiPoll, poll);
}
}
});
} else if (!note.hasPoll && values.hasPoll) {
// Start transaction
await this.db.transaction(async transactionalEntityManager => {
await transactionalEntityManager.update(MiNote, { id: note.id }, values);
if (values.hasPoll) {
const poll = new MiPoll({
noteId: note.id,
choices: data.poll!.choices,
expiresAt: data.poll!.expiresAt,
multiple: data.poll!.multiple,
votes: new Array(data.poll!.choices.length).fill(0),
noteVisibility: note.visibility,
userId: user.id,
userHost: user.host,
});
await transactionalEntityManager.insert(MiPoll, poll);
}
});
} else if (note.hasPoll && !values.hasPoll) {
// Start transaction
await this.db.transaction(async transactionalEntityManager => {
await transactionalEntityManager.update(MiNote, { id: note.id }, values);
if (!values.hasPoll) {
await transactionalEntityManager.delete(MiPoll, { noteId: note.id });
}
});
} else {
await this.notesRepository.update({ id: note.id }, values);
}
return await this.notesRepository.findOneBy({ id: note.id });
} catch (e) {
console.error(e);
throw e;
}
}
@bindThis
private async postNoteUpdated(note: MiNote, user: {
id: MiUser['id'];
username: MiUser['username'];
host: MiUser['host'];
isBot: MiUser['isBot'];
}, silent: boolean) {
if (!silent) {
if (this.userEntityService.isLocalUser(user)) this.activeUsersChart.write(user);
this.globalEventService.publishNoteStream(note.id, 'updated', { cw: note.cw, text: note.text });
//#region AP deliver
if (this.userEntityService.isLocalUser(user)) {
await (async () => {
// @ts-ignore
const noteActivity = await this.renderNoteActivity(note, user);
await this.deliverToConcerned(user, note, noteActivity);
})();
}
//#endregion
}
// Register to search database
this.reIndex(note);
}
@bindThis
private async renderNoteActivity(note: MiNote, user: MiUser) {
const content = this.apRendererService.renderUpdate(await this.apRendererService.renderNote(note, false), user);
return this.apRendererService.addContext(content);
}
@bindThis
private async getMentionedRemoteUsers(note: MiNote) {
const where = [] as any[];
// mention / reply / dm
const uris = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri);
if (uris.length > 0) {
where.push(
{ uri: In(uris) },
);
}
// renote / quote
if (note.renoteUserId) {
where.push({
id: note.renoteUserId,
});
}
if (where.length === 0) return [];
return await this.usersRepository.find({
where,
}) as MiRemoteUser[];
}
@bindThis
private async deliverToConcerned(user: { id: MiLocalUser['id']; host: null; }, note: MiNote, content: any) {
console.log('deliverToConcerned', util.inspect(content, { depth: null }));
await this.apDeliverManagerService.deliverToFollowers(user, content);
await this.relayService.deliverToRelays(user, content);
const remoteUsers = await this.getMentionedRemoteUsers(note);
for (const remoteUser of remoteUsers) {
await this.apDeliverManagerService.deliverToUser(user, content, remoteUser);
}
}
@bindThis
private reIndex(note: MiNote) {
if (note.text == null && note.cw == null) return;
this.searchService.unindexNote(note);
this.searchService.indexNote(note);
}
@bindThis
public dispose(): void {
this.#shutdownController.abort();
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}

View file

@ -21,6 +21,7 @@ import type { Provider } from '@nestjs/common';
export type SystemQueue = Bull.Queue<Record<string, unknown>>;
export type EndedPollNotificationQueue = Bull.Queue<EndedPollNotificationJobData>;
export type ScheduleNotePostQueue = Bull.Queue<ScheduleNotePostJobData>;
export type DeliverQueue = Bull.Queue<DeliverJobData>;
export type InboxQueue = Bull.Queue<InboxJobData>;
export type DbQueue = Bull.Queue;
@ -41,6 +42,12 @@ const $endedPollNotification: Provider = {
inject: [DI.config],
};
const $scheduleNotePost: Provider = {
provide: 'queue:scheduleNotePost',
useFactory: (config: Config) => new Bull.Queue(QUEUE.SCHEDULE_NOTE_POST, baseQueueOptions(config, QUEUE.SCHEDULE_NOTE_POST)),
inject: [DI.config],
};
const $deliver: Provider = {
provide: 'queue:deliver',
useFactory: (config: Config) => new Bull.Queue(QUEUE.DELIVER, baseQueueOptions(config, QUEUE.DELIVER)),
@ -89,6 +96,7 @@ const $systemWebhookDeliver: Provider = {
providers: [
$system,
$endedPollNotification,
$scheduleNotePost,
$deliver,
$inbox,
$db,
@ -100,6 +108,7 @@ const $systemWebhookDeliver: Provider = {
exports: [
$system,
$endedPollNotification,
$scheduleNotePost,
$deliver,
$inbox,
$db,
@ -113,6 +122,7 @@ export class QueueModule implements OnApplicationShutdown {
constructor(
@Inject('queue:system') public systemQueue: SystemQueue,
@Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue,
@Inject('queue:scheduleNotePost') public scheduleNotePostQueue: ScheduleNotePostQueue,
@Inject('queue:deliver') public deliverQueue: DeliverQueue,
@Inject('queue:inbox') public inboxQueue: InboxQueue,
@Inject('queue:db') public dbQueue: DbQueue,
@ -129,6 +139,7 @@ export class QueueModule implements OnApplicationShutdown {
await Promise.all([
this.systemQueue.close(),
this.endedPollNotificationQueue.close(),
this.scheduleNotePostQueue.close(),
this.deliverQueue.close(),
this.inboxQueue.close(),
this.dbQueue.close(),

View file

@ -14,6 +14,7 @@ import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js';
import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js';
import type {
DbJobData,
DeliverJobData,
@ -32,6 +33,7 @@ import type {
SystemQueue,
UserWebhookDeliverQueue,
SystemWebhookDeliverQueue,
ScheduleNotePostQueue
} from './QueueModule.js';
import type httpSignature from '@peertube/http-signature';
import type * as Bull from 'bullmq';
@ -44,6 +46,7 @@ export class QueueService {
@Inject('queue:system') public systemQueue: SystemQueue,
@Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue,
@Inject('queue:scheduleNotePost') public ScheduleNotePostQueue: ScheduleNotePostQueue,
@Inject('queue:deliver') public deliverQueue: DeliverQueue,
@Inject('queue:inbox') public inboxQueue: InboxQueue,
@Inject('queue:db') public dbQueue: DbQueue,

View file

@ -166,29 +166,56 @@ export class ReactionService {
userId: user.id,
reaction,
};
if (user.host == null) {
const exists = await this.noteReactionsRepository.findOneBy({
noteId: note.id,
userId: user.id,
reaction: record.reaction,
});
// Create reaction
try {
await this.noteReactionsRepository.insert(record);
} catch (e) {
if (isDuplicateKeyValueError(e)) {
const exists = await this.noteReactionsRepository.findOneByOrFail({
noteId: note.id,
userId: user.id,
});
const count = await this.noteReactionsRepository.countBy({
noteId: note.id,
userId: user.id,
});
if (exists.reaction !== reaction) {
// 別のリアクションがすでにされていたら置き換える
await this.delete(user, note);
if (count > 3) {
throw new IdentifiableError('51c42bb4-931a-456b-bff7-e5a8a70dd298');
}
if (exists == null) {
if (user.host == null) {
await this.noteReactionsRepository.insert(record);
} else {
// 同じリアクションがすでにされていたらエラー
throw new IdentifiableError('51c42bb4-931a-456b-bff7-e5a8a70dd298');
}
} else {
throw e;
// 同じリアクションがすでにされていたらエラー
throw new IdentifiableError('51c42bb4-931a-456b-bff7-e5a8a70dd298');
}
} else {
try {
await this.noteReactionsRepository.insert(record);
} catch (e) {
if (isDuplicateKeyValueError(e)) {
const exists = await this.noteReactionsRepository.findOneByOrFail({
noteId: note.id,
userId: user.id,
});
if (exists.reaction !== reaction) {
// 別のリアクションがすでにされていたら置き換える
await this.delete(user, note);
await this.noteReactionsRepository.insert(record);
} else {
// 同じリアクションがすでにされていたらエラー
throw new IdentifiableError('51c42bb4-931a-456b-bff7-e5a8a70dd298');
}
} else {
throw e;
}
}
}
// Create reaction
// Increment reactions count
const sql = `jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + 1)::text::jsonb)`;
@ -281,17 +308,24 @@ export class ReactionService {
}
@bindThis
public async delete(user: { id: MiUser['id']; host: MiUser['host']; isBot: MiUser['isBot']; }, note: MiNote) {
public async delete(user: { id: MiUser['id']; host: MiUser['host']; isBot: MiUser['isBot']; }, note: MiNote, reaction?: string) {
// if already unreacted
const exist = await this.noteReactionsRepository.findOneBy({
noteId: note.id,
userId: user.id,
});
let exist;
if (reaction == null) {
exist = await this.noteReactionsRepository.findOneBy({
noteId: note.id,
userId: user.id,
});
} else {
exist = await this.noteReactionsRepository.findOneBy({
noteId: note.id,
userId: user.id,
reaction: reaction.replace(/@./, ''),
});
}
if (exist == null) {
throw new IdentifiableError('60527ec9-b4cb-4a88-a6bd-32d3ad26817d', 'not reacted');
}
// Delete reaction
const result = await this.noteReactionsRepository.delete(exist.id);

View file

@ -35,12 +35,15 @@ export type RolePolicies = {
gtlAvailable: boolean;
ltlAvailable: boolean;
canPublicNote: boolean;
canEditNote: boolean;
canScheduleNote: boolean;
mentionLimit: number;
canInvite: boolean;
inviteLimit: number;
inviteLimitCycle: number;
inviteExpirationTime: number;
canManageCustomEmojis: boolean;
canRequestCustomEmojis: boolean;
canManageAvatarDecorations: boolean;
canSearchNotes: boolean;
canUseTranslator: boolean;
@ -57,6 +60,9 @@ export type RolePolicies = {
userEachUserListsLimit: number;
rateLimitFactor: number;
avatarDecorationLimit: number;
emojiPickerProfileLimit: number;
listPinnedLimit: number;
localTimelineAnyLimit: number;
};
export const DEFAULT_POLICIES: RolePolicies = {
@ -64,11 +70,14 @@ export const DEFAULT_POLICIES: RolePolicies = {
ltlAvailable: true,
canPublicNote: true,
mentionLimit: 20,
canEditNote: true,
canScheduleNote: true,
canInvite: false,
inviteLimit: 0,
inviteLimitCycle: 60 * 24 * 7,
inviteExpirationTime: 0,
canManageCustomEmojis: false,
canRequestCustomEmojis: false,
canManageAvatarDecorations: false,
canSearchNotes: false,
canUseTranslator: true,
@ -85,6 +94,9 @@ export const DEFAULT_POLICIES: RolePolicies = {
userEachUserListsLimit: 50,
rateLimitFactor: 1,
avatarDecorationLimit: 1,
emojiPickerProfileLimit: 2,
listPinnedLimit: 2,
localTimelineAnyLimit: 3,
};
@Injectable()
@ -364,13 +376,17 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
gtlAvailable: calc('gtlAvailable', vs => vs.some(v => v === true)),
ltlAvailable: calc('ltlAvailable', vs => vs.some(v => v === true)),
canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)),
canScheduleNote: calc('canScheduleNote', vs => vs.some(v => v === true)),
canEditNote: calc('canEditNote', vs => vs.some(v => v === true)),
mentionLimit: calc('mentionLimit', vs => Math.max(...vs)),
canInvite: calc('canInvite', vs => vs.some(v => v === true)),
inviteLimit: calc('inviteLimit', vs => Math.max(...vs)),
inviteLimitCycle: calc('inviteLimitCycle', vs => Math.max(...vs)),
inviteExpirationTime: calc('inviteExpirationTime', vs => Math.max(...vs)),
canManageCustomEmojis: calc('canManageCustomEmojis', vs => vs.some(v => v === true)),
canRequestCustomEmojis: calc('canRequestCustomEmojis', vs => vs.some(v => v === true)),
canManageAvatarDecorations: calc('canManageAvatarDecorations', vs => vs.some(v => v === true)),
canRequestCustomEmojis: calc('canRequestCustomEmojis', vs => vs.some(v => v === true)),
canSearchNotes: calc('canSearchNotes', vs => vs.some(v => v === true)),
canUseTranslator: calc('canUseTranslator', vs => vs.some(v => v === true)),
canHideAds: calc('canHideAds', vs => vs.some(v => v === true)),
@ -386,6 +402,9 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
userEachUserListsLimit: calc('userEachUserListsLimit', vs => Math.max(...vs)),
rateLimitFactor: calc('rateLimitFactor', vs => Math.max(...vs)),
avatarDecorationLimit: calc('avatarDecorationLimit', vs => Math.max(...vs)),
emojiPickerProfileLimit: calc('emojiPickerProfileLimit', vs => Math.max(...vs)),
listPinnedLimit: calc('listPinnedLimit', vs => Math.max(...vs)),
localTimelineAnyLimit: calc('localTimelineAnyLimit', vs => Math.max(...vs)),
};
}

View file

@ -14,6 +14,7 @@ import { NotePiningService } from '@/core/NotePiningService.js';
import { UserBlockingService } from '@/core/UserBlockingService.js';
import { NoteDeleteService } from '@/core/NoteDeleteService.js';
import { NoteCreateService } from '@/core/NoteCreateService.js';
import { NoteUpdateService } from '@/core/NoteUpdateService.js';
import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js';
import { AppLockService } from '@/core/AppLockService.js';
import type Logger from '@/logger.js';
@ -73,6 +74,7 @@ export class ApInboxService {
private notePiningService: NotePiningService,
private userBlockingService: UserBlockingService,
private noteCreateService: NoteCreateService,
private noteUpdateService: NoteUpdateService,
private noteDeleteService: NoteDeleteService,
private appLockService: AppLockService,
private apResolverService: ApResolverService,
@ -751,11 +753,13 @@ export class ApInboxService {
@bindThis
private async update(actor: MiRemoteUser, activity: IUpdate): Promise<string> {
const uri = getApId(activity);
if (actor.uri !== activity.actor) {
return 'skip: invalid actor';
}
this.logger.debug('Update');
this.logger.debug(`Update: ${uri}`);
const resolver = this.apResolverService.createResolver();
@ -767,14 +771,51 @@ export class ApInboxService {
if (isActor(object)) {
await this.apPersonService.updatePerson(actor.uri, resolver, object);
return 'ok: Person updated';
} else if (getApType(object) === 'Question') {
} /*else if (getApType(object) === 'Question') {
await this.apQuestionService.updateQuestion(object, resolver).catch(err => console.error(err));
return 'ok: Question updated';
}*/ else if (getApType(object) === 'Note' || getApType(object) === 'Question') {
await this.updateNote(resolver, actor, object, false, activity);
return 'ok: Note updated';
} else {
return `skip: Unknown type: ${getApType(object)}`;
}
}
@bindThis
private async updateNote(resolver: Resolver, actor: MiRemoteUser, note: IObject, silent = false, activity?: IUpdate): Promise<string> {
const uri = getApId(note);
if (typeof note === 'object') {
if (actor.uri !== note.attributedTo) {
return 'skip: actor.uri !== note.attributedTo';
}
if (typeof note.id === 'string') {
if (this.utilityService.extractDbHost(actor.uri) !== this.utilityService.extractDbHost(note.id)) {
return 'skip: host in actor.uri !== note.id';
}
}
}
const unlock = await this.appLockService.getApLock(uri);
try {
const target = await this.notesRepository.findOneBy({uri: uri});
if (!target) return `skip: target note not located: ${uri}`;
await this.apNoteService.updateNote(note, target, resolver, silent);
return 'ok';
} catch (err) {
if (err instanceof StatusError && err.isClientError) {
return `skip ${err.statusCode}`;
} else {
throw err;
}
} finally {
unlock();
}
}
@bindThis
private async move(actor: MiRemoteUser, activity: IMove): Promise<string> {
// fetch the new and old accounts

View file

@ -108,6 +108,7 @@ export class ApRendererService {
actor: this.userEntityService.genLocalUserUri(note.userId),
type: 'Announce',
published: this.idService.parse(note.id).date.toISOString(),
updated: note.updatedAt?.toISOString() ?? undefined,
to,
cc,
object,
@ -438,6 +439,7 @@ export class ApRendererService {
_misskey_quote: quote,
quoteUrl: quote,
published: this.idService.parse(note.id).date.toISOString(),
updated: note.updatedAt?.toISOString() ?? undefined,
to,
cc,
inReplyTo,

View file

@ -1,12 +1,13 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project , Type4ny-project
* SPDX-FileCopyrightText: syuilo and misskey-project , Type4ny-project, cherrypick contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import promiseLimit from 'promise-limit';
import { DI } from '@/di-symbols.js';
import type { PollsRepository, EmojisRepository } from '@/models/_.js';
import type { PollsRepository, EmojisRepository, NotesRepository } from '@/models/_.js';
import type { Config } from '@/config.js';
import type { MiRemoteUser } from '@/models/User.js';
import type { MiNote } from '@/models/Note.js';
@ -24,7 +25,8 @@ import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js';
import { checkHttps } from '@/misc/check-https.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js';
import { NoteUpdateService } from '@/core/NoteUpdateService.js';
import { getApId, getApType, getOneApHrefNullable, getOneApId, isEmoji, validPost } from '../type.js';
import { ApLoggerService } from '../ApLoggerService.js';
import { ApMfmService } from '../ApMfmService.js';
import { ApDbResolverService } from '../ApDbResolverService.js';
@ -52,6 +54,9 @@ export class ApNoteService {
@Inject(DI.emojisRepository)
private emojisRepository: EmojisRepository,
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
private idService: IdService,
private apMfmService: ApMfmService,
private apResolverService: ApResolverService,
@ -69,6 +74,7 @@ export class ApNoteService {
private appLockService: AppLockService,
private pollService: PollService,
private noteCreateService: NoteCreateService,
private noteUpdateService: NoteUpdateService,
private apDbResolverService: ApDbResolverService,
private apLoggerService: ApLoggerService,
) {
@ -295,6 +301,7 @@ export class ApNoteService {
try {
return await this.noteCreateService.create(actor, {
createdAt: note.published ? new Date(note.published) : null,
updatedAt: note.updated ? new Date(note.updated) : null,
files,
reply,
renote: quote,
@ -324,6 +331,85 @@ export class ApNoteService {
}
}
@bindThis
public async updateNote(value: string | IObject, target: MiNote, resolver?: Resolver, silent = false): Promise<MiNote | null> {
if (resolver == null) resolver = this.apResolverService.createResolver();
const object = await resolver.resolve(value);
const entryUri = getApId(value);
const err = this.validateNote(object, entryUri);
if (err) {
this.logger.error(err.message, {
resolver: { history: resolver.getHistory() },
value,
object,
});
throw new Error('invalid note');
}
const note = object as IPost;
// 投稿者をフェッチ
if (note.attributedTo == null) {
throw new Error('invalid note.attributedTo: ' + note.attributedTo);
}
const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo), resolver) as MiRemoteUser;
// 投稿者が凍結されていたらスキップ
if (actor.isSuspended) {
throw new Error('actor has been suspended');
}
const limit = promiseLimit<MiDriveFile>(2);
const files = (await Promise.all(toArray(note.attachment).map(attach => (
limit(() => this.apImageService.resolveImage(actor, {
...attach,
sensitive: note.sensitive, // Noteがsensitiveなら添付もsensitiveにする
}))
))));
const cw = note.summary === '' ? null : note.summary;
// テキストのパース
let text: string | null = null;
if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') {
text = note.source.content;
} else if (typeof note._misskey_content !== 'undefined') {
text = note._misskey_content;
} else if (typeof note.content === 'string') {
text = this.apMfmService.htmlToMfm(note.content, note.tag);
}
const apHashtags = extractApHashtags(note.tag);
const emojis = await this.extractEmojis(note.tag ?? [], actor.host).catch(e => {
this.logger.info(`extractEmojis: ${e}`);
return [];
});
const apEmojis = emojis.map(emoji => emoji.name);
const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined);
try {
return await this.noteUpdateService.update(actor, {
updatedAt: note.updated ? new Date(note.updated) : null,
files,
name: note.name,
cw,
text,
apHashtags,
apEmojis,
poll,
}, target, silent);
} catch (err: any) {
this.logger.warn(`note update failed: ${err}`);
return err;
}
}
/**
* Noteを解決します
*

View file

@ -14,6 +14,7 @@ export interface IObject {
summary?: string;
_misskey_summary?: string;
published?: string;
updated?: string;
cc?: ApObject;
to?: ApObject;
attributedTo?: ApObject;

View file

@ -65,21 +65,21 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
this.followingsRepository.createQueryBuilder('following')
.select('COUNT(DISTINCT following.followeeHost)')
.where('following.followeeHost IS NOT NULL')
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT IN (:...blocked)', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
.andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
.getRawOne()
.then(x => parseInt(x.count, 10)),
this.followingsRepository.createQueryBuilder('following')
.select('COUNT(DISTINCT following.followerHost)')
.where('following.followerHost IS NOT NULL')
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followerHost NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followerHost NOT IN (:...blocked)', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
.andWhere(`following.followerHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
.getRawOne()
.then(x => parseInt(x.count, 10)),
this.followingsRepository.createQueryBuilder('following')
.select('COUNT(DISTINCT following.followeeHost)')
.where('following.followeeHost IS NOT NULL')
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT IN (:...blocked)', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
.andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
.andWhere(`following.followeeHost IN (${ pubsubSubQuery.getQuery() })`)
.setParameters(pubsubSubQuery.getParameters())
@ -88,7 +88,7 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
this.instancesRepository.createQueryBuilder('instance')
.select('COUNT(instance.id)')
.where(`instance.host IN (${ subInstancesQuery.getQuery() })`)
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT IN (:...blocked)', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
.andWhere('instance.suspensionState = \'none\'')
.andWhere('instance.isNotResponding = false')
.getRawOne()
@ -96,7 +96,7 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
this.instancesRepository.createQueryBuilder('instance')
.select('COUNT(instance.id)')
.where(`instance.host IN (${ pubInstancesQuery.getQuery() })`)
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT IN (:...blocked)', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
.andWhere('instance.suspensionState = \'none\'')
.andWhere('instance.isNotResponding = false')
.getRawOne()

View file

@ -4,12 +4,15 @@
*/
import { Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';
import type { AbuseUserReportsRepository } from '@/models/_.js';
import type { AbuseUserReportsRepository, NotesRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
import { bindThis } from '@/decorators.js';
import { IdService } from '@/core/IdService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { isNotNull } from '@/misc/is-not-null.js';
import type { Packed } from '@/misc/json-schema.js';
import { UserEntityService } from './UserEntityService.js';
@ -19,7 +22,11 @@ export class AbuseUserReportEntityService {
@Inject(DI.abuseUserReportsRepository)
private abuseUserReportsRepository: AbuseUserReportsRepository,
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
private userEntityService: UserEntityService,
private noteEntityService: NoteEntityService,
private idService: IdService,
) {
}
@ -34,11 +41,27 @@ export class AbuseUserReportEntityService {
},
) {
const report = typeof src === 'object' ? src : await this.abuseUserReportsRepository.findOneByOrFail({ id: src });
const notes = [];
if (report.noteIds && report.noteIds.length > 0) {
for (const x of report.noteIds) {
const exists = await this.notesRepository.countBy({ id: x });
if (exists === 0) {
notes.push('deleted');
continue;
}
notes.push(await this.noteEntityService.pack(x));
}
} else if (report.notes.length > 0) {
notes.push(...(report.notes));
}
console.log(report.notes.length, null, notes);
return await awaitAll({
id: report.id,
createdAt: this.idService.parse(report.id).date.toISOString(),
comment: report.comment,
notes,
resolved: report.resolved,
reporterId: report.reporterId,
targetUserId: report.targetUserId,

View file

@ -132,7 +132,10 @@ export class DriveFileEntityService {
}
return url;
}
@bindThis
public async getFromUrl(url: string): Promise<MiDriveFile | null> {
return this.driveFilesRepository.findOneBy({ url: url });
}
@bindThis
public async calcDriveUsageOf(user: MiUser['id'] | { id: MiUser['id'] }): Promise<number> {
const id = typeof user === 'object' ? user.id : user;

View file

@ -34,6 +34,7 @@ export class EmojiEntityService {
localOnly: emoji.localOnly ? true : undefined,
isSensitive: emoji.isSensitive ? true : undefined,
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : undefined,
draft: emoji.draft,
};
}
@ -62,6 +63,7 @@ export class EmojiEntityService {
isSensitive: emoji.isSensitive,
localOnly: emoji.localOnly,
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction,
draft: emoji.draft,
};
}

View file

@ -0,0 +1,70 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { EmojiRequestsRepository } from '@/models/_.js';
import type { Packed } from '@/misc/json-schema.js';
import { bindThis } from '@/decorators.js';
import { MiEmojiRequest } from '@/models/EmojiRequest.js';
@Injectable()
export class EmojiRequestsEntityService {
constructor(
@Inject(DI.emojiRequestsRepository)
private emojiRequestsRepository: EmojiRequestsRepository,
) {
}
@bindThis
public async packSimple(
src: MiEmojiRequest['id'] | MiEmojiRequest,
): Promise<Packed<'EmojiRequestSimple'>> {
const emoji = typeof src === 'object' ? src : await this.emojiRequestsRepository.findOneByOrFail({ id: src });
return {
aliases: emoji.aliases,
name: emoji.name,
category: emoji.category,
// || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ)
url: emoji.publicUrl,
isSensitive: emoji.isSensitive ? true : undefined,
};
}
@bindThis
public packSimpleMany(
emojis: any[],
) {
return Promise.all(emojis.map(x => this.packSimple(x)));
}
@bindThis
public async packDetailed(
src: MiEmojiRequest['id'] | MiEmojiRequest,
): Promise<Packed<'EmojiRequestDetailed'>> {
const emoji = typeof src === 'object' ? src : await this.emojiRequestsRepository.findOneByOrFail({ id: src });
return {
id: emoji.id,
aliases: emoji.aliases,
name: emoji.name,
category: emoji.category,
url: emoji.publicUrl,
license: emoji.license,
isSensitive: emoji.isSensitive,
localOnly: emoji.localOnly,
fileId: emoji.fileId,
};
}
@bindThis
public packDetailedMany(
emojis: any[],
) {
return Promise.all(emojis.map(x => this.packDetailed(x)));
}
}

View file

@ -70,6 +70,11 @@ export class MetaEntityService {
inquiryUrl: instance.inquiryUrl,
disableRegistration: instance.disableRegistration,
emailRequiredForSignup: instance.emailRequiredForSignup,
bannerDark: instance.bannerDark,
bannerLight: instance.bannerLight,
iconDark: instance.iconDark,
iconLight: instance.iconLight,
googleAnalyticsId: instance.googleAnalyticsId,
enableHcaptcha: instance.enableHcaptcha,
hcaptchaSiteKey: instance.hcaptchaSiteKey,
enableMcaptcha: instance.enableMcaptcha,
@ -85,6 +90,7 @@ export class MetaEntityService {
bannerUrl: instance.bannerUrl,
infoImageUrl: instance.infoImageUrl,
serverErrorImageUrl: instance.serverErrorImageUrl,
googleAnalyticsId: instance.googleAnalyticsId,
notFoundImageUrl: instance.notFoundImageUrl,
iconUrl: instance.iconUrl,
backgroundImageUrl: instance.backgroundImageUrl,

View file

@ -208,6 +208,30 @@ export class NoteEntityService implements OnModuleInit {
return undefined;
}
@bindThis
public async populateMyReactions(note: { id: MiNote['id']; reactions: MiNote['reactions']; reactionAndUserPairCache?: MiNote['reactionAndUserPairCache']; }, meId: MiUser['id'], _hint_?: {
myReactions: Map<MiNote['id'], string | null>;
}) {
const reactionsCount = Object.values(note.reactions).reduce((a, b) => a + b, 0);
if (reactionsCount === 0) return undefined;
// パフォーマンスのためートが作成されてから2秒以上経っていない場合はリアクションを取得しない
if (this.idService.parse(note.id).date.getTime() + 2000 > Date.now()) {
return undefined;
}
const reactions = await this.noteReactionsRepository.findBy({
userId: meId,
noteId: note.id,
});
if (reactions.length > 0) {
return reactions.map(reaction => this.reactionService.convertLegacyReaction(reaction.reaction));
}
return undefined;
}
@bindThis
public async isVisibleForMe(note: MiNote, meId: MiUser['id'] | null): Promise<boolean> {
@ -324,6 +348,9 @@ export class NoteEntityService implements OnModuleInit {
const packed: Packed<'Note'> = await awaitAll({
id: note.id,
createdAt: this.idService.parse(note.id).date.toISOString(),
updatedAt: note.updatedAt ? note.updatedAt.toISOString() : undefined,
updatedAtHistory: note.updatedAtHistory ? note.updatedAtHistory.map(x => x.toISOString()) : undefined,
noteEditHistory: note.noteEditHistory.length ? note.noteEditHistory : undefined,
userId: note.userId,
user: packedUsers?.get(note.userId) ?? this.userEntityService.pack(note.user ?? note.userId, me),
text: text,
@ -378,6 +405,7 @@ export class NoteEntityService implements OnModuleInit {
...(meId && Object.keys(note.reactions).length > 0 ? {
myReaction: this.populateMyReaction(note, meId, options?._hint_),
myReactions: this.populateMyReactions(note, meId, options?._hint_),
} : {}),
} : {}),
});

View file

@ -162,6 +162,9 @@ export class NotificationEntityService implements OnModuleInit {
...(notification.type === 'achievementEarned' ? {
achievement: notification.achievement,
} : {}),
...(notification.type === 'loginbonus' ? {
loginbonus: notification.loginbonus,
} : {}),
...(notification.type === 'app' ? {
body: notification.customBody,
header: notification.customHeader,

View file

@ -470,9 +470,8 @@ export class UserEntityService implements OnModuleInit {
createdAt: this.idService.parse(announcement.id).date.toISOString(),
...announcement,
})) : null;
console.log(user.getPoints);
const notificationsInfo = isMe && isDetailed ? await this.getNotificationsInfo(user.id) : null;
const packed = {
id: user.id,
name: user.name,
@ -506,7 +505,7 @@ export class UserEntityService implements OnModuleInit {
iconUrl: r.iconUrl,
displayOrder: r.displayOrder,
}))) : undefined,
...(user.host == null ? { getPoints: user.getPoints } : {}),
...(isDetailed ? {
url: profile!.url,
uri: user.uri,

View file

@ -66,7 +66,7 @@ export class ServerStatsService implements OnApplicationShutdown {
if (log.length > 200) log.pop();
};
tick();
await tick();
this.intervalId = setInterval(tick, interval);
}

View file

@ -15,6 +15,7 @@ export const DI = {
//#region Repositories
usersRepository: Symbol('usersRepository'),
notesRepository: Symbol('notesRepository'),
scheduledNotesRepository: Symbol('scheduledNotesRepository'),
announcementsRepository: Symbol('announcementsRepository'),
announcementReadsRepository: Symbol('announcementReadsRepository'),
appsRepository: Symbol('appsRepository'),
@ -40,6 +41,7 @@ export const DI = {
followRequestsRepository: Symbol('followRequestsRepository'),
instancesRepository: Symbol('instancesRepository'),
emojisRepository: Symbol('emojisRepository'),
emojiRequestsRepository: Symbol('emojiRequestsRepository'),
driveFilesRepository: Symbol('driveFilesRepository'),
driveFoldersRepository: Symbol('driveFoldersRepository'),
metasRepository: Symbol('metasRepository'),

View file

@ -33,7 +33,7 @@ import { packedClipSchema } from '@/models/json-schema/clip.js';
import { packedFederationInstanceSchema } from '@/models/json-schema/federation-instance.js';
import { packedQueueCountSchema } from '@/models/json-schema/queue.js';
import { packedGalleryPostSchema } from '@/models/json-schema/gallery-post.js';
import { packedEmojiDetailedSchema, packedEmojiSimpleSchema } from '@/models/json-schema/emoji.js';
import { packedEmojiDetailedSchema, packedEmojiRequestSimpleSchema, packedEmojiSimpleSchema, packedEmojiRequestDetailedSchema } from '@/models/json-schema/emoji.js';
import { packedFlashSchema } from '@/models/json-schema/flash.js';
import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
import { packedSigninSchema } from '@/models/json-schema/signin.js';
@ -94,7 +94,9 @@ export const refs = {
FederationInstance: packedFederationInstanceSchema,
GalleryPost: packedGalleryPostSchema,
EmojiSimple: packedEmojiSimpleSchema,
EmojiRequestSimple: packedEmojiRequestSimpleSchema,
EmojiDetailed: packedEmojiDetailedSchema,
EmojiRequestDetailed: packedEmojiRequestDetailedSchema,
Flash: packedFlashSchema,
Signin: packedSigninSchema,
RoleCondFormulaLogics: packedRoleCondFormulaLogicsSchema,

View file

@ -60,6 +60,16 @@ export class MiAbuseUserReport {
})
public comment: string;
@Column('jsonb', {
default: [],
})
public notes: any[];
@Column('jsonb', {
default: [],
})
public noteIds: string[] | null;
//#region Denormalized fields
@Index()
@Column('varchar', {

View file

@ -31,6 +31,12 @@ export class MiAvatarDecoration {
})
public description: string;
@Column('varchar', {
length: 256,
default: '',
})
public category: string;
// TODO: 定期ジョブで存在しなくなったロールIDを除去するようにする
@Column('varchar', {
array: true, length: 128, default: '{}',

View file

@ -81,4 +81,10 @@ export class MiEmoji {
array: true, length: 128, default: '{}',
})
public roleIdsThatCanBeUsedThisEmojiAsReaction: string[];
@Column('boolean', {
default: false,
nullable: false,
})
public draft: boolean;
}

View file

@ -0,0 +1,71 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { PrimaryColumn, Entity, Index, Column } from 'typeorm';
import { id } from './util/id.js';
@Entity('emoji_request')
@Index(['name'], { unique: true })
export class MiEmojiRequest {
@PrimaryColumn(id())
public id: string;
@Column('timestamp with time zone', {
nullable: true,
})
public updatedAt: Date | null;
@Column('varchar', {
length: 128,
})
public name: string;
@Column('varchar', {
length: 128, nullable: true,
})
public category: string | null;
@Column('varchar', {
length: 512,
})
public originalUrl: string;
@Column('varchar', {
length: 512,
default: '',
})
public publicUrl: string;
// publicUrlの方のtypeが入る
@Column('varchar', {
length: 64, nullable: true,
})
public type: string | null;
@Column('varchar', {
array: true, length: 128, default: '{}',
})
public aliases: string[];
@Column('varchar', {
length: 1024, nullable: true,
})
public license: string | null;
@Column('varchar', {
length: 1024, nullable: false,
})
public fileId: string;
@Column('boolean', {
default: false,
})
public localOnly: boolean;
@Column('boolean', {
default: false,
})
public isSensitive: boolean;
}

View file

@ -139,6 +139,11 @@ export class MiMeta {
nullable: true,
})
public serverErrorImageUrl: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public googleAnalyticsId: string | null;
@Column('varchar', {
length: 1024,
@ -224,11 +229,33 @@ export class MiMeta {
})
public enableRecaptcha: boolean;
@Column('varchar', {
length: 1024,
nullable: true,
})
public DiscordWebhookUrl: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public DiscordWebhookUrlWordBlock: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public EmojiBotToken: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public recaptchaSiteKey: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public ApiBase: string | null;
@Column('varchar', {
length: 1024,
@ -417,6 +444,30 @@ export class MiMeta {
})
public objectStorageBaseUrl: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public bannerDark: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public bannerLight: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public iconDark: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public iconLight: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
@ -589,6 +640,27 @@ export class MiMeta {
})
public notesPerOneAd: number;
@Column('boolean', {
default: false,
})
public requestEmojiAllOk: boolean;
@Column('boolean', {
default: false,
})
public enableGDPRMode: boolean;
@Column('boolean', {
default: false,
})
public enableProxyCheckio: boolean;
@Column('varchar', {
length: 32,
nullable: true,
})
public proxyCheckioApiKey: string;
@Column('boolean', {
default: true,
})

View file

@ -14,6 +14,23 @@ import type { MiDriveFile } from './DriveFile.js';
export class MiNote {
@PrimaryColumn(id())
public id: string;
@Column('timestamp with time zone', {
default: null,
})
public updatedAt: Date | null;
@Column('timestamp with time zone', {
array: true,
default: null,
})
public updatedAtHistory: Date[] | null;
@Column('varchar', {
length: 3000,
array: true,
default: '{}',
})
public noteEditHistory: string[];
@Index()
@Column({

View file

@ -9,7 +9,8 @@ import { MiUser } from './User.js';
import { MiNote } from './Note.js';
@Entity('note_reaction')
@Index(['userId', 'noteId'], { unique: true })
@Index(['userId', 'noteId', 'reaction'], { unique: true })
@Index(['userId', 'noteId'])
export class MiNoteReaction {
@PrimaryColumn(id())
public id: string;

View file

@ -3,11 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { Provider } from '@nestjs/common';
import { Module } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import {
MiAbuseReportNotificationRecipient,
MiAbuseUserReport,
MiAccessToken,
MiAd,
@ -18,7 +18,6 @@ import {
MiAuthSession,
MiAvatarDecoration,
MiBlocking,
MiBubbleGameRecord,
MiChannel,
MiChannelFavorite,
MiChannelFollowing,
@ -28,10 +27,11 @@ import {
MiDriveFile,
MiDriveFolder,
MiEmoji,
MiEmojiRequest,
MiFlash,
MiFlashLike,
MiFollowing,
MiFollowRequest,
MiFollowing,
MiGalleryLike,
MiGalleryPost,
MiHashtag,
@ -51,19 +51,19 @@ import {
MiPollVote,
MiPromoNote,
MiPromoRead,
MiRegistrationTicket,
MiRegistryItem,
MiRelay,
MiRenoteMuting,
MiRepository,
miRepository,
MiRetentionAggregation,
MiRegistrationTicket,
MiRegistryItem,
MiReversiGame,
MiSystemWebhook,
MiRelay,
MiRenoteMuting,
MiRetentionAggregation,
MiRole,
MiRoleAssignment,
MiSignin,
MiSwSubscription,
MiSystemWebhook,
MiUsedUsername,
MiUser,
MiUserIp,
@ -77,8 +77,10 @@ import {
MiUserProfile,
MiUserPublickey,
MiUserSecurityKey,
MiWebhook
} from './_.js';
MiWebhook,
MiScheduledNote,
MiBubbleGameRecord } from './_.js';
import type { Provider } from '@nestjs/common';
import type { DataSource } from 'typeorm';
const $usersRepository: Provider = {
@ -93,6 +95,12 @@ const $notesRepository: Provider = {
inject: [DI.db],
};
const $scheduledNotesRepository: Provider = {
provide: DI.scheduledNotesRepository,
useFactory: (db: DataSource) => db.getRepository(MiScheduledNote),
inject: [DI.db],
};
const $announcementsRepository: Provider = {
provide: DI.announcementsRepository,
useFactory: (db: DataSource) => db.getRepository(MiAnnouncement).extend(miRepository as MiRepository<MiAnnouncement>),
@ -243,6 +251,12 @@ const $emojisRepository: Provider = {
inject: [DI.db],
};
const $emojiRequestsRepository: Provider = {
provide: DI.emojiRequestsRepository,
useFactory: (db: DataSource) => db.getRepository(MiEmojiRequest),
inject: [DI.db],
};
const $driveFilesRepository: Provider = {
provide: DI.driveFilesRepository,
useFactory: (db: DataSource) => db.getRepository(MiDriveFile).extend(miRepository as MiRepository<MiDriveFile>),
@ -500,6 +514,7 @@ const $reversiGamesRepository: Provider = {
providers: [
$usersRepository,
$notesRepository,
$scheduledNotesRepository,
$announcementsRepository,
$announcementReadsRepository,
$appsRepository,
@ -525,6 +540,7 @@ const $reversiGamesRepository: Provider = {
$followRequestsRepository,
$instancesRepository,
$emojisRepository,
$emojiRequestsRepository,
$driveFilesRepository,
$driveFoldersRepository,
$metasRepository,
@ -571,6 +587,7 @@ const $reversiGamesRepository: Provider = {
exports: [
$usersRepository,
$notesRepository,
$scheduledNotesRepository,
$announcementsRepository,
$announcementReadsRepository,
$appsRepository,
@ -596,6 +613,7 @@ const $reversiGamesRepository: Provider = {
$followRequestsRepository,
$instancesRepository,
$emojisRepository,
$emojiRequestsRepository,
$driveFilesRepository,
$driveFoldersRepository,
$metasRepository,

View file

@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm';
import type { MiNoteCreateOption } from '@/types.js';
import { id } from './util/id.js';
import { MiUser } from './User.js';
@Entity('note_schedule')
export class MiScheduledNote {
@PrimaryColumn(id())
public id: string;
@Column('jsonb')
public note: MiNoteCreateOption;
@Index()
@Column('varchar', {
length: 260,
})
public userId: MiUser['id'];
@Column('timestamp with time zone')
public scheduledAt: Date;
}

View file

@ -25,6 +25,11 @@ export class MiUser {
})
public lastFetchedAt: Date | null;
@Column('integer', {
default: '0',
})
public getPoints: number;
@Index()
@Column('timestamp with time zone', {
nullable: true,
@ -179,6 +184,12 @@ export class MiUser {
})
public isCat: boolean;
@Column('boolean', {
default: false,
comment: 'Whether the User is a gorilla.',
})
public isGorilla: boolean;
@Column('boolean', {
default: false,
comment: 'Whether the User is the root.',

View file

@ -29,6 +29,7 @@ import { MiClipFavorite } from '@/models/ClipFavorite.js';
import { MiDriveFile } from '@/models/DriveFile.js';
import { MiDriveFolder } from '@/models/DriveFolder.js';
import { MiEmoji } from '@/models/Emoji.js';
import { MiEmojiRequest } from '@/models/EmojiRequest.js';
import { MiFollowing } from '@/models/Following.js';
import { MiFollowRequest } from '@/models/FollowRequest.js';
import { MiGalleryLike } from '@/models/GalleryLike.js';
@ -80,6 +81,7 @@ import { MiUserListFavorite } from '@/models/UserListFavorite.js';
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
import { MiReversiGame } from '@/models/ReversiGame.js';
import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js';
import { MiScheduledNote } from './ScheduledNote.js';
export interface MiRepository<T extends ObjectLiteral> {
createTableColumnNames(this: Repository<T> & MiRepository<T>): string[];
@ -144,6 +146,7 @@ export {
MiDriveFile,
MiDriveFolder,
MiEmoji,
MiEmojiRequest,
MiFollowing,
MiFollowRequest,
MiGalleryLike,
@ -159,6 +162,7 @@ export {
MiNoteReaction,
MiNoteThreadMuting,
MiNoteUnread,
MiScheduledNote,
MiPage,
MiPageLike,
MiPasswordResetRequest,
@ -215,6 +219,7 @@ export type ClipFavoritesRepository = Repository<MiClipFavorite> & MiRepository<
export type DriveFilesRepository = Repository<MiDriveFile> & MiRepository<MiDriveFile>;
export type DriveFoldersRepository = Repository<MiDriveFolder> & MiRepository<MiDriveFolder>;
export type EmojisRepository = Repository<MiEmoji> & MiRepository<MiEmoji>;
export type EmojiRequestsRepository = Repository<MiEmojiRequest>;
export type FollowingsRepository = Repository<MiFollowing> & MiRepository<MiFollowing>;
export type FollowRequestsRepository = Repository<MiFollowRequest> & MiRepository<MiFollowRequest>;
export type GalleryLikesRepository = Repository<MiGalleryLike> & MiRepository<MiGalleryLike>;
@ -230,6 +235,7 @@ export type NoteFavoritesRepository = Repository<MiNoteFavorite> & MiRepository<
export type NoteReactionsRepository = Repository<MiNoteReaction> & MiRepository<MiNoteReaction>;
export type NoteThreadMutingsRepository = Repository<MiNoteThreadMuting> & MiRepository<MiNoteThreadMuting>;
export type NoteUnreadsRepository = Repository<MiNoteUnread> & MiRepository<MiNoteUnread>;
export type ScheduledNotesRepository = Repository<MiScheduledNote>;
export type PagesRepository = Repository<MiPage> & MiRepository<MiPage>;
export type PageLikesRepository = Repository<MiPageLike> & MiRepository<MiPageLike>;
export type PasswordResetRequestsRepository = Repository<MiPasswordResetRequest> & MiRepository<MiPasswordResetRequest>;

View file

@ -44,6 +44,40 @@ export const packedEmojiSimpleSchema = {
format: 'id',
},
},
draft: {
type: 'boolean',
optional: false, nullable: true,
},
},
} as const;
export const packedEmojiRequestSimpleSchema = {
type: 'object',
properties: {
aliases: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
name: {
type: 'string',
optional: false, nullable: false,
},
category: {
type: 'string',
optional: false, nullable: true,
},
url: {
type: 'string',
optional: false, nullable: false,
},
isSensitive: {
type: 'boolean',
optional: true, nullable: false,
},
},
} as const;
@ -85,6 +119,10 @@ export const packedEmojiDetailedSchema = {
type: 'string',
optional: false, nullable: true,
},
draft: {
type: 'boolean',
optional: false, nullable: true,
},
isSensitive: {
type: 'boolean',
optional: false, nullable: false,
@ -104,3 +142,51 @@ export const packedEmojiDetailedSchema = {
},
},
} as const;
export const packedEmojiRequestDetailedSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
aliases: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
name: {
type: 'string',
optional: false, nullable: false,
},
category: {
type: 'string',
optional: false, nullable: true,
},
url: {
type: 'string',
optional: false, nullable: false,
},
license: {
type: 'string',
optional: false, nullable: true,
},
isSensitive: {
type: 'boolean',
optional: false, nullable: false,
},
localOnly: {
type: 'boolean',
optional: false, nullable: false,
},
fileId: {
type: 'string',
optional: false, nullable: false,
},
},
} as const;

View file

@ -17,6 +17,24 @@ export const packedNoteSchema = {
optional: false, nullable: false,
format: 'date-time',
},
updatedAt: {
type: 'string',
optional: true, nullable: true,
format: 'date-time',
},
updatedAtHistory: {
type: 'array',
optional: true, nullable: true,
items: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
},
noteEditHistory: {
type: 'array',
optional: true, nullable: false,
},
deletedAt: {
type: 'string',
optional: true, nullable: true,

View file

@ -155,6 +155,14 @@ export const packedUserLiteSchema = {
onlineStatus: {
type: 'string',
nullable: false, optional: false,
isGorilla: {
type: 'boolean',
nullable: false, optional: true,
},
onlineStatus: {
type: 'string',
format: 'url',
nullable: true, optional: false,
enum: ['unknown', 'online', 'active', 'offline'],
},
badgeRoles: {
@ -180,7 +188,8 @@ export const packedUserLiteSchema = {
},
},
},
} as const;
}
} as const
export const packedUserDetailedNotMeOnlySchema = {
type: 'object',

View file

@ -28,6 +28,7 @@ import { MiClipFavorite } from '@/models/ClipFavorite.js';
import { MiDriveFile } from '@/models/DriveFile.js';
import { MiDriveFolder } from '@/models/DriveFolder.js';
import { MiEmoji } from '@/models/Emoji.js';
import { MiEmojiRequest } from '@/models/EmojiRequest.js';
import { MiFollowing } from '@/models/Following.js';
import { MiFollowRequest } from '@/models/FollowRequest.js';
import { MiGalleryLike } from '@/models/GalleryLike.js';
@ -76,6 +77,7 @@ import { MiRoleAssignment } from '@/models/RoleAssignment.js';
import { MiFlash } from '@/models/Flash.js';
import { MiFlashLike } from '@/models/FlashLike.js';
import { MiUserMemo } from '@/models/UserMemo.js';
import { MiScheduledNote } from '@/models/ScheduledNote.js';
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
import { MiReversiGame } from '@/models/ReversiGame.js';
@ -99,7 +101,7 @@ class MyCustomLogger implements Logger {
@bindThis
public logQuery(query: string, parameters?: any[]) {
sqlLogger.info(this.highlight(query).substring(0, 100));
sqlLogger.info(this.highlight(query));
}
@bindThis
@ -153,6 +155,7 @@ export const entities = [
MiRenoteMuting,
MiBlocking,
MiNote,
MiScheduledNote,
MiNoteFavorite,
MiNoteReaction,
MiNoteThreadMuting,
@ -166,6 +169,7 @@ export const entities = [
MiPoll,
MiPollVote,
MiEmoji,
MiEmojiRequest,
MiHashtag,
MiSwSubscription,
MiAbuseUserReport,

View file

@ -10,6 +10,7 @@ import { QueueLoggerService } from './QueueLoggerService.js';
import { QueueProcessorService } from './QueueProcessorService.js';
import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
import { ScheduleNotePostProcessorService } from './processors/ScheduleNotePostProcessorService.js';
import { InboxProcessorService } from './processors/InboxProcessorService.js';
import { UserWebhookDeliverProcessorService } from './processors/UserWebhookDeliverProcessorService.js';
import { SystemWebhookDeliverProcessorService } from './processors/SystemWebhookDeliverProcessorService.js';
@ -75,6 +76,7 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor
UserWebhookDeliverProcessorService,
SystemWebhookDeliverProcessorService,
EndedPollNotificationProcessorService,
ScheduleNotePostProcessorService,
DeliverProcessorService,
InboxProcessorService,
AggregateRetentionProcessorService,

View file

@ -13,6 +13,7 @@ import { bindThis } from '@/decorators.js';
import { UserWebhookDeliverProcessorService } from './processors/UserWebhookDeliverProcessorService.js';
import { SystemWebhookDeliverProcessorService } from './processors/SystemWebhookDeliverProcessorService.js';
import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
import { ScheduleNotePostProcessorService } from './processors/ScheduleNotePostProcessorService.js';
import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
import { InboxProcessorService } from './processors/InboxProcessorService.js';
import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js';
@ -82,6 +83,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
private relationshipQueueWorker: Bull.Worker;
private objectStorageQueueWorker: Bull.Worker;
private endedPollNotificationQueueWorker: Bull.Worker;
private schedulerNotePostQueueWorker: Bull.Worker;
constructor(
@Inject(DI.config)
@ -91,6 +93,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
private userWebhookDeliverProcessorService: UserWebhookDeliverProcessorService,
private systemWebhookDeliverProcessorService: SystemWebhookDeliverProcessorService,
private endedPollNotificationProcessorService: EndedPollNotificationProcessorService,
private scheduleNotePostProcessorService: ScheduleNotePostProcessorService,
private deliverProcessorService: DeliverProcessorService,
private inboxProcessorService: InboxProcessorService,
private deleteDriveFilesProcessorService: DeleteDriveFilesProcessorService,
@ -487,6 +490,13 @@ export class QueueProcessorService implements OnApplicationShutdown {
}
//#endregion
//#region schedule note post
this.schedulerNotePostQueueWorker = new Bull.Worker(QUEUE.SCHEDULE_NOTE_POST, (job) => this.scheduleNotePostProcessorService.process(job), {
...baseQueueOptions(this.config, QUEUE.SCHEDULE_NOTE_POST),
autorun: false,
});
//#endregion
//#region ended poll notification
{
this.endedPollNotificationQueueWorker = new Bull.Worker(QUEUE.ENDED_POLL_NOTIFICATION, (job) => {
@ -515,6 +525,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
this.relationshipQueueWorker.run(),
this.objectStorageQueueWorker.run(),
this.endedPollNotificationQueueWorker.run(),
this.schedulerNotePostQueueWorker.run(),
]);
}
@ -530,6 +541,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
this.relationshipQueueWorker.close(),
this.objectStorageQueueWorker.close(),
this.endedPollNotificationQueueWorker.close(),
this.schedulerNotePostQueueWorker.close(),
]);
}

View file

@ -11,6 +11,7 @@ export const QUEUE = {
INBOX: 'inbox',
SYSTEM: 'system',
ENDED_POLL_NOTIFICATION: 'endedPollNotification',
SCHEDULE_NOTE_POST: 'scheduleNotePost',
DB: 'db',
RELATIONSHIP: 'relationship',
OBJECT_STORAGE: 'objectStorage',

View file

@ -103,6 +103,7 @@ export class ImportCustomEmojisProcessorService {
isSensitive: emojiInfo.isSensitive,
localOnly: emojiInfo.localOnly,
roleIdsThatCanBeUsedThisEmojiAsReaction: [],
draft: false,
});
}

View file

@ -0,0 +1,46 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import type Logger from '@/logger.js';
import { bindThis } from '@/decorators.js';
import { NoteCreateService } from '@/core/NoteCreateService.js';
import type { ScheduledNotesRepository, UsersRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
import type { ScheduleNotePostJobData } from '../types.js';
@Injectable()
export class ScheduleNotePostProcessorService {
private logger: Logger;
constructor(
@Inject(DI.scheduledNotesRepository)
private scheduledNotesRepository: ScheduledNotesRepository,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
private noteCreateService: NoteCreateService,
private queueLoggerService: QueueLoggerService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('schedule-note-post');
}
@bindThis
public async process(job: Bull.Job<ScheduleNotePostJobData>): Promise<void> {
this.scheduledNotesRepository.findOneBy({ id: job.data.scheduledNoteId }).then(async (data) => {
if (!data) {
this.logger.warn(`Schedule note ${job.data.scheduledNoteId} not found`);
} else {
data.note.createdAt = new Date();
const me = await this.usersRepository.findOneByOrFail({ id: data.userId });
await this.noteCreateService.create(me, data.note);
await this.scheduledNotesRepository.remove(data);
}
});
}
}

View file

@ -6,9 +6,13 @@
import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js';
import type { MiDriveFile } from '@/models/DriveFile.js';
import type { MiNote } from '@/models/Note.js';
import type { MiUser } from '@/models/User.js';
import type { MiLocalUser, MiUser } from '@/models/User.js';
import type { MiWebhook } from '@/models/Webhook.js';
import type { IActivity } from '@/core/activitypub/type.js';
import { IPoll } from '@/models/Poll.js';
import { MiScheduledNote } from '@/models/ScheduledNote.js';
import { MiChannel } from '@/models/Channel.js';
import { MiApp } from '@/models/App.js';
import type httpSignature from '@peertube/http-signature';
export type DeliverJobData = {
@ -106,6 +110,17 @@ export type EndedPollNotificationJobData = {
noteId: MiNote['id'];
};
export type ScheduleNotePostJobData = {
scheduledNoteId: MiNote['id'];
}
type MinimumUser = {
id: MiUser['id'];
host: MiUser['host'];
username: MiUser['username'];
uri: MiUser['uri'];
};
export type SystemWebhookDeliverJobData = {
type: string;
content: unknown;

View file

@ -342,7 +342,8 @@ export class FileServerService {
'avatar' in request.query ||
'static' in request.query ||
'preview' in request.query ||
'badge' in request.query
'badge' in request.query ||
'datasaver' in request.query
) {
if (!isConvertibleImage) {
// 画像でないなら404でお茶を濁す
@ -361,7 +362,7 @@ export class FileServerService {
} else {
const data = (await sharpBmp(file.path, file.mime, { animated: !('static' in request.query) }))
.resize({
height: 'emoji' in request.query ? 128 : 320,
height: 'emoji' in request.query ? 64 : 128,
withoutEnlargement: true,
})
.webp(webpDefault);
@ -407,7 +408,28 @@ export class FileServerService {
ext: 'png',
type: 'image/png',
};
} else if (file.mime === 'image/svg+xml') {
} else if ('datasaver' in request.query){
if (!isAnimationConvertibleImage && !('static' in request.query)) {
image = {
data: fs.createReadStream(file.path),
ext: file.ext,
type: file.mime,
};
} else {
const data = (await sharpBmp(file.path, file.mime, { animated: !('static' in request.query) }))
.resize({
height: 32,
withoutEnlargement: true,
})
.webp(webpDefault);
image = {
data,
ext: 'webp',
type: 'image/webp',
};
}
}else if (file.mime === 'image/svg+xml') {
image = this.imageProcessingService.convertToWebpStream(file.path, 2048, 2048);
} else if (!file.mime.startsWith('image/') || !FILE_TYPE_BROWSERSAFE.includes(file.mime)) {
throw new StatusError('Rejected type', 403, 'Rejected type');

View file

@ -6,6 +6,7 @@
import { Module } from '@nestjs/common';
import { CoreModule } from '@/core/CoreModule.js';
import * as ep___users_lists_list_favorite from '@/server/api/endpoints/users/lists/list-favorite.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';
@ -36,18 +37,23 @@ 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_setlocalOnlyBulk from './endpoints/admin/emoji/set-localonly-bulk.js';
import * as ep___admin_emoji_setisSensitiveBulk from './endpoints/admin/emoji/set-issensitive-bulk.js';
import * as ep___admin_emoji_add from './endpoints/admin/emoji/add.js';
import * as ep___admin_emoji_addRequest from './endpoints/admin/emoji/add-request.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_listRequest from './endpoints/admin/emoji/list-request.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_emoji_updateRequest from './endpoints/admin/emoji/update-request.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';
@ -264,6 +270,7 @@ 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___emojiRequests from './endpoints/emoji-requests.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';
@ -278,13 +285,17 @@ 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_schedule_delete from './endpoints/notes/schedule/delete.js';
import * as ep___notes_schedule_list from './endpoints/notes/schedule/list.js';
import * as ep___notes_delete from './endpoints/notes/delete.js';
import * as ep___notes_update from './endpoints/notes/update.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_globalTimeline from './endpoints/notes/global-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_anyLocalTimeline from './endpoints/notes/any-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';
@ -349,6 +360,7 @@ 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_user_stats from './endpoints/users/stats.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';
@ -387,8 +399,9 @@ import * as ep___reversi_surrender from './endpoints/reversi/surrender.js';
import * as ep___reversi_verify from './endpoints/reversi/verify.js';
import { GetterService } from './GetterService.js';
import { ApiLoggerService } from './ApiLoggerService.js';
import * as ep___admin_accounts_present_points from './endpoints/admin/accounts/present-points.js';
import type { Provider } from '@nestjs/common';
import * as ep___emoji_speedtest from './endpoints/admin/emoji/speedtest.js';
const $admin_meta: Provider = { provide: 'ep:admin/meta', useClass: ep___admin_meta.default };
const $admin_abuseUserReports: Provider = { provide: 'ep:admin/abuse-user-reports', useClass: ep___admin_abuseUserReports.default };
const $admin_abuseReport_notificationRecipient_list: Provider = { provide: 'ep:admin/abuse-report/notification-recipient/list', useClass: ep___admin_abuseReport_notificationRecipient_list.default };
@ -399,6 +412,7 @@ const $admin_abuseReport_notificationRecipient_delete: Provider = { provide: 'ep
const $admin_accounts_create: Provider = { provide: 'ep:admin/accounts/create', useClass: ep___admin_accounts_create.default };
const $admin_accounts_delete: Provider = { provide: 'ep:admin/accounts/delete', useClass: ep___admin_accounts_delete.default };
const $admin_accounts_findByEmail: Provider = { provide: 'ep:admin/accounts/find-by-email', useClass: ep___admin_accounts_findByEmail.default };
const $admin_accounts_present_points: Provider = { provide: 'ep:admin/accounts/present-points', useClass: ep___admin_accounts_present_points.default };
const $admin_ad_create: Provider = { provide: 'ep:admin/ad/create', useClass: ep___admin_ad_create.default };
const $admin_ad_delete: Provider = { provide: 'ep:admin/ad/delete', useClass: ep___admin_ad_delete.default };
const $admin_ad_list: Provider = { provide: 'ep:admin/ad/list', useClass: ep___admin_ad_list.default };
@ -414,23 +428,29 @@ const $admin_avatarDecorations_update: Provider = { provide: 'ep:admin/avatar-de
const $admin_deleteAllFilesOfAUser: Provider = { provide: 'ep:admin/delete-all-files-of-a-user', useClass: ep___admin_deleteAllFilesOfAUser.default };
const $admin_unsetUserAvatar: Provider = { provide: 'ep:admin/unset-user-avatar', useClass: ep___admin_unsetUserAvatar.default };
const $admin_unsetUserBanner: Provider = { provide: 'ep:admin/unset-user-banner', useClass: ep___admin_unsetUserBanner.default };
const $emoji_speedtest: Provider = { provide: 'ep:emoji/speedtest', useClass: ep___emoji_speedtest.default };
const $admin_drive_cleanRemoteFiles: Provider = { provide: 'ep:admin/drive/clean-remote-files', useClass: ep___admin_drive_cleanRemoteFiles.default };
const $admin_drive_cleanup: Provider = { provide: 'ep:admin/drive/cleanup', useClass: ep___admin_drive_cleanup.default };
const $admin_drive_files: Provider = { provide: 'ep:admin/drive/files', useClass: ep___admin_drive_files.default };
const $admin_drive_showFile: Provider = { provide: 'ep:admin/drive/show-file', useClass: ep___admin_drive_showFile.default };
const $admin_emoji_addAliasesBulk: Provider = { provide: 'ep:admin/emoji/add-aliases-bulk', useClass: ep___admin_emoji_addAliasesBulk.default };
const $admin_emoji_setlocalOnlyBulk: Provider = { provide: 'ep:admin/emoji/set-localonly-bulk', useClass: ep___admin_emoji_setlocalOnlyBulk.default };
const $admin_emoji_setisSensitiveBulk: Provider = { provide: 'ep:admin/emoji/set-issensitive-bulk', useClass: ep___admin_emoji_setisSensitiveBulk.default };
const $admin_emoji_add: Provider = { provide: 'ep:admin/emoji/add', useClass: ep___admin_emoji_add.default };
const $admin_emoji_addRequest: Provider = { provide: 'ep:admin/emoji/add-request', useClass: ep___admin_emoji_addRequest.default };
const $admin_emoji_copy: Provider = { provide: 'ep:admin/emoji/copy', useClass: ep___admin_emoji_copy.default };
const $admin_emoji_deleteBulk: Provider = { provide: 'ep:admin/emoji/delete-bulk', useClass: ep___admin_emoji_deleteBulk.default };
const $admin_emoji_delete: Provider = { provide: 'ep:admin/emoji/delete', useClass: ep___admin_emoji_delete.default };
const $admin_emoji_importZip: Provider = { provide: 'ep:admin/emoji/import-zip', useClass: ep___admin_emoji_importZip.default };
const $admin_emoji_listRemote: Provider = { provide: 'ep:admin/emoji/list-remote', useClass: ep___admin_emoji_listRemote.default };
const $admin_emoji_list: Provider = { provide: 'ep:admin/emoji/list', useClass: ep___admin_emoji_list.default };
const $admin_emoji_listRequest: Provider = { provide: 'ep:admin/emoji/list-request', useClass: ep___admin_emoji_listRequest.default };
const $admin_emoji_removeAliasesBulk: Provider = { provide: 'ep:admin/emoji/remove-aliases-bulk', useClass: ep___admin_emoji_removeAliasesBulk.default };
const $admin_emoji_setAliasesBulk: Provider = { provide: 'ep:admin/emoji/set-aliases-bulk', useClass: ep___admin_emoji_setAliasesBulk.default };
const $admin_emoji_setCategoryBulk: Provider = { provide: 'ep:admin/emoji/set-category-bulk', useClass: ep___admin_emoji_setCategoryBulk.default };
const $admin_emoji_setLicenseBulk: Provider = { provide: 'ep:admin/emoji/set-license-bulk', useClass: ep___admin_emoji_setLicenseBulk.default };
const $admin_emoji_update: Provider = { provide: 'ep:admin/emoji/update', useClass: ep___admin_emoji_update.default };
const $admin_emoji_updateRequest: Provider = { provide: 'ep:admin/emoji/update-request', useClass: ep___admin_emoji_updateRequest.default };
const $admin_federation_deleteAllFiles: Provider = { provide: 'ep:admin/federation/delete-all-files', useClass: ep___admin_federation_deleteAllFiles.default };
const $admin_federation_refreshRemoteInstanceMetadata: Provider = { provide: 'ep:admin/federation/refresh-remote-instance-metadata', useClass: ep___admin_federation_refreshRemoteInstanceMetadata.default };
const $admin_federation_removeAllFollowing: Provider = { provide: 'ep:admin/federation/remove-all-following', useClass: ep___admin_federation_removeAllFollowing.default };
@ -615,6 +635,7 @@ const $i_importMuting: Provider = { provide: 'ep:i/import-muting', useClass: ep_
const $i_importUserLists: Provider = { provide: 'ep:i/import-user-lists', useClass: ep___i_importUserLists.default };
const $i_importAntennas: Provider = { provide: 'ep:i/import-antennas', useClass: ep___i_importAntennas.default };
const $i_notifications: Provider = { provide: 'ep:i/notifications', useClass: ep___i_notifications.default };
const $i_userstats: Provider = { provide: 'ep:i/stats', useClass: ep___users_user_stats.default };
const $i_notificationsGrouped: Provider = { provide: 'ep:i/notifications-grouped', useClass: ep___i_notificationsGrouped.default };
const $i_pageLikes: Provider = { provide: 'ep:i/page-likes', useClass: ep___i_pageLikes.default };
const $i_pages: Provider = { provide: 'ep:i/pages', useClass: ep___i_pages.default };
@ -647,6 +668,7 @@ const $invite_list: Provider = { provide: 'ep:invite/list', useClass: ep___invit
const $invite_limit: Provider = { provide: 'ep:invite/limit', useClass: ep___invite_limit.default };
const $meta: Provider = { provide: 'ep:meta', useClass: ep___meta.default };
const $emojis: Provider = { provide: 'ep:emojis', useClass: ep___emojis.default };
const $emoji_requests: Provider = { provide: 'ep:emoji-requests', useClass: ep___emojiRequests.default };
const $emoji: Provider = { provide: 'ep:emoji', useClass: ep___emoji.default };
const $miauth_genToken: Provider = { provide: 'ep:miauth/gen-token', useClass: ep___miauth_genToken.default };
const $mute_create: Provider = { provide: 'ep:mute/create', useClass: ep___mute_create.default };
@ -661,13 +683,17 @@ const $notes_children: Provider = { provide: 'ep:notes/children', useClass: ep__
const $notes_clips: Provider = { provide: 'ep:notes/clips', useClass: ep___notes_clips.default };
const $notes_conversation: Provider = { provide: 'ep:notes/conversation', useClass: ep___notes_conversation.default };
const $notes_create: Provider = { provide: 'ep:notes/create', useClass: ep___notes_create.default };
const $notes_schedule_delete: Provider = { provide: 'ep:notes/schedule/delete', useClass: ep___notes_schedule_delete.default };
const $notes_schedule_list: Provider = { provide: 'ep:notes/schedule/list', useClass: ep___notes_schedule_list.default };
const $notes_delete: Provider = { provide: 'ep:notes/delete', useClass: ep___notes_delete.default };
const $notes_update: Provider = { provide: 'ep:notes/update', useClass: ep___notes_update.default };
const $notes_favorites_create: Provider = { provide: 'ep:notes/favorites/create', useClass: ep___notes_favorites_create.default };
const $notes_favorites_delete: Provider = { provide: 'ep:notes/favorites/delete', useClass: ep___notes_favorites_delete.default };
const $notes_featured: Provider = { provide: 'ep:notes/featured', useClass: ep___notes_featured.default };
const $notes_globalTimeline: Provider = { provide: 'ep:notes/global-timeline', useClass: ep___notes_globalTimeline.default };
const $notes_hybridTimeline: Provider = { provide: 'ep:notes/hybrid-timeline', useClass: ep___notes_hybridTimeline.default };
const $notes_localTimeline: Provider = { provide: 'ep:notes/local-timeline', useClass: ep___notes_localTimeline.default };
const $notes_anyLocalTimeline: Provider = { provide: 'ep:notes/any-local-timeline', useClass: ep___notes_anyLocalTimeline.default };
const $notes_mentions: Provider = { provide: 'ep:notes/mentions', useClass: ep___notes_mentions.default };
const $notes_polls_recommendation: Provider = { provide: 'ep:notes/polls/recommendation', useClass: ep___notes_polls_recommendation.default };
const $notes_polls_vote: Provider = { provide: 'ep:notes/polls/vote', useClass: ep___notes_polls_vote.default };
@ -735,6 +761,7 @@ const $users_featuredNotes: Provider = { provide: 'ep:users/featured-notes', use
const $users_lists_create: Provider = { provide: 'ep:users/lists/create', useClass: ep___users_lists_create.default };
const $users_lists_delete: Provider = { provide: 'ep:users/lists/delete', useClass: ep___users_lists_delete.default };
const $users_lists_list: Provider = { provide: 'ep:users/lists/list', useClass: ep___users_lists_list.default };
const $users_lists_list_favorite: Provider = { provide: 'ep:users/lists/list-favorite', useClass: ep___users_lists_list_favorite.default };
const $users_lists_pull: Provider = { provide: 'ep:users/lists/pull', useClass: ep___users_lists_pull.default };
const $users_lists_push: Provider = { provide: 'ep:users/lists/push', useClass: ep___users_lists_push.default };
const $users_lists_show: Provider = { provide: 'ep:users/lists/show', useClass: ep___users_lists_show.default };
@ -786,6 +813,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$admin_accounts_create,
$admin_accounts_delete,
$admin_accounts_findByEmail,
$admin_accounts_present_points,
$admin_ad_create,
$admin_ad_delete,
$admin_ad_list,
@ -800,24 +828,30 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$admin_avatarDecorations_update,
$admin_deleteAllFilesOfAUser,
$admin_unsetUserAvatar,
$emoji_speedtest,
$admin_unsetUserBanner,
$admin_drive_cleanRemoteFiles,
$admin_drive_cleanup,
$admin_drive_files,
$admin_drive_showFile,
$admin_emoji_addAliasesBulk,
$admin_emoji_setlocalOnlyBulk,
$admin_emoji_setisSensitiveBulk,
$admin_emoji_add,
$admin_emoji_addRequest,
$admin_emoji_copy,
$admin_emoji_deleteBulk,
$admin_emoji_delete,
$admin_emoji_importZip,
$admin_emoji_listRemote,
$admin_emoji_list,
$admin_emoji_listRequest,
$admin_emoji_removeAliasesBulk,
$admin_emoji_setAliasesBulk,
$admin_emoji_setCategoryBulk,
$admin_emoji_setLicenseBulk,
$admin_emoji_update,
$admin_emoji_updateRequest,
$admin_federation_deleteAllFiles,
$admin_federation_refreshRemoteInstanceMetadata,
$admin_federation_removeAllFollowing,
@ -890,6 +924,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$channels_timeline,
$channels_unfollow,
$channels_update,
$i_userstats,
$channels_favorite,
$channels_unfavorite,
$channels_myFavorites,
@ -1034,6 +1069,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$invite_limit,
$meta,
$emojis,
$emoji_requests,
$emoji,
$miauth_genToken,
$mute_create,
@ -1048,13 +1084,17 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$notes_clips,
$notes_conversation,
$notes_create,
$notes_schedule_delete,
$notes_schedule_list,
$notes_delete,
$notes_update,
$notes_favorites_create,
$notes_favorites_delete,
$notes_featured,
$notes_globalTimeline,
$notes_hybridTimeline,
$notes_localTimeline,
$notes_anyLocalTimeline,
$notes_mentions,
$notes_polls_recommendation,
$notes_polls_vote,
@ -1122,6 +1162,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$users_lists_create,
$users_lists_delete,
$users_lists_list,
$users_lists_list_favorite,
$users_lists_pull,
$users_lists_push,
$users_lists_show,
@ -1167,6 +1208,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$admin_accounts_create,
$admin_accounts_delete,
$admin_accounts_findByEmail,
$admin_accounts_present_points,
$admin_ad_create,
$admin_ad_delete,
$admin_ad_list,
@ -1182,24 +1224,31 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$admin_deleteAllFilesOfAUser,
$admin_unsetUserAvatar,
$admin_unsetUserBanner,
$emoji_speedtest,
$admin_drive_cleanRemoteFiles,
$admin_drive_cleanup,
$admin_drive_files,
$admin_drive_showFile,
$admin_emoji_addAliasesBulk,
$admin_emoji_add,
$admin_emoji_addRequest,
$admin_emoji_copy,
$admin_emoji_deleteBulk,
$admin_emoji_delete,
$admin_emoji_importZip,
$admin_emoji_listRemote,
$admin_emoji_list,
$admin_emoji_listRequest,
$admin_emoji_removeAliasesBulk,
$admin_emoji_setAliasesBulk,
$admin_emoji_setCategoryBulk,
$admin_emoji_setLicenseBulk,
$admin_emoji_setlocalOnlyBulk,
$admin_emoji_setisSensitiveBulk,
$admin_emoji_update,
$admin_emoji_updateRequest,
$admin_federation_deleteAllFiles,
$i_userstats,
$admin_federation_refreshRemoteInstanceMetadata,
$admin_federation_removeAllFollowing,
$admin_federation_updateInstance,
@ -1415,6 +1464,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$invite_limit,
$meta,
$emojis,
$emoji_requests,
$emoji,
$miauth_genToken,
$mute_create,
@ -1429,13 +1479,17 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$notes_clips,
$notes_conversation,
$notes_create,
$notes_schedule_delete,
$notes_schedule_list,
$notes_delete,
$notes_update,
$notes_favorites_create,
$notes_favorites_delete,
$notes_featured,
$notes_globalTimeline,
$notes_hybridTimeline,
$notes_localTimeline,
$notes_anyLocalTimeline,
$notes_mentions,
$notes_polls_recommendation,
$notes_polls_vote,
@ -1501,6 +1555,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$users_lists_create,
$users_lists_delete,
$users_lists_list,
$users_lists_list_favorite,
$users_lists_pull,
$users_lists_push,
$users_lists_show,

View file

@ -6,6 +6,7 @@
import { Inject, Injectable } from '@nestjs/common';
import bcrypt from 'bcryptjs';
import { IsNull } from 'typeorm';
import ProxyCheck from 'proxycheck-ts';
import { DI } from '@/di-symbols.js';
import type { RegistrationTicketsRepository, UsedUsernamesRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository, MiRegistrationTicket } from '@/models/_.js';
import type { Config } from '@/config.js';
@ -108,6 +109,24 @@ export class SignupApiService {
const invitationCode = body['invitationCode'];
const emailAddress = body['emailAddress'];
const { DiscordWebhookUrl } = (await this.metaService.fetch());
if (DiscordWebhookUrl) {
const data_disc = { 'username': 'ユーザー登録お知らせ',
'content':
'ユーザー名 :' + username + '\n' +
'メールアドレス : ' + emailAddress + '\n' +
'IPアドレス : ' + request.headers['x-real-ip'] ?? request.ip,
};
await fetch(DiscordWebhookUrl, {
'method': 'post',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data_disc),
});
}
if (instance.emailRequiredForSignup) {
if (emailAddress == null || typeof emailAddress !== 'string') {
reply.code(400);

View file

@ -5,7 +5,9 @@
import { permissions } from 'misskey-js';
import type { KeyOf, Schema } from '@/misc/json-schema.js';
import { RolePolicies } from '@/core/RoleService.js';
import * as ep___admin_emoji_setlocalOnlyBulk from './endpoints/admin/emoji/set-localonly-bulk.js';
import * as ep___admin_emoji_setisSensitiveBulk from './endpoints/admin/emoji/set-issensitive-bulk.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
@ -21,6 +23,8 @@ 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_accounts_present_points from './endpoints/admin/accounts/present-points.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';
@ -42,17 +46,21 @@ 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_addRequest from './endpoints/admin/emoji/add-request.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_listRequest from './endpoints/admin/emoji/list-request.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_emoji_updateRequest from './endpoints/admin/emoji/update-request.js';
import * as ep___emoji_speedtest from './endpoints/admin/emoji/speedtest.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';
@ -264,12 +272,14 @@ 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_user_stats from './endpoints/users/stats.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___emojiRequests from './endpoints/emoji-requests.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';
@ -284,13 +294,17 @@ 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_schedule_delete from './endpoints/notes/schedule/delete.js';
import * as ep___notes_schedule_list from './endpoints/notes/schedule/list.js';
import * as ep___notes_delete from './endpoints/notes/delete.js';
import * as ep___notes_update from './endpoints/notes/update.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_globalTimeline from './endpoints/notes/global-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_anyLocalTimeline from './endpoints/notes/any-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';
@ -358,6 +372,7 @@ 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_list_favorite from './endpoints/users/lists/list-favorite.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';
@ -391,7 +406,6 @@ 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],
@ -403,6 +417,7 @@ const eps = [
['admin/accounts/create', ep___admin_accounts_create],
['admin/accounts/delete', ep___admin_accounts_delete],
['admin/accounts/find-by-email', ep___admin_accounts_findByEmail],
['admin/accounts/present-points', ep___admin_accounts_present_points],
['admin/ad/create', ep___admin_ad_create],
['admin/ad/delete', ep___admin_ad_delete],
['admin/ad/list', ep___admin_ad_list],
@ -424,17 +439,23 @@ const eps = [
['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/add-request', ep___admin_emoji_addRequest],
['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/list-request', ep___admin_emoji_listRequest],
['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-localonly-bulk', ep___admin_emoji_setlocalOnlyBulk],
['admin/emoji/set-issensitive-bulk', ep___admin_emoji_setisSensitiveBulk],
['admin/emoji/set-license-bulk', ep___admin_emoji_setLicenseBulk],
['admin/emoji/update', ep___admin_emoji_update],
['admin/emoji/update-request', ep___admin_emoji_updateRequest],
['emoji/speedtest', ep___emoji_speedtest],
['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],
@ -645,12 +666,14 @@ const eps = [
['i/webhooks/show', ep___i_webhooks_show],
['i/webhooks/update', ep___i_webhooks_update],
['i/webhooks/delete', ep___i_webhooks_delete],
['i/stats', ep___i_user_stats],
['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-requests', ep___emojiRequests],
['emoji', ep___emoji],
['miauth/gen-token', ep___miauth_genToken],
['mute/create', ep___mute_create],
@ -665,13 +688,17 @@ const eps = [
['notes/clips', ep___notes_clips],
['notes/conversation', ep___notes_conversation],
['notes/create', ep___notes_create],
['notes/schedule/delete', ep___notes_schedule_delete],
['notes/schedule/list', ep___notes_schedule_list],
['notes/delete', ep___notes_delete],
['notes/update', ep___notes_update],
['notes/favorites/create', ep___notes_favorites_create],
['notes/favorites/delete', ep___notes_favorites_delete],
['notes/featured', ep___notes_featured],
['notes/global-timeline', ep___notes_globalTimeline],
['notes/hybrid-timeline', ep___notes_hybridTimeline],
['notes/local-timeline', ep___notes_localTimeline],
['notes/any-local-timeline', ep___notes_anyLocalTimeline],
['notes/mentions', ep___notes_mentions],
['notes/polls/recommendation', ep___notes_polls_recommendation],
['notes/polls/vote', ep___notes_polls_vote],
@ -739,6 +766,7 @@ const eps = [
['users/lists/create', ep___users_lists_create],
['users/lists/delete', ep___users_lists_delete],
['users/lists/list', ep___users_lists_list],
['users/lists/list-favorite', ep___users_lists_list_favorite],
['users/lists/pull', ep___users_lists_pull],
['users/lists/push', ep___users_lists_push],
['users/lists/show', ep___users_lists_show],

View file

@ -0,0 +1,52 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* 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 { NotificationService } from '@/core/NotificationService.js';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireAdmin: true,
kind: 'write:admin:account',
} as const;
export const paramDef = {
type: 'object',
properties: {
userId: { type: 'string', format: 'misskey:id' },
points: { type: 'number' },
},
required: ['userId', 'points'],
} as const;
@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 notificationService: NotificationService,
) {
super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy({ id: ps.userId });
if (user == null) {
throw new Error('user not found');
}
this.usersRepository.update( user.id, {
getPoints: user.getPoints + ps.points,
});
this.notificationService.createNotification(user.id, 'loginbonus', {
loginbonus: ps.points,
});
return {};
});
}
}

View file

@ -24,6 +24,7 @@ export const paramDef = {
roleIdsThatCanBeUsedThisDecoration: { type: 'array', items: {
type: 'string',
} },
category: { type: 'string', nullable: true },
},
required: ['name', 'description', 'url'],
} as const;
@ -39,6 +40,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
description: ps.description,
url: ps.url,
roleIdsThatCanBeUsedThisDecoration: ps.roleIdsThatCanBeUsedThisDecoration,
category: ps.category ?? '',
}, me);
});
}

View file

@ -95,6 +95,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
name: avatarDecoration.name,
description: avatarDecoration.description,
url: avatarDecoration.url,
category: avatarDecoration.category,
roleIdsThatCanBeUsedThisDecoration: avatarDecoration.roleIdsThatCanBeUsedThisDecoration,
}));
});

View file

@ -30,6 +30,7 @@ export const paramDef = {
roleIdsThatCanBeUsedThisDecoration: { type: 'array', items: {
type: 'string',
} },
category: { type: 'string', nullable: true },
},
required: ['id'],
} as const;
@ -45,6 +46,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
description: ps.description,
url: ps.url,
roleIdsThatCanBeUsedThisDecoration: ps.roleIdsThatCanBeUsedThisDecoration,
category: ps.category ?? '',
}, me);
});
}

View file

@ -0,0 +1,154 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { DriveFilesRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { ApiError } from '../../../error.js';
import {MetaService} from "@/core/MetaService.js";
import {DriveService} from "@/core/DriveService.js";
export const meta = {
tags: ['admin'],
requireCredential: true,
requireRolePolicy: 'canRequestCustomEmojis',
errors: {
noSuchFile: {
message: 'No such file.',
code: 'NO_SUCH_FILE',
id: 'fc46b5a4-6b92-4c33-ac66-b806659bb5cf',
},
duplicateName: {
message: 'Duplicate name.',
code: 'DUPLICATE_NAME',
id: 'f7a3462c-4e6e-4069-8421-b9bd4f4c3975',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' },
category: {
type: 'string',
nullable: true,
description: 'Use `null` to reset the category.',
},
aliases: { type: 'array', items: {
type: 'string',
} },
license: { type: 'string', nullable: true },
isSensitive: { type: 'boolean', nullable: true },
localOnly: { type: 'boolean', nullable: true },
fileId: { type: 'string', format: 'misskey:id' },
isNotifyIsHome: { type: 'boolean', nullable: true },
},
required: ['name', 'fileId'],
} as const;
// TODO: ロジックをサービスに切り出す
@Injectable()
// eslint-disable-next-line import/no-default-export
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
private metaService: MetaService,
private customEmojiService: CustomEmojiService,
private driveService: DriveService,
private moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
const isDuplicate = await this.customEmojiService.checkDuplicate(ps.name);
const isRequestDuplicate = await this.customEmojiService.checkRequestDuplicate(ps.name);
if (isDuplicate || isRequestDuplicate) throw new ApiError(meta.errors.duplicateName);
let driveFile;
let tmp = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
if (tmp == null) throw new ApiError(meta.errors.noSuchFile);
try {
driveFile = await this.driveService.uploadFromUrl({ url: tmp.url , user: null, force: true });
} catch (e) {
throw new ApiError();
}
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
const {ApiBase,EmojiBotToken,DiscordWebhookUrl,requestEmojiAllOk} = (await this.metaService.fetch())
let emoji;
if (requestEmojiAllOk){
emoji = await this.customEmojiService.add({
driveFile,
name: ps.name,
category: ps.category ?? null,
aliases: ps.aliases ?? [],
license: ps.license ?? null,
host: null,
isSensitive: ps.isSensitive ?? false,
localOnly: ps.localOnly ?? false,
roleIdsThatCanBeUsedThisEmojiAsReaction: [],
});
}else{
emoji = await this.customEmojiService.request({
driveFile,
name: ps.name,
category: ps.category ?? null,
aliases: ps.aliases ?? [],
license: ps.license ?? null,
isSensitive: ps.isSensitive ?? false,
localOnly: ps.localOnly ?? false,
});
}
await this.moderationLogService.log(me, 'addCustomEmoji', {
emojiId: emoji.id,
emoji: emoji,
});
if (EmojiBotToken){
const data_Miss = {
'i': EmojiBotToken,
'visibility': ps.isNotifyIsHome ? 'home' : 'public',
'text':
'絵文字名 : :' + ps.name + ':\n' +
'カテゴリ : ' + ps.category + '\n' +
'ライセンス : ' + ps.license + '\n' +
'タグ : ' + ps.aliases + '\n' +
'追加したユーザー : ' + '@' + me.username + '\n'
};
await fetch(ApiBase+'/notes/create', {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body:JSON.stringify( data_Miss)
})
}
if (DiscordWebhookUrl){
const data_disc = {"username": "絵文字追加通知ちゃん",
'content':
'絵文字名 : :'+ ps.name +':\n' +
'カテゴリ : ' + ps.category + '\n'+
'ライセンス : '+ ps.license + '\n'+
'タグ : '+ps.aliases+ '\n'+
'追加したユーザー : ' + '@'+me.username + '\n'
}
await fetch(DiscordWebhookUrl, {
'method':'post',
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data_disc),
})
}
return {
id: emoji.id,
};
});
}
}

View file

@ -47,15 +47,19 @@ 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,13 +71,12 @@ 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) => {
const driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
const isDuplicate = await this.customEmojiService.checkDuplicate(ps.name);
if (isDuplicate) throw new ApiError(meta.errors.duplicateName);

View file

@ -37,7 +37,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private customEmojiService: CustomEmojiService,
) {
super(meta, paramDef, async (ps, me) => {
await this.customEmojiService.delete(ps.id, me);
const emoji = await this.customEmojiService.getEmojiById(ps.id);
const RequestEmoji = await this.customEmojiService.getEmojiRequestById(ps.id);
if (emoji != null) {
await this.customEmojiService.delete(ps.id, me);
}
if (RequestEmoji != null) {
await this.customEmojiService.deleteRequest(ps.id);
}
});
}
}

View file

@ -0,0 +1,108 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { EmojiRequestsRepository } from '@/models/_.js';
import type { MiEmojiRequest } from '@/models/EmojiRequest.js';
import { QueryService } from '@/core/QueryService.js';
import { DI } from '@/di-symbols.js';
import { EmojiRequestsEntityService } from '@/core/entities/EmojiRequestsEntityService.js';
//import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireRolePolicy: 'canManageCustomEmojis',
res: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
aliases: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
},
},
name: {
type: 'string',
optional: false, nullable: false,
},
category: {
type: 'string',
optional: false, nullable: true,
},
url: {
type: 'string',
optional: false, nullable: false,
},
},
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
query: { type: 'string', nullable: true, default: null },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
},
required: [],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.emojiRequestsRepository)
private emojiRequestsRepository: EmojiRequestsRepository,
private emojiRequestsEntityService: EmojiRequestsEntityService,
private queryService: QueryService,
) {
super(meta, paramDef, async (ps, me) => {
const q = this.queryService.makePaginationQuery(this.emojiRequestsRepository.createQueryBuilder('emoji'), ps.sinceId, ps.untilId);
let emojis: MiEmojiRequest[];
if (ps.query) {
//q.andWhere('emoji.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` });
//const emojis = await q.limit(ps.limit).getMany();
emojis = await q.getMany();
const queryarry = ps.query.match(/\:([a-z0-9_]*)\:/g);
if (queryarry) {
emojis = emojis.filter(emoji =>
queryarry.includes(`:${emoji.name}:`),
);
} else {
emojis = emojis.filter(emoji =>
emoji.name.includes(ps.query!) ||
emoji.aliases.some(a => a.includes(ps.query!)) ||
emoji.category?.includes(ps.query!));
}
emojis.splice(ps.limit + 1);
} else {
emojis = await q.limit(ps.limit).getMany();
}
return this.emojiRequestsEntityService.packDetailedMany(emojis);
});
}
}

View file

@ -65,6 +65,7 @@ export const paramDef = {
type: 'object',
properties: {
query: { type: 'string', nullable: true, default: null },
draft: { type: 'boolean', nullable: true, default: null },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
@ -87,6 +88,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
let emojis: MiEmoji[];
if (ps.draft !== null) {
if (ps.draft) {
q.andWhere('emoji.draft = TRUE');
} else {
q.andWhere('emoji.draft = FALSE');
}
}
if (ps.query) {
//q.andWhere('emoji.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` });
//const emojis = await q.limit(ps.limit).getMany();

View file

@ -0,0 +1,41 @@
/*
* 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 { CustomEmojiService } from '@/core/CustomEmojiService.js';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireRolePolicy: 'canManageCustomEmojis',
} as const;
export const paramDef = {
type: 'object',
properties: {
ids: { type: 'array', items: {
type: 'string', format: 'misskey:id',
} },
isSensitive: {
type: 'boolean',
nullable: false,
description: 'Use `null` to reset the licence.',
},
},
required: ['ids'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private customEmojiService: CustomEmojiService,
) {
super(meta, paramDef, async (ps, me) => {
await this.customEmojiService.setisSensitiveBulk(ps.ids, ps.isSensitive ?? false);
});
}
}

View file

@ -0,0 +1,41 @@
/*
* 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 { CustomEmojiService } from '@/core/CustomEmojiService.js';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireRolePolicy: 'canManageCustomEmojis',
} as const;
export const paramDef = {
type: 'object',
properties: {
ids: { type: 'array', items: {
type: 'string', format: 'misskey:id',
} },
localOnly: {
type: 'boolean',
nullable: false,
description: 'Use `null` to reset the licence.',
},
},
required: ['ids'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private customEmojiService: CustomEmojiService,
) {
super(meta, paramDef, async (ps, me) => {
await this.customEmojiService.setLocalOnlyBulk(ps.ids, ps.localOnly ?? false);
});
}
}

View file

@ -0,0 +1,78 @@
import { Injectable } from '@nestjs/common';
import sharp from 'sharp';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireRolePolicy: 'canManageCustomEmojis',
kind: 'write:admin:emoji',
} as const;
export const paramDef = {
type: 'object',
properties: {
url: {
type: 'string',
},
},
required: ['url'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
private customEmojiService: CustomEmojiService,
) {
super(meta, paramDef, async (ps, me) => {
const response = await fetch(ps.url, {
'headers': {
'accept': 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
'cache-control': 'no-cache',
'pragma': 'no-cache',
'priority': 'u=1, i',
},
'method': 'GET',
});
if (!response.ok) {
throw new Error(`Failed to fetch image: ${response.status} ${response.statusText}`);
}
const buffer = await response.arrayBuffer();
const metadata = await sharp(buffer).metadata();
if (!metadata.pages) {
throw new Error('Invalid image format or no animation frames found.');
}
const frameRate = metadata.delay && metadata.delay.length > 0
? 1000 / metadata.delay[0]
: 30; // Fallback to 30 FPS if no delay information is present
const colorsPerFrame: number[] = [];
for (let i = 0; i < metadata.pages; i++) {
const { data, info } = await sharp(buffer, { page: i }).raw().toBuffer({ resolveWithObject: true });
const uniqueColors = new Set<string>();
for (let y = 0; y < info.height; y++) {
for (let x = 0; x < info.width; x++) {
const offset = (y * info.width + x) * info.channels;
const color = `${data[offset]}-${data[offset + 1]}-${data[offset + 2]}`;
uniqueColors.add(color);
}
}
colorsPerFrame.push(uniqueColors.size);
}
const colorChanges = colorsPerFrame.map((colorCount, index, arr) => {
if (index === 0) return 0;
return Math.abs(colorCount - arr[index - 1]);
});
const averageColorChangePerSecond = colorChanges.reduce((sum, change) => sum + change, 0) / colorsPerFrame.length;
console.log('Average color change per second:', 10 < averageColorChangePerSecond);
return Boolean(10 < averageColorChangePerSecond);
// You can store or use this information as needed
});
}
}

View file

@ -0,0 +1,117 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import type { DriveFilesRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireRolePolicy: 'canManageCustomEmojis',
errors: {
noSuchEmoji: {
message: 'No such emoji.',
code: 'NO_SUCH_EMOJI',
id: '684dec9d-a8c2-4364-9aa8-456c49cb1dc8',
},
noSuchFile: {
message: 'No such file.',
code: 'NO_SUCH_FILE',
id: '14fb9fd9-0731-4e2f-aeb9-f09e4740333d',
},
sameNameEmojiExists: {
message: 'Emoji that have same name already exists.',
code: 'SAME_NAME_EMOJI_EXISTS',
id: '7180fe9d-1ee3-bff9-647d-fe9896d2ffb8',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
id: { type: 'string', format: 'misskey:id' },
name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' },
fileId: { type: 'string', format: 'misskey:id' },
category: {
type: 'string',
nullable: true,
description: 'Use `null` to reset the category.',
},
aliases: { type: 'array', items: {
type: 'string',
} },
license: { type: 'string', nullable: true },
isSensitive: { type: 'boolean' },
localOnly: { type: 'boolean' },
roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
type: 'string',
} },
Request: { type: 'boolean' },
},
required: ['id', 'name', 'aliases'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
private customEmojiService: CustomEmojiService,
private driveFileEntityService: DriveFileEntityService,
) {
super(meta, paramDef, async (ps, me) => {
let driveFile;
const isRequest = !!ps.Request;
if (ps.fileId) {
driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
}
const emoji = await this.customEmojiService.getEmojiRequestById(ps.id);
if (emoji != null) {
if (ps.name !== emoji.name) {
const isDuplicate = await this.customEmojiService.checkRequestDuplicate(ps.name);
if (isDuplicate) throw new ApiError(meta.errors.sameNameEmojiExists);
}
} else {
throw new ApiError(meta.errors.noSuchEmoji);
}
if (!isRequest) {
const file = await this.driveFileEntityService.getFromUrl(emoji.originalUrl);
if (file === null) throw new ApiError(meta.errors.noSuchFile);
await this.customEmojiService.add({
driveFile: file,
name: ps.name,
category: ps.category ?? null,
aliases: ps.aliases ?? [],
host: null,
license: ps.license ?? null,
isSensitive: ps.isSensitive ?? false,
localOnly: ps.localOnly ?? false,
roleIdsThatCanBeUsedThisEmojiAsReaction: [],
}, me);
await this.customEmojiService.deleteRequest(ps.id);
} else {
await this.customEmojiService.updateRequest(ps.id, {
name: ps.name,
category: ps.category ?? null,
aliases: ps.aliases ?? [],
license: ps.license ?? null,
isSensitive: ps.isSensitive ?? false,
localOnly: ps.localOnly ?? false,
}, me);
}
});
}
}

View file

@ -5,8 +5,9 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import type { DriveFilesRepository } from '@/models/_.js';
import type { DriveFilesRepository , EmojisRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
@ -33,6 +34,11 @@ export const meta = {
code: 'SAME_NAME_EMOJI_EXISTS',
id: '7180fe9d-1ee3-bff9-647d-fe9896d2ffb8',
},
duplicationEmojiAdd: {
message: 'This emoji is already added.',
code: 'DUPLICATION_EMOJI_ADD',
id: 'mattyaski_emoji_duplication_error',
}
},
} as const;
@ -56,6 +62,7 @@ export const paramDef = {
roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
type: 'string',
} },
Request: { type: 'boolean' },
},
anyOf: [
{ required: ['id'] },
@ -68,11 +75,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
private customEmojiService: CustomEmojiService,
private driveFileEntityService: DriveFileEntityService,
) {
super(meta, paramDef, async (ps, me) => {
let driveFile;
const isRequest = !!ps.Request;
if (ps.fileId) {
driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
@ -94,7 +102,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
emojiId = emoji.id;
}
await this.customEmojiService.update(emojiId, {
if (!isRequest) {await this.customEmojiService.update(emojiId, {
driveFile,
name: ps.name,
category: ps.category,
@ -103,7 +111,21 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
isSensitive: ps.isSensitive,
localOnly: ps.localOnly,
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction,
}, me);
draft: false,
}, me);} else {
const file = await this.driveFileEntityService.getFromUrl(emoji.originalUrl);
if (file === null) throw new ApiError(meta.errors.noSuchFile);
await this.customEmojiService.request({
driveFile: file,
name: ps.name,
category: ps.category ?? null,
aliases: ps.aliases ?? [],
license: ps.license ?? null,
isSensitive: ps.isSensitive ?? false,
localOnly: ps.localOnly ?? false,
}, me);
await this.customEmojiService.delete(ps.id);
}
});
}
}

View file

@ -457,6 +457,25 @@ export const meta = {
type: 'string',
optional: false, nullable: false,
},
enableGDPRMode: {
type: 'boolean',
optional: false, nullable: false,
},
DiscordWebhookUrl: {
type: 'string',
optional: false, nullable: true,
}, DiscordWebhookUrlWordBlock: {
type: 'string',
optional: false, nullable: true,
},
enableProxyCheckio: {
type: 'boolean',
optional: false, nullable: false,
},
proxyCheckioApiKey: {
type: 'string',
optional: false, nullable: true,
},
urlPreviewEnabled: {
type: 'boolean',
optional: false, nullable: false,
@ -481,6 +500,10 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
iconLight: { type: 'string', nullable: true },
iconDark: { type: 'string', nullable: true },
bannerLight: { type: 'string', nullable: true },
bannerDark: { type: 'string', nullable: true },
},
},
} as const;
@ -531,6 +554,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
turnstileSiteKey: instance.turnstileSiteKey,
swPublickey: instance.swPublicKey,
themeColor: instance.themeColor,
requestEmojiAllOk: instance.requestEmojiAllOk,
mascotImageUrl: instance.mascotImageUrl,
bannerUrl: instance.bannerUrl,
serverErrorImageUrl: instance.serverErrorImageUrl,
@ -607,6 +631,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
perUserHomeTimelineCacheMax: instance.perUserHomeTimelineCacheMax,
perUserListTimelineCacheMax: instance.perUserListTimelineCacheMax,
notesPerOneAd: instance.notesPerOneAd,
DiscordWebhookUrl: instance.DiscordWebhookUrl,
DiscordWebhookUrlWordBlock: instance.DiscordWebhookUrlWordBlock,
EmojiBotToken: instance.EmojiBotToken,
ApiBase: instance.ApiBase,
enableGDPRMode: instance.enableGDPRMode,
enableProxyCheckio: instance.enableProxyCheckio,
proxyCheckioApiKey: instance.proxyCheckioApiKey,
summalyProxy: instance.urlPreviewSummaryProxyUrl,
urlPreviewEnabled: instance.urlPreviewEnabled,
urlPreviewTimeout: instance.urlPreviewTimeout,
@ -614,6 +645,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
urlPreviewRequireContentLength: instance.urlPreviewRequireContentLength,
urlPreviewUserAgent: instance.urlPreviewUserAgent,
urlPreviewSummaryProxyUrl: instance.urlPreviewSummaryProxyUrl,
iconLight: instance.iconLight,
iconDark: instance.iconDark,
bannerLight: instance.bannerLight,
bannerDark: instance.bannerDark,
};
});
}

View file

@ -5,7 +5,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, UserWebhookDeliverQueue, SystemWebhookDeliverQueue } from '@/core/QueueModule.js';
import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue, UserWebhookDeliverQueue } from '@/core/QueueModule.js';
export const meta = {
tags: ['admin'],
@ -49,6 +49,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
@Inject('queue:system') public systemQueue: SystemQueue,
@Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue,
@Inject('queue:scheduleNotePost') public scheduleNotePostQueue: ScheduleNotePostQueue,
@Inject('queue:deliver') public deliverQueue: DeliverQueue,
@Inject('queue:inbox') public inboxQueue: InboxQueue,
@Inject('queue:db') public dbQueue: DbQueue,

View file

@ -50,6 +50,7 @@ export const paramDef = {
mascotImageUrl: { type: 'string', nullable: true },
bannerUrl: { type: 'string', nullable: true },
serverErrorImageUrl: { type: 'string', nullable: true },
googleAnalyticsId: { type: 'string', nullable: true },
infoImageUrl: { type: 'string', nullable: true },
notFoundImageUrl: { type: 'string', nullable: true },
iconUrl: { type: 'string', nullable: true },
@ -90,6 +91,9 @@ export const paramDef = {
type: 'string',
},
},
summalyProxy: { type: 'string', nullable: true },
DiscordWebhookUrl: { type: 'string', nullable: true },
DiscordWebhookUrlWordBlock: { type: 'string', nullable: true },
deeplAuthKey: { type: 'string', nullable: true },
deeplIsPro: { type: 'boolean' },
enableEmail: { type: 'boolean' },
@ -110,6 +114,7 @@ export const paramDef = {
inquiryUrl: { type: 'string', nullable: true },
useObjectStorage: { type: 'boolean' },
objectStorageBaseUrl: { type: 'string', nullable: true },
requestEmojiAllOk: { type: 'boolean', nullable: true },
objectStorageBucket: { type: 'string', nullable: true },
objectStoragePrefix: { type: 'string', nullable: true },
objectStorageEndpoint: { type: 'string', nullable: true },
@ -160,6 +165,19 @@ export const paramDef = {
urlPreviewRequireContentLength: { type: 'boolean' },
urlPreviewUserAgent: { type: 'string', nullable: true },
urlPreviewSummaryProxyUrl: { type: 'string', nullable: true },
EmojiBotToken: { type: 'string', nullable: true },
ApiBase: { type: 'string', nullable: true },
enableGDPRMode: { type: 'boolean' },
enableProxyCheckio: {
type: 'boolean', nullable: true,
},
proxyCheckioApiKey: {
type: 'string', nullable: true,
},
iconLight: { type: 'string', nullable: true },
iconDark: { type: 'string', nullable: true },
bannerLight: { type: 'string', nullable: true },
bannerDark: { type: 'string', nullable: true },
},
required: [],
} as const;
@ -206,7 +224,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (ps.themeColor !== undefined) {
set.themeColor = ps.themeColor;
}
if (ps.DiscordWebhookUrl !== undefined) {
set.DiscordWebhookUrl = ps.DiscordWebhookUrl;
}
if (ps.DiscordWebhookUrlWordBlock !== undefined) {
set.DiscordWebhookUrlWordBlock = ps.DiscordWebhookUrlWordBlock;
}
if (ps.EmojiBotToken !== undefined) {
set.EmojiBotToken = ps.EmojiBotToken;
}
if (ps.ApiBase !== undefined) {
set.ApiBase = ps.ApiBase;
}
if (ps.mascotImageUrl !== undefined) {
set.mascotImageUrl = ps.mascotImageUrl;
}
@ -230,11 +259,28 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (ps.serverErrorImageUrl !== undefined) {
set.serverErrorImageUrl = ps.serverErrorImageUrl;
}
if (ps.googleAnalyticsId !== undefined) {
set.googleAnalyticsId = ps.googleAnalyticsId;
}
if (ps.enableProxyCheckio !== undefined) {
set.enableProxyCheckio = ps.enableProxyCheckio;
}
if (ps.proxyCheckioApiKey !== undefined) {
set.proxyCheckioApiKey = ps.proxyCheckioApiKey;
}
if (ps.infoImageUrl !== undefined) {
set.infoImageUrl = ps.infoImageUrl;
}
if (ps.enableGDPRMode !== undefined) {
set.enableGDPRMode = ps.enableGDPRMode;
}
if (ps.requestEmojiAllOk !== undefined) {
set.requestEmojiAllOk = ps.requestEmojiAllOk;
}
if (ps.notFoundImageUrl !== undefined) {
set.notFoundImageUrl = ps.notFoundImageUrl;
}
@ -616,7 +662,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const value = ((ps.urlPreviewSummaryProxyUrl ?? ps.summalyProxy) ?? '').trim();
set.urlPreviewSummaryProxyUrl = value === '' ? null : value;
}
if (ps.bannerDark !== undefined) {
set.bannerDark = ps.bannerDark;
}
if (ps.bannerLight !== undefined) {
set.bannerLight = ps.bannerLight;
}
if (ps.iconDark !== undefined) {
set.iconDark = ps.iconDark;
}
if (ps.iconLight !== undefined) {
set.iconLight = ps.iconLight;
}
const before = await this.metaService.fetch(true);
await this.metaService.update(set);

View file

@ -75,7 +75,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId);
query.andWhere(':file <@ note.fileIds', { file: [file.id] });
const notes = await query.limit(ps.limit).getMany();
return await this.noteEntityService.packMany(notes, me, {

View file

@ -0,0 +1,64 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import type { EmojiRequestsRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { EmojiRequestsEntityService } from '@/core/entities/EmojiRequestsEntityService.js';
import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['meta'],
requireCredential: false,
allowGet: true,
cacheSec: 3600,
res: {
type: 'object',
optional: false, nullable: false,
properties: {
emojis: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
ref: 'EmojiRequestSimple',
},
},
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
},
required: [],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.emojiRequestsRepository)
private emojiRequestsRepository: EmojiRequestsRepository,
private emojiRequestsEntityService: EmojiRequestsEntityService,
) {
super(meta, paramDef, async () => {
const emojis = await this.emojiRequestsRepository.find({
order: {
category: 'ASC',
name: 'ASC',
},
});
return {
emojis: await this.emojiRequestsEntityService.packSimpleMany(emojis),
};
});
}
}

View file

@ -75,6 +75,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
name: decoration.name,
description: decoration.description,
url: decoration.url,
category: decoration.category,
roleIdsThatCanBeUsedThisDecoration: decoration.roleIdsThatCanBeUsedThisDecoration.filter(roleId => allRoles.some(role => role.id === roleId)),
}));
});

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