;
+// https://github.com/misskey-dev/misskey/issues/8535
+// To avoid excessive stack depth error,
+// deceive TypeScript with UnionToIntersection (or more precisely, `infer` expression within it).
export type ObjType =
- { -readonly [P in keyof s]?: SchemaType } &
- { -readonly [P in RequiredProps]: SchemaType } &
- { -readonly [P in RequiredPropertyNames]: SchemaType };
+ UnionToIntersection<
+ { -readonly [R in RequiredPropertyNames]-?: SchemaType } &
+ { -readonly [R in RequiredProps]-?: SchemaType } &
+ { -readonly [P in keyof s]?: SchemaType }
+ >;
type NullOrUndefined =
- p['nullable'] extends true
- ? p['optional'] extends true
- ? (T | null | undefined)
- : (T | null)
- : p['optional'] extends true
- ? (T | undefined)
- : T;
+ | (p['nullable'] extends true ? null : never)
+ | (p['optional'] extends true ? undefined : never)
+ | T;
// https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection
// Get intersection from union
@@ -139,9 +144,9 @@ export type SchemaTypeDef
=
p['type'] extends 'number' ? number :
p['type'] extends 'string' ? (
p['enum'] extends readonly string[] ?
- p['enum'][number] :
- p['format'] extends 'date-time' ? string : // Dateにする??
- string
+ p['enum'][number] :
+ p['format'] extends 'date-time' ? string : // Dateにする??
+ string
) :
p['type'] extends 'boolean' ? boolean :
p['type'] extends 'object' ? (
diff --git a/packages/backend/src/misc/webhook-cache.ts b/packages/backend/src/misc/webhook-cache.ts
new file mode 100644
index 000000000..4bd233366
--- /dev/null
+++ b/packages/backend/src/misc/webhook-cache.ts
@@ -0,0 +1,49 @@
+import { Webhooks } from '@/models/index.js';
+import { Webhook } from '@/models/entities/webhook.js';
+import { subsdcriber } from '../db/redis.js';
+
+let webhooksFetched = false;
+let webhooks: Webhook[] = [];
+
+export async function getActiveWebhooks() {
+ if (!webhooksFetched) {
+ webhooks = await Webhooks.findBy({
+ active: true,
+ });
+ webhooksFetched = true;
+ }
+
+ return webhooks;
+}
+
+subsdcriber.on('message', async (_, data) => {
+ const obj = JSON.parse(data);
+
+ if (obj.channel === 'internal') {
+ const { type, body } = obj.message;
+ switch (type) {
+ case 'webhookCreated':
+ if (body.active) {
+ webhooks.push(body);
+ }
+ break;
+ case 'webhookUpdated':
+ if (body.active) {
+ const i = webhooks.findIndex(a => a.id === body.id);
+ if (i > -1) {
+ webhooks[i] = body;
+ } else {
+ webhooks.push(body);
+ }
+ } else {
+ webhooks = webhooks.filter(a => a.id !== body.id);
+ }
+ break;
+ case 'webhookDeleted':
+ webhooks = webhooks.filter(a => a.id !== body.id);
+ break;
+ default:
+ break;
+ }
+ }
+});
diff --git a/packages/backend/src/models/entities/access-token.ts b/packages/backend/src/models/entities/access-token.ts
index 69cdc49ce..c6e2141a4 100644
--- a/packages/backend/src/models/entities/access-token.ts
+++ b/packages/backend/src/models/entities/access-token.ts
@@ -15,7 +15,6 @@ export class AccessToken {
@Column('timestamp with time zone', {
nullable: true,
- default: null,
})
public lastUsedAt: Date | null;
@@ -29,7 +28,6 @@ export class AccessToken {
@Column('varchar', {
length: 128,
nullable: true,
- default: null,
})
public session: string | null;
@@ -52,7 +50,6 @@ export class AccessToken {
@Column({
...id(),
nullable: true,
- default: null,
})
public appId: App['id'] | null;
@@ -65,21 +62,18 @@ export class AccessToken {
@Column('varchar', {
length: 128,
nullable: true,
- default: null,
})
public name: string | null;
@Column('varchar', {
length: 512,
nullable: true,
- default: null,
})
public description: string | null;
@Column('varchar', {
length: 512,
nullable: true,
- default: null,
})
public iconUrl: string | null;
diff --git a/packages/backend/src/models/entities/auth-session.ts b/packages/backend/src/models/entities/auth-session.ts
index b82585620..295d1b486 100644
--- a/packages/backend/src/models/entities/auth-session.ts
+++ b/packages/backend/src/models/entities/auth-session.ts
@@ -23,7 +23,7 @@ export class AuthSession {
...id(),
nullable: true,
})
- public userId: User['id'];
+ public userId: User['id'] | null;
@ManyToOne(type => User, {
onDelete: 'CASCADE',
diff --git a/packages/backend/src/models/entities/clip.ts b/packages/backend/src/models/entities/clip.ts
index da6b3c7a7..1386684c3 100644
--- a/packages/backend/src/models/entities/clip.ts
+++ b/packages/backend/src/models/entities/clip.ts
@@ -37,7 +37,7 @@ export class Clip {
public isPublic: boolean;
@Column('varchar', {
- length: 2048, nullable: true, default: null,
+ length: 2048, nullable: true,
comment: 'The description of the Clip.',
})
public description: string | null;
diff --git a/packages/backend/src/models/entities/drive-file.ts b/packages/backend/src/models/entities/drive-file.ts
index 3d375f0e3..a636d1d51 100644
--- a/packages/backend/src/models/entities/drive-file.ts
+++ b/packages/backend/src/models/entities/drive-file.ts
@@ -79,7 +79,6 @@ export class DriveFile {
})
public properties: { width?: number; height?: number; orientation?: number; avgColor?: string };
- @Index()
@Column('boolean')
public storedInternal: boolean;
diff --git a/packages/backend/src/models/entities/emoji.ts b/packages/backend/src/models/entities/emoji.ts
index b72ca7233..7332dd185 100644
--- a/packages/backend/src/models/entities/emoji.ts
+++ b/packages/backend/src/models/entities/emoji.ts
@@ -36,6 +36,7 @@ export class Emoji {
@Column('varchar', {
length: 512,
+ default: '',
})
public publicUrl: string;
diff --git a/packages/backend/src/models/entities/instance.ts b/packages/backend/src/models/entities/instance.ts
index bb24d6b30..7ea923438 100644
--- a/packages/backend/src/models/entities/instance.ts
+++ b/packages/backend/src/models/entities/instance.ts
@@ -107,53 +107,53 @@ export class Instance {
public isSuspended: boolean;
@Column('varchar', {
- length: 64, nullable: true, default: null,
+ length: 64, nullable: true,
comment: 'The software of the Instance.',
})
public softwareName: string | null;
@Column('varchar', {
- length: 64, nullable: true, default: null,
+ length: 64, nullable: true,
})
public softwareVersion: string | null;
@Column('boolean', {
- nullable: true, default: null,
+ nullable: true,
})
public openRegistrations: boolean | null;
@Column('varchar', {
- length: 256, nullable: true, default: null,
+ length: 256, nullable: true,
})
public name: string | null;
@Column('varchar', {
- length: 4096, nullable: true, default: null,
+ length: 4096, nullable: true,
})
public description: string | null;
@Column('varchar', {
- length: 128, nullable: true, default: null,
+ length: 128, nullable: true,
})
public maintainerName: string | null;
@Column('varchar', {
- length: 256, nullable: true, default: null,
+ length: 256, nullable: true,
})
public maintainerEmail: string | null;
@Column('varchar', {
- length: 256, nullable: true, default: null,
+ length: 256, nullable: true,
})
public iconUrl: string | null;
@Column('varchar', {
- length: 256, nullable: true, default: null,
+ length: 256, nullable: true,
})
public faviconUrl: string | null;
@Column('varchar', {
- length: 64, nullable: true, default: null,
+ length: 64, nullable: true,
})
public themeColor: string | null;
diff --git a/packages/backend/src/models/entities/meta.ts b/packages/backend/src/models/entities/meta.ts
index 4d58b5f04..80b5228bc 100644
--- a/packages/backend/src/models/entities/meta.ts
+++ b/packages/backend/src/models/entities/meta.ts
@@ -78,7 +78,7 @@ export class Meta {
public blockedHosts: string[];
@Column('varchar', {
- length: 512, array: true, default: '{"/featured", "/channels", "/explore", "/pages", "/about-misskey"}',
+ length: 512, array: true, default: '{/featured,/channels,/explore,/pages,/about-misskey}',
})
public pinnedPages: string[];
@@ -346,14 +346,12 @@ export class Meta {
@Column('varchar', {
length: 8192,
- default: null,
nullable: true,
})
public defaultLightTheme: string | null;
@Column('varchar', {
length: 8192,
- default: null,
nullable: true,
})
public defaultDarkTheme: string | null;
diff --git a/packages/backend/src/models/entities/muting.ts b/packages/backend/src/models/entities/muting.ts
index b3a7e7a67..8f9e69063 100644
--- a/packages/backend/src/models/entities/muting.ts
+++ b/packages/backend/src/models/entities/muting.ts
@@ -17,7 +17,6 @@ export class Muting {
@Index()
@Column('timestamp with time zone', {
nullable: true,
- default: null,
})
public expiresAt: Date | null;
diff --git a/packages/backend/src/models/entities/note.ts b/packages/backend/src/models/entities/note.ts
index da49d53b6..0ffeb85f6 100644
--- a/packages/backend/src/models/entities/note.ts
+++ b/packages/backend/src/models/entities/note.ts
@@ -53,8 +53,8 @@ export class Note {
})
public threadId: string | null;
- @Column('varchar', {
- length: 8192, nullable: true,
+ @Column('text', {
+ nullable: true,
})
public text: string | null;
@@ -179,7 +179,7 @@ export class Note {
@Index()
@Column({
...id(),
- nullable: true, default: null,
+ nullable: true,
comment: 'The ID of source channel.',
})
public channelId: Channel['id'] | null;
diff --git a/packages/backend/src/models/entities/user-profile.ts b/packages/backend/src/models/entities/user-profile.ts
index f95cb144c..1778742ea 100644
--- a/packages/backend/src/models/entities/user-profile.ts
+++ b/packages/backend/src/models/entities/user-profile.ts
@@ -192,6 +192,7 @@ export class UserProfile {
@Column('jsonb', {
default: [],
+ comment: 'List of instances muted by the user.',
})
public mutedInstances: string[];
diff --git a/packages/backend/src/models/entities/user.ts b/packages/backend/src/models/entities/user.ts
index c76824c97..df92fb825 100644
--- a/packages/backend/src/models/entities/user.ts
+++ b/packages/backend/src/models/entities/user.ts
@@ -1,6 +1,6 @@
import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm';
-import { DriveFile } from './drive-file.js';
import { id } from '../id.js';
+import { DriveFile } from './drive-file.js';
@Entity()
@Index(['usernameLower', 'host'], { unique: true })
@@ -207,7 +207,7 @@ export class User {
@Column('boolean', {
default: false,
- comment: 'Whether to show users replying to other users in the timeline'
+ comment: 'Whether to show users replying to other users in the timeline.',
})
public showTimelineReplies: boolean;
diff --git a/packages/backend/src/models/entities/webhook.ts b/packages/backend/src/models/entities/webhook.ts
new file mode 100644
index 000000000..56b411f87
--- /dev/null
+++ b/packages/backend/src/models/entities/webhook.ts
@@ -0,0 +1,73 @@
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
+import { User } from './user.js';
+import { id } from '../id.js';
+
+export const webhookEventTypes = ['mention', 'unfollow', 'follow', 'followed', 'note', 'reply', 'renote', 'reaction'] as const;
+
+@Entity()
+export class Webhook {
+ @PrimaryColumn(id())
+ public id: string;
+
+ @Column('timestamp with time zone', {
+ comment: 'The created date of the Antenna.',
+ })
+ public createdAt: Date;
+
+ @Index()
+ @Column({
+ ...id(),
+ comment: 'The owner ID.',
+ })
+ public userId: User['id'];
+
+ @ManyToOne(type => User, {
+ onDelete: 'CASCADE',
+ })
+ @JoinColumn()
+ public user: User | null;
+
+ @Column('varchar', {
+ length: 128,
+ comment: 'The name of the Antenna.',
+ })
+ public name: string;
+
+ @Index()
+ @Column('varchar', {
+ length: 128, array: true, default: '{}',
+ })
+ public on: (typeof webhookEventTypes)[number][];
+
+ @Column('varchar', {
+ length: 1024,
+ })
+ public url: string;
+
+ @Column('varchar', {
+ length: 1024,
+ })
+ public secret: string;
+
+ @Index()
+ @Column('boolean', {
+ default: true,
+ })
+ public active: boolean;
+
+ /**
+ * 直近のリクエスト送信日時
+ */
+ @Column('timestamp with time zone', {
+ nullable: true,
+ })
+ public latestSentAt: Date | null;
+
+ /**
+ * 直近のリクエスト送信時のHTTPステータスコード
+ */
+ @Column('integer', {
+ nullable: true,
+ })
+ public latestStatus: number | null;
+}
diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts
index 54582347c..814b37d44 100644
--- a/packages/backend/src/models/index.ts
+++ b/packages/backend/src/models/index.ts
@@ -64,6 +64,7 @@ import { Ad } from './entities/ad.js';
import { PasswordResetRequest } from './entities/password-reset-request.js';
import { UserPending } from './entities/user-pending.js';
import { InstanceRepository } from './repositories/instance.js';
+import { Webhook } from './entities/webhook.js';
export const Announcements = db.getRepository(Announcement);
export const AnnouncementReads = db.getRepository(AnnouncementRead);
@@ -125,5 +126,6 @@ export const Channels = (ChannelRepository);
export const ChannelFollowings = db.getRepository(ChannelFollowing);
export const ChannelNotePinings = db.getRepository(ChannelNotePining);
export const RegistryItems = db.getRepository(RegistryItem);
+export const Webhooks = db.getRepository(Webhook);
export const Ads = db.getRepository(Ad);
export const PasswordResetRequests = db.getRepository(PasswordResetRequest);
diff --git a/packages/backend/src/models/repositories/drive-file.ts b/packages/backend/src/models/repositories/drive-file.ts
index 69dc1721c..b626359d9 100644
--- a/packages/backend/src/models/repositories/drive-file.ts
+++ b/packages/backend/src/models/repositories/drive-file.ts
@@ -1,6 +1,5 @@
import { db } from '@/db/postgre.js';
import { DriveFile } from '@/models/entities/drive-file.js';
-import { Users, DriveFolders } from '../index.js';
import { User } from '@/models/entities/user.js';
import { toPuny } from '@/misc/convert-host.js';
import { awaitAll, Promiseable } from '@/prelude/await-all.js';
@@ -9,6 +8,7 @@ import config from '@/config/index.js';
import { query, appendQuery } from '@/prelude/url.js';
import { Meta } from '@/models/entities/meta.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
+import { Users, DriveFolders } from '../index.js';
type PackOptions = {
detail?: boolean,
@@ -29,7 +29,7 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({
getPublicProperties(file: DriveFile): DriveFile['properties'] {
if (file.properties.orientation != null) {
- const properties = JSON.parse(JSON.stringify(file.properties));
+ const properties = structuredClone(file.properties);
if (file.properties.orientation >= 5) {
[properties.width, properties.height] = [properties.height, properties.width];
}
@@ -111,7 +111,40 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({
async pack(
src: DriveFile['id'] | DriveFile,
- options?: PackOptions
+ options?: PackOptions,
+ ): Promise> {
+ const opts = Object.assign({
+ detail: false,
+ self: false,
+ }, options);
+
+ const file = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
+
+ return await awaitAll>({
+ id: file.id,
+ createdAt: file.createdAt.toISOString(),
+ name: file.name,
+ type: file.type,
+ md5: file.md5,
+ size: file.size,
+ isSensitive: file.isSensitive,
+ blurhash: file.blurhash,
+ properties: opts.self ? file.properties : this.getPublicProperties(file),
+ url: opts.self ? file.url : this.getPublicUrl(file, false),
+ thumbnailUrl: this.getPublicUrl(file, true),
+ comment: file.comment,
+ folderId: file.folderId,
+ folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, {
+ detail: true,
+ }) : null,
+ userId: opts.withUser ? file.userId : null,
+ user: (opts.withUser && file.userId) ? Users.pack(file.userId) : null,
+ });
+ },
+
+ async packNullable(
+ src: DriveFile['id'] | DriveFile,
+ options?: PackOptions,
): Promise | null> {
const opts = Object.assign({
detail: false,
@@ -145,9 +178,9 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({
async packMany(
files: (DriveFile['id'] | DriveFile)[],
- options?: PackOptions
- ) {
- const items = await Promise.all(files.map(f => this.pack(f, options)));
- return items.filter(x => x != null);
+ options?: PackOptions,
+ ): Promise[]> {
+ const items = await Promise.all(files.map(f => this.packNullable(f, options)));
+ return items.filter((x): x is Packed<'DriveFile'> => x != null);
},
});
diff --git a/packages/backend/src/models/repositories/page.ts b/packages/backend/src/models/repositories/page.ts
index 1bffb23fa..092b26b39 100644
--- a/packages/backend/src/models/repositories/page.ts
+++ b/packages/backend/src/models/repositories/page.ts
@@ -1,10 +1,10 @@
import { db } from '@/db/postgre.js';
import { Page } from '@/models/entities/page.js';
import { Packed } from '@/misc/schema.js';
-import { Users, DriveFiles, PageLikes } from '../index.js';
import { awaitAll } from '@/prelude/await-all.js';
import { DriveFile } from '@/models/entities/drive-file.js';
import { User } from '@/models/entities/user.js';
+import { Users, DriveFiles, PageLikes } from '../index.js';
export const PageRepository = db.getRepository(Page).extend({
async pack(
@@ -14,7 +14,7 @@ export const PageRepository = db.getRepository(Page).extend({
const meId = me ? me.id : null;
const page = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
- const attachedFiles: Promise[] = [];
+ const attachedFiles: Promise[] = [];
const collectFile = (xs: any[]) => {
for (const x of xs) {
if (x.type === 'image') {
@@ -73,7 +73,7 @@ export const PageRepository = db.getRepository(Page).extend({
script: page.script,
eyeCatchingImageId: page.eyeCatchingImageId,
eyeCatchingImage: page.eyeCatchingImageId ? await DriveFiles.pack(page.eyeCatchingImageId) : null,
- attachedFiles: DriveFiles.packMany(await Promise.all(attachedFiles)),
+ attachedFiles: DriveFiles.packMany((await Promise.all(attachedFiles)).filter((x): x is DriveFile => x != null)),
likedCount: page.likedCount,
isLiked: meId ? await PageLikes.findOneBy({ pageId: page.id, userId: meId }).then(x => x != null) : undefined,
});
diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts
index 2f4b7d678..541fbaf00 100644
--- a/packages/backend/src/models/repositories/user.ts
+++ b/packages/backend/src/models/repositories/user.ts
@@ -1,7 +1,6 @@
import { EntityRepository, Repository, In, Not } from 'typeorm';
import Ajv from 'ajv';
import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js';
-import { Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Announcements, AnnouncementReads, Antennas, AntennaNotes, ChannelFollowings, Instances, DriveFiles } from '../index.js';
import config from '@/config/index.js';
import { Packed } from '@/misc/schema.js';
import { awaitAll, Promiseable } from '@/prelude/await-all.js';
@@ -9,8 +8,9 @@ import { populateEmojis } from '@/misc/populate-emojis.js';
import { getAntennas } from '@/misc/antenna-cache.js';
import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js';
import { Cache } from '@/misc/cache.js';
-import { Instance } from '../entities/instance.js';
import { db } from '@/db/postgre.js';
+import { Instance } from '../entities/instance.js';
+import { Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Announcements, AnnouncementReads, Antennas, AntennaNotes, ChannelFollowings, Instances, DriveFiles } from '../index.js';
const userInstanceCache = new Cache(1000 * 60 * 60 * 3);
@@ -112,7 +112,7 @@ export const UserRepository = db.getRepository(User).extend({
const joinings = await UserGroupJoinings.findBy({ userId: userId });
const groupQs = Promise.all(joinings.map(j => MessagingMessages.createQueryBuilder('message')
- .where(`message.groupId = :groupId`, { groupId: j.userGroupId })
+ .where('message.groupId = :groupId', { groupId: j.userGroupId })
.andWhere('message.userId != :userId', { userId: userId })
.andWhere('NOT (:userId = ANY(message.reads))', { userId: userId })
.andWhere('message.createdAt > :joinedAt', { joinedAt: j.createdAt }) // 自分が加入する前の会話については、未読扱いしない
@@ -204,8 +204,18 @@ export const UserRepository = db.getRepository(User).extend({
);
},
- getAvatarUrl(user: User): string {
- // TODO: avatarIdがあるがavatarがない(JOINされてない)場合のハンドリング
+ async getAvatarUrl(user: User): Promise {
+ if (user.avatar) {
+ return DriveFiles.getPublicUrl(user.avatar, true) || this.getIdenticonUrl(user.id);
+ } else if (user.avatarId) {
+ const avatar = await DriveFiles.findOneByOrFail({ id: user.avatarId });
+ return DriveFiles.getPublicUrl(avatar, true) || this.getIdenticonUrl(user.id);
+ } else {
+ return this.getIdenticonUrl(user.id);
+ }
+ },
+
+ getAvatarUrlSync(user: User): string {
if (user.avatar) {
return DriveFiles.getPublicUrl(user.avatar, true) || this.getIdenticonUrl(user.id);
} else {
@@ -223,7 +233,7 @@ export const UserRepository = db.getRepository(User).extend({
options?: {
detail?: D,
includeSecrets?: boolean,
- }
+ },
): Promise> {
const opts = Object.assign({
detail: false,
@@ -274,7 +284,7 @@ export const UserRepository = db.getRepository(User).extend({
name: user.name,
username: user.username,
host: user.host,
- avatarUrl: this.getAvatarUrl(user),
+ avatarUrl: this.getAvatarUrlSync(user),
avatarBlurhash: user.avatar?.blurhash || null,
avatarColor: null, // 後方互換性のため
isAdmin: user.isAdmin || falsy,
@@ -283,7 +293,7 @@ export const UserRepository = db.getRepository(User).extend({
isCat: user.isCat || falsy,
instance: user.host ? userInstanceCache.fetch(user.host,
() => Instances.findOneBy({ host: user.host! }),
- v => v != null
+ v => v != null,
).then(instance => instance ? {
name: instance.name,
softwareName: instance.softwareName,
@@ -403,7 +413,7 @@ export const UserRepository = db.getRepository(User).extend({
options?: {
detail?: D,
includeSecrets?: boolean,
- }
+ },
): Promise[]> {
return Promise.all(users.map(u => this.pack(u, me, options)));
},
diff --git a/packages/backend/src/models/schema/emoji.ts b/packages/backend/src/models/schema/emoji.ts
index 5f9af88db..e97fdd5ef 100644
--- a/packages/backend/src/models/schema/emoji.ts
+++ b/packages/backend/src/models/schema/emoji.ts
@@ -27,6 +27,7 @@ export const packedEmojiSchema = {
host: {
type: 'string',
optional: false, nullable: true,
+ description: 'The local host is represented with `null`.',
},
url: {
type: 'string',
diff --git a/packages/backend/src/models/schema/user.ts b/packages/backend/src/models/schema/user.ts
index 616bedc0d..253681695 100644
--- a/packages/backend/src/models/schema/user.ts
+++ b/packages/backend/src/models/schema/user.ts
@@ -21,6 +21,7 @@ export const packedUserLiteSchema = {
type: 'string',
nullable: true, optional: false,
example: 'misskey.example.com',
+ description: 'The local host is represented with `null`.',
},
avatarUrl: {
type: 'string',
diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts
index b679a552b..2d40290e4 100644
--- a/packages/backend/src/queue/index.ts
+++ b/packages/backend/src/queue/index.ts
@@ -1,4 +1,5 @@
import httpSignature from 'http-signature';
+import { v4 as uuid } from 'uuid';
import config from '@/config/index.js';
import { envOption } from '../env.js';
@@ -8,13 +9,15 @@ import processInbox from './processors/inbox.js';
import processDb from './processors/db/index.js';
import processObjectStorage from './processors/object-storage/index.js';
import processSystemQueue from './processors/system/index.js';
+import processWebhookDeliver from './processors/webhook-deliver.js';
import { endedPollNotification } from './processors/ended-poll-notification.js';
import { queueLogger } from './logger.js';
import { DriveFile } from '@/models/entities/drive-file.js';
import { getJobInfo } from './get-job-info.js';
-import { systemQueue, dbQueue, deliverQueue, inboxQueue, objectStorageQueue, endedPollNotificationQueue } from './queues.js';
+import { systemQueue, dbQueue, deliverQueue, inboxQueue, objectStorageQueue, endedPollNotificationQueue, webhookDeliverQueue } from './queues.js';
import { ThinUser } from './types.js';
import { IActivity } from '@/remote/activitypub/type.js';
+import { Webhook, webhookEventTypes } from '@/models/entities/webhook.js';
function renderError(e: Error): any {
return {
@@ -26,6 +29,7 @@ function renderError(e: Error): any {
const systemLogger = queueLogger.createSubLogger('system');
const deliverLogger = queueLogger.createSubLogger('deliver');
+const webhookLogger = queueLogger.createSubLogger('webhook');
const inboxLogger = queueLogger.createSubLogger('inbox');
const dbLogger = queueLogger.createSubLogger('db');
const objectStorageLogger = queueLogger.createSubLogger('objectStorage');
@@ -70,6 +74,14 @@ objectStorageQueue
.on('error', (job: any, err: Error) => objectStorageLogger.error(`error ${err}`, { job, e: renderError(err) }))
.on('stalled', (job) => objectStorageLogger.warn(`stalled id=${job.id}`));
+webhookDeliverQueue
+ .on('waiting', (jobId) => webhookLogger.debug(`waiting id=${jobId}`))
+ .on('active', (job) => webhookLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
+ .on('completed', (job, result) => webhookLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
+ .on('failed', (job, err) => webhookLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`))
+ .on('error', (job: any, err: Error) => webhookLogger.error(`error ${err}`, { job, e: renderError(err) }))
+ .on('stalled', (job) => webhookLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`));
+
export function deliver(user: ThinUser, content: unknown, to: string | null) {
if (content == null) return null;
if (to == null) return null;
@@ -251,12 +263,36 @@ export function createCleanRemoteFilesJob() {
});
}
+export function webhookDeliver(webhook: Webhook, type: typeof webhookEventTypes[number], content: unknown) {
+ const data = {
+ type,
+ content,
+ webhookId: webhook.id,
+ userId: webhook.userId,
+ to: webhook.url,
+ secret: webhook.secret,
+ createdAt: Date.now(),
+ eventId: uuid(),
+ };
+
+ return webhookDeliverQueue.add(data, {
+ attempts: 4,
+ timeout: 1 * 60 * 1000, // 1min
+ backoff: {
+ type: 'apBackoff',
+ },
+ removeOnComplete: true,
+ removeOnFail: true,
+ });
+}
+
export default function() {
if (envOption.onlyServer) return;
deliverQueue.process(config.deliverJobConcurrency || 128, processDeliver);
inboxQueue.process(config.inboxJobConcurrency || 16, processInbox);
endedPollNotificationQueue.process(endedPollNotification);
+ webhookDeliverQueue.process(64, processWebhookDeliver);
processDb(dbQueue);
processObjectStorage(objectStorageQueue);
diff --git a/packages/backend/src/queue/processors/webhook-deliver.ts b/packages/backend/src/queue/processors/webhook-deliver.ts
new file mode 100644
index 000000000..d49206f68
--- /dev/null
+++ b/packages/backend/src/queue/processors/webhook-deliver.ts
@@ -0,0 +1,59 @@
+import { URL } from 'node:url';
+import Bull from 'bull';
+import Logger from '@/services/logger.js';
+import { WebhookDeliverJobData } from '../types.js';
+import { getResponse, StatusError } from '@/misc/fetch.js';
+import { Webhooks } from '@/models/index.js';
+import config from '@/config/index.js';
+
+const logger = new Logger('webhook');
+
+export default async (job: Bull.Job) => {
+ try {
+ logger.debug(`delivering ${job.data.webhookId}`);
+
+ const res = await getResponse({
+ url: job.data.to,
+ method: 'POST',
+ headers: {
+ 'User-Agent': 'Misskey-Hooks',
+ 'X-Misskey-Host': config.host,
+ 'X-Misskey-Hook-Id': job.data.webhookId,
+ 'X-Misskey-Hook-Secret': job.data.secret,
+ },
+ body: JSON.stringify({
+ hookId: job.data.webhookId,
+ userId: job.data.userId,
+ eventId: job.data.eventId,
+ createdAt: job.data.createdAt,
+ type: job.data.type,
+ body: job.data.content,
+ }),
+ });
+
+ Webhooks.update({ id: job.data.webhookId }, {
+ latestSentAt: new Date(),
+ latestStatus: res.status,
+ });
+
+ return 'Success';
+ } catch (res) {
+ Webhooks.update({ id: job.data.webhookId }, {
+ latestSentAt: new Date(),
+ latestStatus: res instanceof StatusError ? res.statusCode : 1,
+ });
+
+ if (res instanceof StatusError) {
+ // 4xx
+ if (res.isClientError) {
+ return `${res.statusCode} ${res.statusMessage}`;
+ }
+
+ // 5xx etc.
+ throw `${res.statusCode} ${res.statusMessage}`;
+ } else {
+ // DNS error, socket error, timeout ...
+ throw res;
+ }
+ }
+};
diff --git a/packages/backend/src/queue/queues.ts b/packages/backend/src/queue/queues.ts
index d612dee45..f3a267790 100644
--- a/packages/backend/src/queue/queues.ts
+++ b/packages/backend/src/queue/queues.ts
@@ -1,6 +1,6 @@
import config from '@/config/index.js';
import { initialize as initializeQueue } from './initialize.js';
-import { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData, EndedPollNotificationJobData } from './types.js';
+import { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData, EndedPollNotificationJobData, WebhookDeliverJobData } from './types.js';
export const systemQueue = initializeQueue>('system');
export const endedPollNotificationQueue = initializeQueue('endedPollNotification');
@@ -8,6 +8,7 @@ export const deliverQueue = initializeQueue('deliver', config.de
export const inboxQueue = initializeQueue('inbox', config.inboxJobPerSec || 16);
export const dbQueue = initializeQueue('db');
export const objectStorageQueue = initializeQueue('objectStorage');
+export const webhookDeliverQueue = initializeQueue('webhookDeliver', 64);
export const queues = [
systemQueue,
@@ -16,4 +17,5 @@ export const queues = [
inboxQueue,
dbQueue,
objectStorageQueue,
+ webhookDeliverQueue,
];
diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts
index 5191caea4..6c0b9d9bf 100644
--- a/packages/backend/src/queue/types.ts
+++ b/packages/backend/src/queue/types.ts
@@ -1,6 +1,7 @@
import { DriveFile } from '@/models/entities/drive-file.js';
import { Note } from '@/models/entities/note';
import { User } from '@/models/entities/user.js';
+import { Webhook } from '@/models/entities/webhook';
import { IActivity } from '@/remote/activitypub/type.js';
import httpSignature from 'http-signature';
@@ -46,6 +47,17 @@ export type EndedPollNotificationJobData = {
noteId: Note['id'];
};
+export type WebhookDeliverJobData = {
+ type: string;
+ content: unknown;
+ webhookId: Webhook['id'];
+ userId: User['id'];
+ to: string;
+ secret: string;
+ createdAt: number;
+ eventId: string;
+};
+
export type ThinUser = {
id: User['id'];
};
diff --git a/packages/backend/src/remote/activitypub/ap-request.ts b/packages/backend/src/remote/activitypub/ap-request.ts
index 96bfec3b1..8b55f2247 100644
--- a/packages/backend/src/remote/activitypub/ap-request.ts
+++ b/packages/backend/src/remote/activitypub/ap-request.ts
@@ -95,7 +95,7 @@ function genSigningString(request: Request, includeHeaders: string[]) {
function lcObjectKey(src: Record) {
const dst: Record = {};
- for (const key of Object.keys(src).filter(x => x != '__proto__' && typeof src[x] === 'string')) dst[key.toLowerCase()] = src[key];
+ for (const key of Object.keys(src).filter(x => x !== '__proto__' && typeof src[x] === 'string')) dst[key.toLowerCase()] = src[key];
return dst;
}
diff --git a/packages/backend/src/remote/activitypub/deliver-manager.ts b/packages/backend/src/remote/activitypub/deliver-manager.ts
index 9f21dc4cc..4c1999e4c 100644
--- a/packages/backend/src/remote/activitypub/deliver-manager.ts
+++ b/packages/backend/src/remote/activitypub/deliver-manager.ts
@@ -79,37 +79,46 @@ export default class DeliverManager {
const inboxes = new Set();
- // build inbox list
- for (const recipe of this.recipes) {
- if (isFollowers(recipe)) {
- // followers deliver
- // TODO: SELECT DISTINCT ON ("followerSharedInbox") "followerSharedInbox" みたいな問い合わせにすればよりパフォーマンス向上できそう
- // ただ、sharedInboxがnullなリモートユーザーも稀におり、その対応ができなさそう?
- const followers = await Followings.find({
- where: {
- followeeId: this.actor.id,
- followerHost: Not(IsNull()),
- },
- select: {
- followerSharedInbox: true,
- followerInbox: true,
- },
- }) as {
- followerSharedInbox: string | null;
- followerInbox: string;
- }[];
+ /*
+ build inbox list
- for (const following of followers) {
- const inbox = following.followerSharedInbox || following.followerInbox;
- inboxes.add(inbox);
- }
- } else if (isDirect(recipe)) {
- // direct deliver
- const inbox = recipe.to.inbox;
- if (inbox) inboxes.add(inbox);
+ Process follower recipes first to avoid duplication when processing
+ direct recipes later.
+ */
+ if (this.recipes.some(r => isFollowers(r))) {
+ // followers deliver
+ // TODO: SELECT DISTINCT ON ("followerSharedInbox") "followerSharedInbox" みたいな問い合わせにすればよりパフォーマンス向上できそう
+ // ただ、sharedInboxがnullなリモートユーザーも稀におり、その対応ができなさそう?
+ const followers = await Followings.find({
+ where: {
+ followeeId: this.actor.id,
+ followerHost: Not(IsNull()),
+ },
+ select: {
+ followerSharedInbox: true,
+ followerInbox: true,
+ },
+ }) as {
+ followerSharedInbox: string | null;
+ followerInbox: string;
+ }[];
+
+ for (const following of followers) {
+ const inbox = following.followerSharedInbox || following.followerInbox;
+ inboxes.add(inbox);
}
}
+ this.recipes.filter((recipe): recipe is IDirectRecipe =>
+ // followers recipes have already been processed
+ isDirect(recipe)
+ // check that shared inbox has not been added yet
+ && !(recipe.to.sharedInbox && inboxes.has(recipe.to.sharedInbox))
+ // check that they actually have an inbox
+ && recipe.to.inbox != null,
+ )
+ .forEach(recipe => inboxes.add(recipe.to.inbox!));
+
// deliver
for (const inbox of inboxes) {
deliver(this.actor, this.activity, inbox);
diff --git a/packages/backend/src/remote/activitypub/kernel/move/index.ts b/packages/backend/src/remote/activitypub/kernel/move/index.ts
deleted file mode 100644
index e69de29bb..000000000
diff --git a/packages/backend/src/remote/activitypub/kernel/read.ts b/packages/backend/src/remote/activitypub/kernel/read.ts
index 7f1519ac2..f7b0bcecd 100644
--- a/packages/backend/src/remote/activitypub/kernel/read.ts
+++ b/packages/backend/src/remote/activitypub/kernel/read.ts
@@ -18,7 +18,7 @@ export const performReadActivity = async (actor: CacheableRemoteUser, activity:
return `skip: message not found`;
}
- if (actor.id != message.recipientId) {
+ if (actor.id !== message.recipientId) {
return `skip: actor is not a message recipient`;
}
diff --git a/packages/backend/src/remote/activitypub/kernel/undo/accept.ts b/packages/backend/src/remote/activitypub/kernel/undo/accept.ts
index 8f6eab685..a6e3929b0 100644
--- a/packages/backend/src/remote/activitypub/kernel/undo/accept.ts
+++ b/packages/backend/src/remote/activitypub/kernel/undo/accept.ts
@@ -1,6 +1,6 @@
import unfollow from '@/services/following/delete.js';
import cancelRequest from '@/services/following/requests/cancel.js';
-import {IAccept} from '../../type.js';
+import { IAccept } from '../../type.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { Followings } from '@/models/index.js';
import DbResolver from '../../db-resolver.js';
diff --git a/packages/backend/src/remote/activitypub/misc/ld-signature.ts b/packages/backend/src/remote/activitypub/misc/ld-signature.ts
index 5132c6ef9..362a543ec 100644
--- a/packages/backend/src/remote/activitypub/misc/ld-signature.ts
+++ b/packages/backend/src/remote/activitypub/misc/ld-signature.ts
@@ -113,7 +113,8 @@ export class LdSignature {
headers: {
Accept: 'application/ld+json, application/json',
},
- timeout: this.loderTimeout,
+ // TODO
+ //timeout: this.loderTimeout,
agent: u => u.protocol === 'http:' ? httpAgent : httpsAgent,
}).then(res => {
if (!res.ok) {
diff --git a/packages/backend/src/remote/activitypub/models/mention.ts b/packages/backend/src/remote/activitypub/models/mention.ts
index a16009296..13f77424e 100644
--- a/packages/backend/src/remote/activitypub/models/mention.ts
+++ b/packages/backend/src/remote/activitypub/models/mention.ts
@@ -1,9 +1,9 @@
-import { toArray, unique } from '@/prelude/array.js';
-import { IObject, isMention, IApMention } from '../type.js';
-import { resolvePerson } from './person.js';
import promiseLimit from 'promise-limit';
-import Resolver from '../resolver.js';
+import { toArray, unique } from '@/prelude/array.js';
import { CacheableUser, User } from '@/models/entities/user.js';
+import { IObject, isMention, IApMention } from '../type.js';
+import Resolver from '../resolver.js';
+import { resolvePerson } from './person.js';
export async function extractApMentions(tags: IObject | IObject[] | null | undefined) {
const hrefs = unique(extractApMentionObjects(tags).map(x => x.href as string));
@@ -12,7 +12,7 @@ export async function extractApMentions(tags: IObject | IObject[] | null | undef
const limit = promiseLimit(2);
const mentionedUsers = (await Promise.all(
- hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null)))
+ hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null))),
)).filter((x): x is CacheableUser => x != null);
return mentionedUsers;
diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts
index 88661865d..6097e3b6e 100644
--- a/packages/backend/src/remote/activitypub/models/person.ts
+++ b/packages/backend/src/remote/activitypub/models/person.ts
@@ -1,17 +1,8 @@
import { URL } from 'node:url';
import promiseLimit from 'promise-limit';
-import $, { Context } from 'cafy';
import config from '@/config/index.js';
-import Resolver from '../resolver.js';
-import { resolveImage } from './image.js';
-import { isCollectionOrOrderedCollection, isCollection, IActor, getApId, getOneApHrefNullable, IObject, isPropertyValue, IApPropertyValue, getApType, isActor } from '../type.js';
-import { fromHtml } from '../../../mfm/from-html.js';
-import { htmlToMfm } from '../misc/html-to-mfm.js';
-import { resolveNote, extractEmojis } from './note.js';
import { registerOrFetchInstanceDoc } from '@/services/register-or-fetch-instance-doc.js';
-import { extractApHashtags } from './tag.js';
-import { apLogger } from '../logger.js';
import { Note } from '@/models/entities/note.js';
import { updateUsertags } from '@/services/update-hashtag.js';
import { Users, Instances, DriveFiles, Followings, UserProfiles, UserPublickeys } from '@/models/index.js';
@@ -32,6 +23,14 @@ import { StatusError } from '@/misc/fetch.js';
import { uriPersonCache } from '@/services/user-cache.js';
import { publishInternalEvent } from '@/services/stream.js';
import { db } from '@/db/postgre.js';
+import { apLogger } from '../logger.js';
+import { htmlToMfm } from '../misc/html-to-mfm.js';
+import { fromHtml } from '../../../mfm/from-html.js';
+import { isCollectionOrOrderedCollection, isCollection, IActor, getApId, getOneApHrefNullable, IObject, isPropertyValue, IApPropertyValue, getApType, isActor } from '../type.js';
+import Resolver from '../resolver.js';
+import { extractApHashtags } from './tag.js';
+import { resolveNote, extractEmojis } from './note.js';
+import { resolveImage } from './image.js';
const logger = apLogger;
@@ -54,20 +53,33 @@ function validateActor(x: IObject, uri: string): IActor {
throw new Error(`invalid Actor type '${x.type}'`);
}
- const validate = (name: string, value: any, validater: Context) => {
- const e = validater.test(value);
- if (e) throw new Error(`invalid Actor: ${name} ${e.message}`);
- };
+ if (!(typeof x.id === 'string' && x.id.length > 0)) {
+ throw new Error('invalid Actor: wrong id');
+ }
- validate('id', x.id, $.default.str.min(1));
- validate('inbox', x.inbox, $.default.str.min(1));
- validate('preferredUsername', x.preferredUsername, $.default.str.min(1).max(128).match(/^\w([\w-.]*\w)?$/));
+ if (!(typeof x.inbox === 'string' && x.inbox.length > 0)) {
+ throw new Error('invalid Actor: wrong inbox');
+ }
+
+ if (!(typeof x.preferredUsername === 'string' && x.preferredUsername.length > 0 && x.preferredUsername.length <= 128 && /^\w([\w-.]*\w)?$/.test(x.preferredUsername))) {
+ throw new Error('invalid Actor: wrong username');
+ }
// These fields are only informational, and some AP software allows these
// fields to be very long. If they are too long, we cut them off. This way
// we can at least see these users and their activities.
- validate('name', truncate(x.name, nameLength), $.default.optional.nullable.str);
- validate('summary', truncate(x.summary, summaryLength), $.default.optional.nullable.str);
+ if (x.name) {
+ if (!(typeof x.name === 'string' && x.name.length > 0)) {
+ throw new Error('invalid Actor: wrong name');
+ }
+ x.name = truncate(x.name, nameLength);
+ }
+ if (x.summary) {
+ if (!(typeof x.summary === 'string' && x.summary.length > 0)) {
+ throw new Error('invalid Actor: wrong summary');
+ }
+ x.summary = truncate(x.summary, summaryLength);
+ }
const idHost = toPuny(new URL(x.id!).hostname);
if (idHost !== expectHost) {
@@ -271,7 +283,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise): Promise {
+export async function updatePerson(uri: string, resolver?: Resolver | null, hint?: IObject): Promise {
if (typeof uri !== 'string') throw new Error('uri is not string');
// URIがこのサーバーを指しているならスキップ
@@ -289,7 +301,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
if (resolver == null) resolver = new Resolver();
- const object = hint || await resolver.resolve(uri) as any;
+ const object = hint || await resolver.resolve(uri);
const person = validateActor(object, uri);
@@ -400,10 +412,10 @@ export async function resolvePerson(uri: string, resolver?: Resolver): Promise any
} = {
- 'misskey:authentication:twitter': (userId, screenName) => ({ userId, screenName }),
- 'misskey:authentication:github': (id, login) => ({ id, login }),
- 'misskey:authentication:discord': (id, name) => $discord(id, name),
-};
+ 'misskey:authentication:twitter': (userId, screenName) => ({ userId, screenName }),
+ 'misskey:authentication:github': (id, login) => ({ id, login }),
+ 'misskey:authentication:discord': (id, name) => $discord(id, name),
+ };
const $discord = (id: string, name: string) => {
if (typeof name !== 'string') {
@@ -461,7 +473,7 @@ export async function updateFeatured(userId: User['id']) {
// Resolve to (Ordered)Collection Object
const collection = await resolver.resolveCollection(user.featured);
- if (!isCollectionOrOrderedCollection(collection)) throw new Error(`Object is not Collection or OrderedCollection`);
+ if (!isCollectionOrOrderedCollection(collection)) throw new Error('Object is not Collection or OrderedCollection');
// Resolve to Object(may be Note) arrays
const unresolvedItems = isCollection(collection) ? collection.items : collection.orderedItems;
diff --git a/packages/backend/src/remote/activitypub/models/question.ts b/packages/backend/src/remote/activitypub/models/question.ts
index 9e75864c6..034501572 100644
--- a/packages/backend/src/remote/activitypub/models/question.ts
+++ b/packages/backend/src/remote/activitypub/models/question.ts
@@ -69,7 +69,7 @@ export async function updateQuestion(value: any) {
const oldCount = poll.votes[poll.choices.indexOf(choice)];
const newCount = apChoices!.filter(ap => ap.name === choice)[0].replies!.totalItems;
- if (oldCount != newCount) {
+ if (oldCount !== newCount) {
changed = true;
poll.votes[poll.choices.indexOf(choice)] = newCount;
}
diff --git a/packages/backend/src/remote/activitypub/renderer/flag.ts b/packages/backend/src/remote/activitypub/renderer/flag.ts
index 6fbc11580..58eadddba 100644
--- a/packages/backend/src/remote/activitypub/renderer/flag.ts
+++ b/packages/backend/src/remote/activitypub/renderer/flag.ts
@@ -5,7 +5,7 @@ import { getInstanceActor } from '@/services/instance-actor.js';
// to anonymise reporters, the reporting actor must be a system user
// object has to be a uri or array of uris
-export const renderFlag = (user: ILocalUser, object: [string], content: string): IActivity => {
+export const renderFlag = (user: ILocalUser, object: [string], content: string) => {
return {
type: 'Flag',
actor: `${config.url}/users/${user.id}`,
diff --git a/packages/backend/src/remote/activitypub/renderer/note.ts b/packages/backend/src/remote/activitypub/renderer/note.ts
index 679c8bbfe..e8d429e5d 100644
--- a/packages/backend/src/remote/activitypub/renderer/note.ts
+++ b/packages/backend/src/remote/activitypub/renderer/note.ts
@@ -1,15 +1,15 @@
-import renderDocument from './document.js';
-import renderHashtag from './hashtag.js';
-import renderMention from './mention.js';
-import renderEmoji from './emoji.js';
+import { In, IsNull } from 'typeorm';
import config from '@/config/index.js';
-import toHtml from '../misc/get-note-html.js';
import { Note, IMentionedRemoteUsers } from '@/models/entities/note.js';
import { DriveFile } from '@/models/entities/drive-file.js';
import { DriveFiles, Notes, Users, Emojis, Polls } from '@/models/index.js';
-import { In, IsNull } from 'typeorm';
import { Emoji } from '@/models/entities/emoji.js';
import { Poll } from '@/models/entities/poll.js';
+import toHtml from '../misc/get-note-html.js';
+import renderEmoji from './emoji.js';
+import renderMention from './mention.js';
+import renderHashtag from './hashtag.js';
+import renderDocument from './document.js';
export default async function renderNote(note: Note, dive = true, isTalk = false): Promise> {
const getPromisedFiles = async (ids: string[]) => {
@@ -83,7 +83,7 @@ export default async function renderNote(note: Note, dive = true, isTalk = false
const files = await getPromisedFiles(note.fileIds);
const text = note.text;
- let poll: Poll | null;
+ let poll: Poll | null = null;
if (note.hasPoll) {
poll = await Polls.findOneBy({ noteId: note.id });
@@ -159,7 +159,7 @@ export async function getEmojis(names: string[]): Promise {
names.map(name => Emojis.findOneBy({
name,
host: IsNull(),
- }))
+ })),
);
return emojis.filter(emoji => emoji != null) as Emoji[];
diff --git a/packages/backend/src/remote/activitypub/resolver.ts b/packages/backend/src/remote/activitypub/resolver.ts
index c1269c75c..334eae984 100644
--- a/packages/backend/src/remote/activitypub/resolver.ts
+++ b/packages/backend/src/remote/activitypub/resolver.ts
@@ -2,10 +2,10 @@ import config from '@/config/index.js';
import { getJson } from '@/misc/fetch.js';
import { ILocalUser } from '@/models/entities/user.js';
import { getInstanceActor } from '@/services/instance-actor.js';
-import { signedGet } from './request.js';
-import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
import { extractDbHost } from '@/misc/convert-host.js';
+import { signedGet } from './request.js';
+import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type.js';
export default class Resolver {
private history: Set;
@@ -56,13 +56,13 @@ export default class Resolver {
this.user = await getInstanceActor();
}
- const object = this.user
+ const object = (this.user
? await signedGet(value, this.user)
- : await getJson(value, 'application/activity+json, application/ld+json');
+ : await getJson(value, 'application/activity+json, application/ld+json')) as IObject;
if (object == null || (
Array.isArray(object['@context']) ?
- !object['@context'].includes('https://www.w3.org/ns/activitystreams') :
+ !(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') :
object['@context'] !== 'https://www.w3.org/ns/activitystreams'
)) {
throw new Error('invalid response');
diff --git a/packages/backend/src/remote/activitypub/type.ts b/packages/backend/src/remote/activitypub/type.ts
index 2051d2624..ef5b98b59 100644
--- a/packages/backend/src/remote/activitypub/type.ts
+++ b/packages/backend/src/remote/activitypub/type.ts
@@ -2,7 +2,7 @@ export type obj = { [x: string]: any };
export type ApObject = IObject | string | (IObject | string)[];
export interface IObject {
- '@context': string | obj | obj[];
+ '@context': string | string[] | obj | obj[];
type: string | string[];
id?: string;
summary?: string;
@@ -48,7 +48,7 @@ export function getOneApId(value: ApObject): string {
export function getApId(value: string | IObject): string {
if (typeof value === 'string') return value;
if (typeof value.id === 'string') return value.id;
- throw new Error(`cannot detemine id`);
+ throw new Error('cannot detemine id');
}
/**
@@ -57,7 +57,7 @@ export function getApId(value: string | IObject): string {
export function getApType(value: IObject): string {
if (typeof value.type === 'string') return value.type;
if (Array.isArray(value.type) && typeof value.type[0] === 'string') return value.type[0];
- throw new Error(`cannot detect type`);
+ throw new Error('cannot detect type');
}
export function getOneApHrefNullable(value: ApObject | undefined): string | undefined {
diff --git a/packages/backend/src/remote/webfinger.ts b/packages/backend/src/remote/webfinger.ts
index 9d3bfab24..337df34c2 100644
--- a/packages/backend/src/remote/webfinger.ts
+++ b/packages/backend/src/remote/webfinger.ts
@@ -15,7 +15,7 @@ type IWebFinger = {
export default async function(query: string): Promise {
const url = genUrl(query);
- return await getJson(url, 'application/jrd+json, application/json');
+ return await getJson(url, 'application/jrd+json, application/json') as IWebFinger;
}
function genUrl(query: string) {
diff --git a/packages/backend/src/server/activitypub/followers.ts b/packages/backend/src/server/activitypub/followers.ts
index 4d4f73316..beb48713a 100644
--- a/packages/backend/src/server/activitypub/followers.ts
+++ b/packages/backend/src/server/activitypub/followers.ts
@@ -1,32 +1,26 @@
import Router from '@koa/router';
+import { FindOptionsWhere, IsNull, LessThan } from 'typeorm';
import config from '@/config/index.js';
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id.js';
import * as url from '@/prelude/url.js';
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js';
import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js';
import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js';
-import { setResponseType } from '../activitypub.js';
import { Users, Followings, UserProfiles } from '@/models/index.js';
-import { IsNull, LessThan } from 'typeorm';
+import { Following } from '@/models/entities/following.js';
+import { setResponseType } from '../activitypub.js';
export default async (ctx: Router.RouterContext) => {
const userId = ctx.params.user;
- // Get 'cursor' parameter
- const [cursor, cursorErr] = $.default.optional.type(ID).get(ctx.request.query.cursor);
-
- // Get 'page' parameter
- const pageErr = !$.default.optional.str.or(['true', 'false']).ok(ctx.request.query.page);
- const page: boolean = ctx.request.query.page === 'true';
-
- // Validate parameters
- if (cursorErr || pageErr) {
+ const cursor = ctx.request.query.cursor;
+ if (cursor != null && typeof cursor !== 'string') {
ctx.status = 400;
return;
}
+ const page = ctx.request.query.page === 'true';
+
const user = await Users.findOneBy({
id: userId,
host: IsNull(),
@@ -57,7 +51,7 @@ export default async (ctx: Router.RouterContext) => {
if (page) {
const query = {
followeeId: user.id,
- } as any;
+ } as FindOptionsWhere;
// カーソルが指定されている場合
if (cursor) {
@@ -86,7 +80,7 @@ export default async (ctx: Router.RouterContext) => {
inStock ? `${partOf}?${url.query({
page: 'true',
cursor: followings[followings.length - 1].id,
- })}` : undefined
+ })}` : undefined,
);
ctx.body = renderActivity(rendered);
diff --git a/packages/backend/src/server/activitypub/following.ts b/packages/backend/src/server/activitypub/following.ts
index 0af1f424f..3a25a6316 100644
--- a/packages/backend/src/server/activitypub/following.ts
+++ b/packages/backend/src/server/activitypub/following.ts
@@ -1,33 +1,26 @@
import Router from '@koa/router';
+import { LessThan, IsNull, FindOptionsWhere } from 'typeorm';
import config from '@/config/index.js';
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id.js';
import * as url from '@/prelude/url.js';
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js';
import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js';
import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js';
-import { setResponseType } from '../activitypub.js';
import { Users, Followings, UserProfiles } from '@/models/index.js';
-import { LessThan, IsNull, FindOptionsWhere } from 'typeorm';
import { Following } from '@/models/entities/following.js';
+import { setResponseType } from '../activitypub.js';
export default async (ctx: Router.RouterContext) => {
const userId = ctx.params.user;
- // Get 'cursor' parameter
- const [cursor, cursorErr] = $.default.optional.type(ID).get(ctx.request.query.cursor);
-
- // Get 'page' parameter
- const pageErr = !$.default.optional.str.or(['true', 'false']).ok(ctx.request.query.page);
- const page: boolean = ctx.request.query.page === 'true';
-
- // Validate parameters
- if (cursorErr || pageErr) {
+ const cursor = ctx.request.query.cursor;
+ if (cursor != null && typeof cursor !== 'string') {
ctx.status = 400;
return;
}
+ const page = ctx.request.query.page === 'true';
+
const user = await Users.findOneBy({
id: userId,
host: IsNull(),
@@ -87,7 +80,7 @@ export default async (ctx: Router.RouterContext) => {
inStock ? `${partOf}?${url.query({
page: 'true',
cursor: followings[followings.length - 1].id,
- })}` : undefined
+ })}` : undefined,
);
ctx.body = renderActivity(rendered);
diff --git a/packages/backend/src/server/activitypub/outbox.ts b/packages/backend/src/server/activitypub/outbox.ts
index 6b9592bcf..7a2586998 100644
--- a/packages/backend/src/server/activitypub/outbox.ts
+++ b/packages/backend/src/server/activitypub/outbox.ts
@@ -1,36 +1,37 @@
import Router from '@koa/router';
+import { Brackets, IsNull } from 'typeorm';
import config from '@/config/index.js';
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id.js';
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js';
import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js';
-import { setResponseType } from '../activitypub.js';
import renderNote from '@/remote/activitypub/renderer/note.js';
import renderCreate from '@/remote/activitypub/renderer/create.js';
import renderAnnounce from '@/remote/activitypub/renderer/announce.js';
import { countIf } from '@/prelude/array.js';
import * as url from '@/prelude/url.js';
import { Users, Notes } from '@/models/index.js';
-import { makePaginationQuery } from '../api/common/make-pagination-query.js';
-import { Brackets, IsNull } from 'typeorm';
import { Note } from '@/models/entities/note.js';
+import { makePaginationQuery } from '../api/common/make-pagination-query.js';
+import { setResponseType } from '../activitypub.js';
export default async (ctx: Router.RouterContext) => {
const userId = ctx.params.user;
- // Get 'sinceId' parameter
- const [sinceId, sinceIdErr] = $.default.optional.type(ID).get(ctx.request.query.since_id);
+ const sinceId = ctx.request.query.since_id;
+ if (sinceId != null && typeof sinceId !== 'string') {
+ ctx.status = 400;
+ return;
+ }
- // Get 'untilId' parameter
- const [untilId, untilIdErr] = $.default.optional.type(ID).get(ctx.request.query.until_id);
+ const untilId = ctx.request.query.until_id;
+ if (untilId != null && typeof untilId !== 'string') {
+ ctx.status = 400;
+ return;
+ }
- // Get 'page' parameter
- const pageErr = !$.default.optional.str.or(['true', 'false']).ok(ctx.request.query.page);
- const page: boolean = ctx.request.query.page === 'true';
+ const page = ctx.request.query.page === 'true';
- // Validate parameters
- if (sinceIdErr || untilIdErr || pageErr || countIf(x => x != null, [sinceId, untilId]) > 1) {
+ if (countIf(x => x != null, [sinceId, untilId]) > 1) {
ctx.status = 400;
return;
}
@@ -52,8 +53,8 @@ export default async (ctx: Router.RouterContext) => {
const query = makePaginationQuery(Notes.createQueryBuilder('note'), sinceId, untilId)
.andWhere('note.userId = :userId', { userId: user.id })
.andWhere(new Brackets(qb => { qb
- .where(`note.visibility = 'public'`)
- .orWhere(`note.visibility = 'home'`);
+ .where('note.visibility = \'public\'')
+ .orWhere('note.visibility = \'home\'');
}))
.andWhere('note.localOnly = FALSE');
@@ -76,7 +77,7 @@ export default async (ctx: Router.RouterContext) => {
notes.length ? `${partOf}?${url.query({
page: 'true',
until_id: notes[notes.length - 1].id,
- })}` : undefined
+ })}` : undefined,
);
ctx.body = renderActivity(rendered);
@@ -85,7 +86,7 @@ export default async (ctx: Router.RouterContext) => {
// index page
const rendered = renderOrderedCollection(partOf, user.notesCount,
`${partOf}?page=true`,
- `${partOf}?page=true&since_id=000000000000000000000000`
+ `${partOf}?page=true&since_id=000000000000000000000000`,
);
ctx.body = renderActivity(rendered);
ctx.set('Cache-Control', 'public, max-age=180');
diff --git a/packages/backend/src/server/api/2fa.ts b/packages/backend/src/server/api/2fa.ts
index e1c226979..96b9316e4 100644
--- a/packages/backend/src/server/api/2fa.ts
+++ b/packages/backend/src/server/api/2fa.ts
@@ -1,6 +1,6 @@
import * as crypto from 'node:crypto';
-import config from '@/config/index.js';
import * as jsrsasign from 'jsrsasign';
+import config from '@/config/index.js';
const ECC_PRELUDE = Buffer.from([0x04]);
const NULL_BYTE = Buffer.from([0]);
@@ -121,14 +121,14 @@ export function verifyLogin({
signature: Buffer,
challenge: string
}) {
- if (clientData.type != 'webauthn.get') {
+ if (clientData.type !== 'webauthn.get') {
throw new Error('type is not webauthn.get');
}
- if (hash(clientData.challenge).toString('hex') != challenge) {
+ if (hash(clientData.challenge).toString('hex') !== challenge) {
throw new Error('challenge mismatch');
}
- if (clientData.origin != config.scheme + '://' + config.host) {
+ if (clientData.origin !== config.scheme + '://' + config.host) {
throw new Error('origin mismatch');
}
@@ -145,14 +145,14 @@ export function verifyLogin({
export const procedures = {
none: {
- verify({ publicKey }: {publicKey: Map}) {
+ verify({ publicKey }: { publicKey: Map }) {
const negTwo = publicKey.get(-2);
- if (!negTwo || negTwo.length != 32) {
+ if (!negTwo || negTwo.length !== 32) {
throw new Error('invalid or no -2 key given');
}
const negThree = publicKey.get(-3);
- if (!negThree || negThree.length != 32) {
+ if (!negThree || negThree.length !== 32) {
throw new Error('invalid or no -3 key given');
}
@@ -183,7 +183,7 @@ export const procedures = {
rpIdHash: Buffer,
credentialId: Buffer,
}) {
- if (attStmt.alg != -7) {
+ if (attStmt.alg !== -7) {
throw new Error('alg mismatch');
}
@@ -196,11 +196,11 @@ export const procedures = {
const negTwo = publicKey.get(-2);
- if (!negTwo || negTwo.length != 32) {
+ if (!negTwo || negTwo.length !== 32) {
throw new Error('invalid or no -2 key given');
}
const negThree = publicKey.get(-3);
- if (!negThree || negThree.length != 32) {
+ if (!negThree || negThree.length !== 32) {
throw new Error('invalid or no -3 key given');
}
@@ -263,7 +263,7 @@ export const procedures = {
.map((key: any) => PEMString(key))
.concat([GSR2]);
- if (getCertSubject(certificateChain[0]).CN != 'attest.android.com') {
+ if (getCertSubject(certificateChain[0]).CN !== 'attest.android.com') {
throw new Error('invalid common name');
}
@@ -283,11 +283,11 @@ export const procedures = {
const negTwo = publicKey.get(-2);
- if (!negTwo || negTwo.length != 32) {
+ if (!negTwo || negTwo.length !== 32) {
throw new Error('invalid or no -2 key given');
}
const negThree = publicKey.get(-3);
- if (!negThree || negThree.length != 32) {
+ if (!negThree || negThree.length !== 32) {
throw new Error('invalid or no -3 key given');
}
@@ -332,11 +332,11 @@ export const procedures = {
const negTwo = publicKey.get(-2);
- if (!negTwo || negTwo.length != 32) {
+ if (!negTwo || negTwo.length !== 32) {
throw new Error('invalid or no -2 key given');
}
const negThree = publicKey.get(-3);
- if (!negThree || negThree.length != 32) {
+ if (!negThree || negThree.length !== 32) {
throw new Error('invalid or no -3 key given');
}
@@ -353,7 +353,7 @@ export const procedures = {
// https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-ecdaa-algorithm-v2.0-id-20180227.html#ecdaa-verify-operation
throw new Error('ECDAA-Verify is not supported');
} else {
- if (attStmt.alg != -7) throw new Error('alg mismatch');
+ if (attStmt.alg !== -7) throw new Error('alg mismatch');
throw new Error('self attestation is not supported');
}
@@ -377,7 +377,7 @@ export const procedures = {
credentialId: Buffer
}) {
const x5c: Buffer[] = attStmt.x5c;
- if (x5c.length != 1) {
+ if (x5c.length !== 1) {
throw new Error('x5c length does not match expectation');
}
@@ -387,11 +387,11 @@ export const procedures = {
const negTwo: Buffer = publicKey.get(-2);
- if (!negTwo || negTwo.length != 32) {
+ if (!negTwo || negTwo.length !== 32) {
throw new Error('invalid or no -2 key given');
}
const negThree: Buffer = publicKey.get(-3);
- if (!negThree || negThree.length != 32) {
+ if (!negThree || negThree.length !== 32) {
throw new Error('invalid or no -3 key given');
}
diff --git a/packages/backend/src/server/api/common/read-messaging-message.ts b/packages/backend/src/server/api/common/read-messaging-message.ts
index 3638518e6..c4c18ffa0 100644
--- a/packages/backend/src/server/api/common/read-messaging-message.ts
+++ b/packages/backend/src/server/api/common/read-messaging-message.ts
@@ -1,6 +1,7 @@
import { publishMainStream, publishGroupMessagingStream } from '@/services/stream.js';
import { publishMessagingStream } from '@/services/stream.js';
import { publishMessagingIndexStream } from '@/services/stream.js';
+import { pushNotification } from '@/services/push-notification.js';
import { User, IRemoteUser } from '@/models/entities/user.js';
import { MessagingMessage } from '@/models/entities/messaging-message.js';
import { MessagingMessages, UserGroupJoinings, Users } from '@/models/index.js';
@@ -50,6 +51,21 @@ export async function readUserMessagingMessage(
if (!await Users.getHasUnreadMessagingMessage(userId)) {
// 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行
publishMainStream(userId, 'readAllMessagingMessages');
+ pushNotification(userId, 'readAllMessagingMessages', undefined);
+ } else {
+ // そのユーザーとのメッセージで未読がなければイベント発行
+ const count = await MessagingMessages.count({
+ where: {
+ userId: otherpartyId,
+ recipientId: userId,
+ isRead: false,
+ },
+ take: 1
+ });
+
+ if (!count) {
+ pushNotification(userId, 'readAllMessagingMessagesOfARoom', { userId: otherpartyId });
+ }
}
}
@@ -104,6 +120,19 @@ export async function readGroupMessagingMessage(
if (!await Users.getHasUnreadMessagingMessage(userId)) {
// 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行
publishMainStream(userId, 'readAllMessagingMessages');
+ pushNotification(userId, 'readAllMessagingMessages', undefined);
+ } else {
+ // そのグループにおいて未読がなければイベント発行
+ const unreadExist = await MessagingMessages.createQueryBuilder('message')
+ .where(`message.groupId = :groupId`, { groupId: groupId })
+ .andWhere('message.userId != :userId', { userId: userId })
+ .andWhere('NOT (:userId = ANY(message.reads))', { userId: userId })
+ .andWhere('message.createdAt > :joinedAt', { joinedAt: joining.createdAt }) // 自分が加入する前の会話については、未読扱いしない
+ .getOne().then(x => x != null);
+
+ if (!unreadExist) {
+ pushNotification(userId, 'readAllMessagingMessagesOfARoom', { groupId });
+ }
}
}
diff --git a/packages/backend/src/server/api/common/read-notification.ts b/packages/backend/src/server/api/common/read-notification.ts
index 1f575042a..0dad35bcc 100644
--- a/packages/backend/src/server/api/common/read-notification.ts
+++ b/packages/backend/src/server/api/common/read-notification.ts
@@ -1,4 +1,5 @@
import { publishMainStream } from '@/services/stream.js';
+import { pushNotification } from '@/services/push-notification.js';
import { User } from '@/models/entities/user.js';
import { Notification } from '@/models/entities/notification.js';
import { Notifications, Users } from '@/models/index.js';
@@ -16,28 +17,29 @@ export async function readNotification(
isRead: true,
});
- post(userId);
+ if (!await Users.getHasUnreadNotification(userId)) return postReadAllNotifications(userId);
+ else return postReadNotifications(userId, notificationIds);
}
export async function readNotificationByQuery(
userId: User['id'],
query: Record
) {
- // Update documents
- await Notifications.update({
+ const notificationIds = await Notifications.find({
...query,
notifieeId: userId,
isRead: false,
- }, {
- isRead: true,
- });
+ }).then(notifications => notifications.map(notification => notification.id));
- post(userId);
+ return readNotification(userId, notificationIds);
}
-async function post(userId: User['id']) {
- if (!await Users.getHasUnreadNotification(userId)) {
- // 全ての(いままで未読だった)通知を(これで)読みましたよというイベントを発行
- publishMainStream(userId, 'readAllNotifications');
- }
+function postReadAllNotifications(userId: User['id']) {
+ publishMainStream(userId, 'readAllNotifications');
+ return pushNotification(userId, 'readAllNotifications', undefined);
+}
+
+function postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) {
+ publishMainStream(userId, 'readNotifications', notificationIds);
+ return pushNotification(userId, 'readNotifications', { notificationIds });
}
diff --git a/packages/backend/src/server/api/common/signin.ts b/packages/backend/src/server/api/common/signin.ts
index f1dccee2c..038fd8d96 100644
--- a/packages/backend/src/server/api/common/signin.ts
+++ b/packages/backend/src/server/api/common/signin.ts
@@ -9,7 +9,7 @@ import { publishMainStream } from '@/services/stream.js';
export default function(ctx: Koa.Context, user: ILocalUser, redirect = false) {
if (redirect) {
//#region Cookie
- ctx.cookies.set('igi', user.token, {
+ ctx.cookies.set('igi', user.token!, {
path: '/',
// SEE: https://github.com/koajs/koa/issues/974
// When using a SSL proxy it should be configured to add the "X-Forwarded-Proto: https" header
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index 6b4eff078..e2db03f13 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -1,5 +1,6 @@
import { Schema } from '@/misc/schema.js';
+import * as ep___admin_meta from './endpoints/admin/meta.js';
import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.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';
@@ -201,6 +202,11 @@ import * as ep___i_unpin from './endpoints/i/unpin.js';
import * as ep___i_updateEmail from './endpoints/i/update-email.js';
import * as ep___i_update from './endpoints/i/update.js';
import * as ep___i_userGroupInvites from './endpoints/i/user-group-invites.js';
+import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js';
+import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
+import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
+import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js';
+import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js';
import * as ep___messaging_history from './endpoints/messaging/history.js';
import * as ep___messaging_messages from './endpoints/messaging/messages.js';
import * as ep___messaging_messages_create from './endpoints/messaging/messages/create.js';
@@ -304,6 +310,7 @@ import * as ep___users_show from './endpoints/users/show.js';
import * as ep___users_stats from './endpoints/users/stats.js';
const eps = [
+ ['admin/meta', ep___admin_meta],
['admin/abuse-user-reports', ep___admin_abuseUserReports],
['admin/accounts/create', ep___admin_accounts_create],
['admin/accounts/delete', ep___admin_accounts_delete],
@@ -505,6 +512,11 @@ const eps = [
['i/update-email', ep___i_updateEmail],
['i/update', ep___i_update],
['i/user-group-invites', ep___i_userGroupInvites],
+ ['i/webhooks/create', ep___i_webhooks_create],
+ ['i/webhooks/list', ep___i_webhooks_list],
+ ['i/webhooks/show', ep___i_webhooks_show],
+ ['i/webhooks/update', ep___i_webhooks_update],
+ ['i/webhooks/delete', ep___i_webhooks_delete],
['messaging/history', ep___messaging_history],
['messaging/messages', ep___messaging_messages],
['messaging/messages/create', ep___messaging_messages_create],
diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts
index 1d8eb1d61..7a5758d75 100644
--- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts
+++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts
@@ -1,5 +1,6 @@
-import define from '../../../define.js';
import { Announcements, AnnouncementReads } from '@/models/index.js';
+import { Announcement } from '@/models/entities/announcement.js';
+import define from '../../../define.js';
import { makePaginationQuery } from '../../../common/make-pagination-query.js';
export const meta = {
@@ -68,11 +69,21 @@ export default define(meta, paramDef, async (ps) => {
const announcements = await query.take(ps.limit).getMany();
+ const reads = new Map();
+
for (const announcement of announcements) {
- (announcement as any).reads = await AnnouncementReads.countBy({
+ reads.set(announcement, await AnnouncementReads.countBy({
announcementId: announcement.id,
- });
+ }));
}
- return announcements;
+ return announcements.map(announcement => ({
+ id: announcement.id,
+ createdAt: announcement.createdAt.toISOString(),
+ updatedAt: announcement.updatedAt?.toISOString() ?? null,
+ title: announcement.title,
+ text: announcement.text,
+ imageUrl: announcement.imageUrl,
+ reads: reads.get(announcement)!,
+ }));
});
diff --git a/packages/backend/src/server/api/endpoints/admin/drive/files.ts b/packages/backend/src/server/api/endpoints/admin/drive/files.ts
index 646d85a1e..119c4db19 100644
--- a/packages/backend/src/server/api/endpoints/admin/drive/files.ts
+++ b/packages/backend/src/server/api/endpoints/admin/drive/files.ts
@@ -27,7 +27,12 @@ export const paramDef = {
untilId: { type: 'string', format: 'misskey:id' },
type: { type: 'string', nullable: true, pattern: /^[a-zA-Z0-9\/\-*]+$/.toString().slice(1, -1) },
origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" },
- hostname: { type: 'string', nullable: true, default: null },
+ hostname: {
+ type: 'string',
+ nullable: true,
+ default: null,
+ description: 'The local host is represented with `null`.',
+ },
},
required: [],
} as const;
diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts
index 4b27fc018..039df74f1 100644
--- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts
+++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts
@@ -40,6 +40,7 @@ export const meta = {
userHost: {
type: 'string',
optional: false, nullable: true,
+ description: 'The local host is represented with `null`.',
},
md5: {
type: 'string',
@@ -151,11 +152,20 @@ export const meta = {
export const paramDef = {
type: 'object',
- properties: {
- fileId: { type: 'string', format: 'misskey:id' },
- url: { type: 'string' },
- },
- required: [],
+ anyOf: [
+ {
+ properties: {
+ fileId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['fileId'],
+ },
+ {
+ properties: {
+ url: { type: 'string' },
+ },
+ required: ['url'],
+ },
+ ],
} as const;
// eslint-disable-next-line import/no-default-export
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts
index f19c3ddbd..d16689a28 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts
@@ -40,6 +40,7 @@ export const meta = {
host: {
type: 'string',
optional: false, nullable: true,
+ description: 'The local host is represented with `null`.',
},
url: {
type: 'string',
@@ -54,7 +55,12 @@ export const paramDef = {
type: 'object',
properties: {
query: { type: 'string', nullable: true, default: null },
- host: { type: 'string', nullable: true, default: null },
+ host: {
+ type: 'string',
+ nullable: true,
+ default: null,
+ description: 'Use `null` to represent the local host.',
+ },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
index f488a71a0..6192978fa 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
@@ -38,8 +38,9 @@ export const meta = {
optional: false, nullable: true,
},
host: {
- type: 'string',
- optional: false, nullable: true,
+ type: 'null',
+ optional: false,
+ description: 'The local host is represented with `null`. The field exists for compatibility with other API endpoints that return files.',
},
url: {
type: 'string',
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
index 6063f3e3b..cff58d617 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
@@ -17,7 +17,11 @@ export const paramDef = {
ids: { type: 'array', items: {
type: 'string', format: 'misskey:id',
} },
- category: { type: 'string', nullable: true },
+ category: {
+ type: 'string',
+ nullable: true,
+ description: 'Use `null` to reset the category.',
+ },
},
required: ['ids'],
} as const;
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
index e26514e0c..5b547b3b7 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
@@ -23,7 +23,11 @@ export const paramDef = {
properties: {
id: { type: 'string', format: 'misskey:id' },
name: { type: 'string' },
- category: { type: 'string', nullable: true },
+ category: {
+ type: 'string',
+ nullable: true,
+ description: 'Use `null` to reset the category.',
+ },
aliases: { type: 'array', items: {
type: 'string',
} },
diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts
index b85a677e8..a01e9f3c6 100644
--- a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts
+++ b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts
@@ -1,5 +1,6 @@
import define from '../../../define.js';
import { Users } from '@/models/index.js';
+import { publishInternalEvent } from '@/services/stream.js';
export const meta = {
tags: ['admin'],
diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts
index 1ec86fef2..1575d81d5 100644
--- a/packages/backend/src/server/api/endpoints/admin/show-users.ts
+++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts
@@ -1,5 +1,5 @@
-import define from '../../define.js';
import { Users } from '@/models/index.js';
+import define from '../../define.js';
export const meta = {
tags: ['admin'],
@@ -24,10 +24,15 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
offset: { type: 'integer', default: 0 },
sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt'] },
- state: { type: 'string', enum: ['all', 'available', 'admin', 'moderator', 'adminOrModerator', 'silenced', 'suspended'], default: "all" },
- origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" },
- username: { type: 'string', default: null },
- hostname: { type: 'string', default: null },
+ state: { type: 'string', enum: ['all', 'alive', 'available', 'admin', 'moderator', 'adminOrModerator', 'silenced', 'suspended'], default: 'all' },
+ origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'local' },
+ username: { type: 'string', nullable: true, default: null },
+ hostname: {
+ type: 'string',
+ nullable: true,
+ default: null,
+ description: 'The local host is represented with `null`.',
+ },
},
required: [],
} as const;
diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
index 3c39bf0f3..b23ee9e3d 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -397,12 +397,14 @@ export default define(meta, paramDef, async (ps, me) => {
}
await db.transaction(async transactionalEntityManager => {
- const meta = await transactionalEntityManager.findOne(Meta, {
+ const metas = await transactionalEntityManager.find(Meta, {
order: {
id: 'DESC',
},
});
+ const meta = metas[0];
+
if (meta) {
await transactionalEntityManager.update(Meta, meta.id, set);
} else {
diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts
index 004e4c131..8aac55b4a 100644
--- a/packages/backend/src/server/api/endpoints/antennas/notes.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts
@@ -57,13 +57,9 @@ export default define(meta, paramDef, async (ps, user) => {
throw new ApiError(meta.errors.noSuchAntenna);
}
- const antennaQuery = AntennaNotes.createQueryBuilder('joining')
- .select('joining.noteId')
- .where('joining.antennaId = :antennaId', { antennaId: antenna.id });
-
const query = makePaginationQuery(Notes.createQueryBuilder('note'),
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
- .andWhere(`note.id IN (${ antennaQuery.getQuery() })`)
+ .innerJoin(AntennaNotes.metadata.targetName, 'antennaNote', 'antennaNote.noteId = note.id')
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')
@@ -75,7 +71,7 @@ export default define(meta, paramDef, async (ps, user) => {
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
- .setParameters(antennaQuery.getParameters());
+ .andWhere('antennaNote.antennaId = :antennaId', { antennaId: antenna.id });
generateVisibilityQuery(query, user);
generateMutedUserQuery(query, user);
diff --git a/packages/backend/src/server/api/endpoints/channels/pin-note.ts b/packages/backend/src/server/api/endpoints/channels/pin-note.ts
deleted file mode 100644
index e69de29bb..000000000
diff --git a/packages/backend/src/server/api/endpoints/clips/create.ts b/packages/backend/src/server/api/endpoints/clips/create.ts
index a2dbef12e..4afe4222a 100644
--- a/packages/backend/src/server/api/endpoints/clips/create.ts
+++ b/packages/backend/src/server/api/endpoints/clips/create.ts
@@ -20,7 +20,7 @@ export const paramDef = {
type: 'object',
properties: {
name: { type: 'string', minLength: 1, maxLength: 100 },
- isPublic: { type: 'boolean' },
+ isPublic: { type: 'boolean', default: false },
description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
},
required: ['name'],
diff --git a/packages/backend/src/server/api/endpoints/clips/notes.ts b/packages/backend/src/server/api/endpoints/clips/notes.ts
index 4b6782fca..4ace747ef 100644
--- a/packages/backend/src/server/api/endpoints/clips/notes.ts
+++ b/packages/backend/src/server/api/endpoints/clips/notes.ts
@@ -57,12 +57,8 @@ export default define(meta, paramDef, async (ps, user) => {
throw new ApiError(meta.errors.noSuchClip);
}
- const clipQuery = ClipNotes.createQueryBuilder('joining')
- .select('joining.noteId')
- .where('joining.clipId = :clipId', { clipId: clip.id });
-
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
- .andWhere(`note.id IN (${ clipQuery.getQuery() })`)
+ .innerJoin(ClipNotes.metadata.targetName, 'clipNote', 'clipNote.noteId = note.id')
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')
@@ -74,7 +70,7 @@ export default define(meta, paramDef, async (ps, user) => {
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
- .setParameters(clipQuery.getParameters());
+ .andWhere('clipNote.clipId = :clipId', { clipId: clip.id });
if (user) {
generateVisibilityQuery(query, user);
diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts
index b6a2cf720..0939ae336 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/create.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts
@@ -48,7 +48,6 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-// @ts-ignore
export default define(meta, paramDef, async (ps, user, _, file, cleanup) => {
// Get 'name' parameter
let name = ps.name || file.originalname;
diff --git a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts
index f9b4ea89e..0b74cb9f0 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts
@@ -1,5 +1,5 @@
-import define from '../../../define.js';
import { DriveFiles } from '@/models/index.js';
+import define from '../../../define.js';
export const meta = {
tags: ['drive'],
diff --git a/packages/backend/src/server/api/endpoints/drive/files/show.ts b/packages/backend/src/server/api/endpoints/drive/files/show.ts
index c8e9d3dd9..fb19345fe 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/show.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts
@@ -1,7 +1,7 @@
-import define from '../../../define.js';
-import { ApiError } from '../../../error.js';
import { DriveFile } from '@/models/entities/drive-file.js';
import { DriveFiles, Users } from '@/models/index.js';
+import define from '../../../define.js';
+import { ApiError } from '../../../error.js';
export const meta = {
tags: ['drive'],
@@ -28,27 +28,30 @@ export const meta = {
code: 'ACCESS_DENIED',
id: '25b73c73-68b1-41d0-bad1-381cfdf6579f',
},
-
- fileIdOrUrlRequired: {
- message: 'fileId or url required.',
- code: 'INVALID_PARAM',
- id: '89674805-722c-440c-8d88-5641830dc3e4',
- },
},
} as const;
export const paramDef = {
type: 'object',
- properties: {
- fileId: { type: 'string', format: 'misskey:id' },
- url: { type: 'string' },
- },
- required: [],
+ anyOf: [
+ {
+ properties: {
+ fileId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['fileId'],
+ },
+ {
+ properties: {
+ url: { type: 'string' },
+ },
+ required: ['url'],
+ },
+ ],
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- let file: DriveFile | undefined;
+ let file: DriveFile | null = null;
if (ps.fileId) {
file = await DriveFiles.findOneBy({ id: ps.fileId });
@@ -62,8 +65,6 @@ export default define(meta, paramDef, async (ps, user) => {
thumbnailUrl: ps.url,
}],
});
- } else {
- throw new ApiError(meta.errors.fileIdOrUrlRequired);
}
if (file == null) {
diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts
index e27297176..07e5c07c6 100644
--- a/packages/backend/src/server/api/endpoints/federation/instances.ts
+++ b/packages/backend/src/server/api/endpoints/federation/instances.ts
@@ -22,7 +22,7 @@ export const meta = {
export const paramDef = {
type: 'object',
properties: {
- host: { type: 'string', nullable: true },
+ host: { type: 'string', nullable: true, description: 'Omit or use `null` to not filter by host.' },
blocked: { type: 'boolean', nullable: true },
notResponding: { type: 'boolean', nullable: true },
suspended: { type: 'boolean', nullable: true },
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
index 0116a55fb..1afb34bfd 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
@@ -50,10 +50,10 @@ export default define(meta, paramDef, async (ps, user) => {
const clientData = JSON.parse(ps.clientDataJSON);
- if (clientData.type != 'webauthn.create') {
+ if (clientData.type !== 'webauthn.create') {
throw new Error('not a creation attestation');
}
- if (clientData.origin != config.scheme + '://' + config.host) {
+ if (clientData.origin !== config.scheme + '://' + config.host) {
throw new Error('origin mismatch');
}
@@ -78,7 +78,7 @@ export default define(meta, paramDef, async (ps, user) => {
const credentialId = authData.slice(55, 55 + credentialIdLength);
const publicKeyData = authData.slice(55 + credentialIdLength);
const publicKey: Map = await cborDecodeFirst(publicKeyData);
- if (publicKey.get(3) != -7) {
+ if (publicKey.get(3) !== -7) {
throw new Error('alg mismatch');
}
diff --git a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts
index 3301808e7..68bd103a6 100644
--- a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts
+++ b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts
@@ -27,7 +27,7 @@ export default define(meta, paramDef, async (ps, user) => {
take: ps.limit,
skip: ps.offset,
order: {
- id: ps.sort == 'asc' ? 1 : -1,
+ id: ps.sort === 'asc' ? 1 : -1,
},
});
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts
new file mode 100644
index 000000000..2e2fd00b8
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts
@@ -0,0 +1,43 @@
+import define from '../../../define.js';
+import { genId } from '@/misc/gen-id.js';
+import { Webhooks } from '@/models/index.js';
+import { publishInternalEvent } from '@/services/stream.js';
+import { webhookEventTypes } from '@/models/entities/webhook.js';
+
+export const meta = {
+ tags: ['webhooks'],
+
+ requireCredential: true,
+
+ kind: 'write:account',
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ name: { type: 'string', minLength: 1, maxLength: 100 },
+ url: { type: 'string', minLength: 1, maxLength: 1024 },
+ secret: { type: 'string', minLength: 1, maxLength: 1024 },
+ on: { type: 'array', items: {
+ type: 'string', enum: webhookEventTypes,
+ } },
+ },
+ required: ['name', 'url', 'secret', 'on'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+export default define(meta, paramDef, async (ps, user) => {
+ const webhook = await Webhooks.insert({
+ id: genId(),
+ createdAt: new Date(),
+ userId: user.id,
+ name: ps.name,
+ url: ps.url,
+ secret: ps.secret,
+ on: ps.on,
+ }).then(x => Webhooks.findOneByOrFail(x.identifiers[0]));
+
+ publishInternalEvent('webhookCreated', webhook);
+
+ return webhook;
+});
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts b/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts
new file mode 100644
index 000000000..2821eaa5f
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts
@@ -0,0 +1,44 @@
+import define from '../../../define.js';
+import { ApiError } from '../../../error.js';
+import { Webhooks } from '@/models/index.js';
+import { publishInternalEvent } from '@/services/stream.js';
+
+export const meta = {
+ tags: ['webhooks'],
+
+ requireCredential: true,
+
+ kind: 'write:account',
+
+ errors: {
+ noSuchWebhook: {
+ message: 'No such webhook.',
+ code: 'NO_SUCH_WEBHOOK',
+ id: 'bae73e5a-5522-4965-ae19-3a8688e71d82',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ webhookId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['webhookId'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+export default define(meta, paramDef, async (ps, user) => {
+ const webhook = await Webhooks.findOneBy({
+ id: ps.webhookId,
+ userId: user.id,
+ });
+
+ if (webhook == null) {
+ throw new ApiError(meta.errors.noSuchWebhook);
+ }
+
+ await Webhooks.delete(webhook.id);
+
+ publishInternalEvent('webhookDeleted', webhook);
+});
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts
new file mode 100644
index 000000000..54e456373
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts
@@ -0,0 +1,25 @@
+import define from '../../../define.js';
+import { Webhooks } from '@/models/index.js';
+
+export const meta = {
+ tags: ['webhooks', 'account'],
+
+ requireCredential: true,
+
+ kind: 'read:account',
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {},
+ required: [],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+export default define(meta, paramDef, async (ps, me) => {
+ const webhooks = await Webhooks.findBy({
+ userId: me.id,
+ });
+
+ return webhooks;
+});
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts
new file mode 100644
index 000000000..02fa1edb5
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts
@@ -0,0 +1,41 @@
+import define from '../../../define.js';
+import { ApiError } from '../../../error.js';
+import { Webhooks } from '@/models/index.js';
+
+export const meta = {
+ tags: ['webhooks'],
+
+ requireCredential: true,
+
+ kind: 'read:account',
+
+ errors: {
+ noSuchWebhook: {
+ message: 'No such webhook.',
+ code: 'NO_SUCH_WEBHOOK',
+ id: '50f614d9-3047-4f7e-90d8-ad6b2d5fb098',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ webhookId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['webhookId'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+export default define(meta, paramDef, async (ps, user) => {
+ const webhook = await Webhooks.findOneBy({
+ id: ps.webhookId,
+ userId: user.id,
+ });
+
+ if (webhook == null) {
+ throw new ApiError(meta.errors.noSuchWebhook);
+ }
+
+ return webhook;
+});
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts
new file mode 100644
index 000000000..f87b9753f
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts
@@ -0,0 +1,59 @@
+import define from '../../../define.js';
+import { ApiError } from '../../../error.js';
+import { Webhooks } from '@/models/index.js';
+import { publishInternalEvent } from '@/services/stream.js';
+import { webhookEventTypes } from '@/models/entities/webhook.js';
+
+export const meta = {
+ tags: ['webhooks'],
+
+ requireCredential: true,
+
+ kind: 'write:account',
+
+ errors: {
+ noSuchWebhook: {
+ message: 'No such webhook.',
+ code: 'NO_SUCH_WEBHOOK',
+ id: 'fb0fea69-da18-45b1-828d-bd4fd1612518',
+ },
+ },
+
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ webhookId: { type: 'string', format: 'misskey:id' },
+ name: { type: 'string', minLength: 1, maxLength: 100 },
+ url: { type: 'string', minLength: 1, maxLength: 1024 },
+ secret: { type: 'string', minLength: 1, maxLength: 1024 },
+ on: { type: 'array', items: {
+ type: 'string', enum: webhookEventTypes,
+ } },
+ active: { type: 'boolean' },
+ },
+ required: ['webhookId', 'name', 'url', 'secret', 'on', 'active'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+export default define(meta, paramDef, async (ps, user) => {
+ const webhook = await Webhooks.findOneBy({
+ id: ps.webhookId,
+ userId: user.id,
+ });
+
+ if (webhook == null) {
+ throw new ApiError(meta.errors.noSuchWebhook);
+ }
+
+ await Webhooks.update(webhook.id, {
+ name: ps.name,
+ url: ps.url,
+ secret: ps.secret,
+ on: ps.on,
+ active: ps.active,
+ });
+
+ publishInternalEvent('webhookUpdated', webhook);
+});
diff --git a/packages/backend/src/server/api/endpoints/messaging/messages.ts b/packages/backend/src/server/api/endpoints/messaging/messages.ts
index 9760709c2..dbf1f6c86 100644
--- a/packages/backend/src/server/api/endpoints/messaging/messages.ts
+++ b/packages/backend/src/server/api/endpoints/messaging/messages.ts
@@ -47,14 +47,25 @@ export const meta = {
export const paramDef = {
type: 'object',
properties: {
- userId: { type: 'string', format: 'misskey:id' },
- groupId: { type: 'string', format: 'misskey:id' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
markAsRead: { type: 'boolean', default: true },
},
- required: [],
+ anyOf: [
+ {
+ properties: {
+ userId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['userId'],
+ },
+ {
+ properties: {
+ groupId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['groupId'],
+ },
+ ],
} as const;
// eslint-disable-next-line import/no-default-export
@@ -126,7 +137,5 @@ export default define(meta, paramDef, async (ps, user) => {
return await Promise.all(messages.map(message => MessagingMessages.pack(message, user, {
populateGroup: false,
})));
- } else {
- throw new Error();
}
});
diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts
index 8c1226b0f..405af5ec1 100644
--- a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts
+++ b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts
@@ -67,12 +67,23 @@ export const meta = {
export const paramDef = {
type: 'object',
properties: {
- userId: { type: 'string', format: 'misskey:id' },
- groupId: { type: 'string', format: 'misskey:id' },
text: { type: 'string', nullable: true, maxLength: 3000 },
fileId: { type: 'string', format: 'misskey:id' },
},
- required: [],
+ anyOf: [
+ {
+ properties: {
+ userId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['userId'],
+ },
+ {
+ properties: {
+ groupId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['groupId'],
+ },
+ ],
} as const;
// eslint-disable-next-line import/no-default-export
diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts
index 057d22f33..e1ae282a9 100644
--- a/packages/backend/src/server/api/endpoints/meta.ts
+++ b/packages/backend/src/server/api/endpoints/meta.ts
@@ -169,6 +169,7 @@ export const meta = {
host: {
type: 'string',
optional: false, nullable: true,
+ description: 'The local host is represented with `null`.',
},
url: {
type: 'string',
diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts
index 0c3a3453f..7e857e673 100644
--- a/packages/backend/src/server/api/endpoints/mute/create.ts
+++ b/packages/backend/src/server/api/endpoints/mute/create.ts
@@ -38,7 +38,11 @@ export const paramDef = {
type: 'object',
properties: {
userId: { type: 'string', format: 'misskey:id' },
- expiresAt: { type: 'integer', nullable: true },
+ expiresAt: {
+ type: 'integer',
+ nullable: true,
+ description: 'A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute.',
+ },
},
required: ['userId'],
} as const;
diff --git a/packages/backend/src/server/api/endpoints/notes.ts b/packages/backend/src/server/api/endpoints/notes.ts
index 96657f8d3..2733c826e 100644
--- a/packages/backend/src/server/api/endpoints/notes.ts
+++ b/packages/backend/src/server/api/endpoints/notes.ts
@@ -19,7 +19,7 @@ export const meta = {
export const paramDef = {
type: 'object',
properties: {
- local: { type: 'boolean' },
+ local: { type: 'boolean', default: false },
reply: { type: 'boolean' },
renote: { type: 'boolean' },
withFiles: { type: 'boolean' },
@@ -52,19 +52,19 @@ export default define(meta, paramDef, async (ps) => {
query.andWhere('note.userHost IS NULL');
}
- if (ps.reply != undefined) {
+ if (ps.reply !== undefined) {
query.andWhere(ps.reply ? 'note.replyId IS NOT NULL' : 'note.replyId IS NULL');
}
- if (ps.renote != undefined) {
+ if (ps.renote !== undefined) {
query.andWhere(ps.renote ? 'note.renoteId IS NOT NULL' : 'note.renoteId IS NULL');
}
- if (ps.withFiles != undefined) {
+ if (ps.withFiles !== undefined) {
query.andWhere(ps.withFiles ? `note.fileIds != '{}'` : `note.fileIds = '{}'`);
}
- if (ps.poll != undefined) {
+ if (ps.poll !== undefined) {
query.andWhere(ps.poll ? 'note.hasPoll = TRUE' : 'note.hasPoll = FALSE');
}
diff --git a/packages/backend/src/server/api/endpoints/notes/conversation.ts b/packages/backend/src/server/api/endpoints/notes/conversation.ts
index 8f5d21db6..b991a495f 100644
--- a/packages/backend/src/server/api/endpoints/notes/conversation.ts
+++ b/packages/backend/src/server/api/endpoints/notes/conversation.ts
@@ -57,7 +57,7 @@ export default define(meta, paramDef, async (ps, user) => {
conversation.push(p);
}
- if (conversation.length == ps.limit) {
+ if (conversation.length === ps.limit) {
return;
}
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 961983f5f..40a3ba73c 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -1,14 +1,15 @@
import ms from 'ms';
+import { In } from 'typeorm';
import create from '@/services/note/create.js';
-import define from '../../define.js';
-import { ApiError } from '../../error.js';
import { User } from '@/models/entities/user.js';
import { Users, DriveFiles, Notes, Channels, Blockings } from '@/models/index.js';
import { DriveFile } from '@/models/entities/drive-file.js';
import { Note } from '@/models/entities/note.js';
-import { noteVisibilities } from '../../../../types.js';
import { Channel } from '@/models/entities/channel.js';
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
+import { noteVisibilities } from '../../../../types.js';
+import { ApiError } from '../../error.js';
+import define from '../../define.js';
export const meta = {
tags: ['notes'],
@@ -59,12 +60,6 @@ export const meta = {
id: '3ac74a84-8fd5-4bb0-870f-01804f82ce15',
},
- contentRequired: {
- message: 'Content required. You need to set text, fileIds, renoteId or poll.',
- code: 'CONTENT_REQUIRED',
- id: '6f57e42b-c348-439b-bc45-993995cc515a',
- },
-
cannotCreateAlreadyExpiredPoll: {
message: 'Poll is already expired.',
code: 'CANNOT_CREATE_ALREADY_EXPIRED_POLL',
@@ -88,33 +83,45 @@ export const meta = {
export const paramDef = {
type: 'object',
properties: {
- visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'], default: "public" },
+ visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'], default: 'public' },
visibleUserIds: { type: 'array', uniqueItems: true, items: {
type: 'string', format: 'misskey:id',
} },
- text: { type: 'string', nullable: true, maxLength: MAX_NOTE_TEXT_LENGTH, default: null },
+ text: { type: 'string', maxLength: MAX_NOTE_TEXT_LENGTH, nullable: true },
cw: { type: 'string', nullable: true, maxLength: 100 },
localOnly: { type: 'boolean', default: false },
noExtractMentions: { type: 'boolean', default: false },
noExtractHashtags: { type: 'boolean', default: false },
noExtractEmojis: { type: 'boolean', default: false },
- fileIds: { type: 'array', uniqueItems: true, minItems: 1, maxItems: 16, items: {
- type: 'string', format: 'misskey:id',
- } },
- mediaIds: { type: 'array', uniqueItems: true, minItems: 1, maxItems: 16, items: {
- type: 'string', format: 'misskey:id',
- } },
+ fileIds: {
+ type: 'array',
+ uniqueItems: true,
+ minItems: 1,
+ maxItems: 16,
+ items: { type: 'string', format: 'misskey:id' },
+ },
+ mediaIds: {
+ deprecated: true,
+ description: 'Use `fileIds` instead. If both are specified, this property is discarded.',
+ type: 'array',
+ uniqueItems: true,
+ minItems: 1,
+ maxItems: 16,
+ items: { type: 'string', format: 'misskey:id' },
+ },
replyId: { type: 'string', format: 'misskey:id', nullable: true },
renoteId: { type: 'string', format: 'misskey:id', nullable: true },
channelId: { type: 'string', format: 'misskey:id', nullable: true },
poll: {
- type: 'object', nullable: true,
+ type: 'object',
+ nullable: true,
properties: {
choices: {
- type: 'array', uniqueItems: true, minItems: 2, maxItems: 10,
- items: {
- type: 'string', minLength: 1, maxLength: 50,
- },
+ type: 'array',
+ uniqueItems: true,
+ minItems: 2,
+ maxItems: 10,
+ items: { type: 'string', minLength: 1, maxLength: 50 },
},
multiple: { type: 'boolean', default: false },
expiresAt: { type: 'integer', nullable: true },
@@ -123,36 +130,62 @@ export const paramDef = {
required: ['choices'],
},
},
- required: [],
+ anyOf: [
+ {
+ // (re)note with text, files and poll are optional
+ properties: {
+ text: { type: 'string', maxLength: MAX_NOTE_TEXT_LENGTH, nullable: false },
+ },
+ required: ['text'],
+ },
+ {
+ // (re)note with files, text and poll are optional
+ required: ['fileIds'],
+ },
+ {
+ // (re)note with files, text and poll are optional
+ required: ['mediaIds'],
+ },
+ {
+ // (re)note with poll, text and files are optional
+ properties: {
+ poll: { type: 'object', nullable: false },
+ },
+ required: ['poll'],
+ },
+ {
+ // pure renote
+ required: ['renoteId'],
+ },
+ ],
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
let visibleUsers: User[] = [];
if (ps.visibleUserIds) {
- visibleUsers = (await Promise.all(ps.visibleUserIds.map(id => Users.findOneBy({ id }))))
- .filter(x => x != null) as User[];
+ visibleUsers = await Users.findBy({
+ id: In(ps.visibleUserIds),
+ });
}
let files: DriveFile[] = [];
const fileIds = ps.fileIds != null ? ps.fileIds : ps.mediaIds != null ? ps.mediaIds : null;
if (fileIds != null) {
- files = (await Promise.all(fileIds.map(fileId =>
- DriveFiles.findOneBy({
- id: fileId,
- userId: user.id,
- })
- ))).filter(file => file != null) as DriveFile[];
+ files = await DriveFiles.findBy({
+ userId: user.id,
+ id: In(fileIds),
+ });
}
- let renote: Note | null;
+ let renote: Note | null = null;
if (ps.renoteId != null) {
// Fetch renote to note
renote = await Notes.findOneBy({ id: ps.renoteId });
if (renote == null) {
throw new ApiError(meta.errors.noSuchRenoteTarget);
- } else if (renote.renoteId && !renote.text && !renote.fileIds) {
+ } else if (renote.renoteId && !renote.text && !renote.fileIds && !renote.hasPoll) {
throw new ApiError(meta.errors.cannotReRenote);
}
@@ -168,17 +201,14 @@ export default define(meta, paramDef, async (ps, user) => {
}
}
- let reply: Note | null;
+ let reply: Note | null = null;
if (ps.replyId != null) {
// Fetch reply
reply = await Notes.findOneBy({ id: ps.replyId });
if (reply == null) {
throw new ApiError(meta.errors.noSuchReplyTarget);
- }
-
- // 返信対象が引用でないRenoteだったらエラー
- if (reply.renoteId && !reply.text && !reply.fileIds) {
+ } else if (reply.renoteId && !reply.text && !reply.fileIds && !reply.hasPoll) {
throw new ApiError(meta.errors.cannotReplyToPureRenote);
}
@@ -204,12 +234,7 @@ export default define(meta, paramDef, async (ps, user) => {
}
}
- // テキストが無いかつ添付ファイルが無いかつRenoteも無いかつ投票も無かったらエラー
- if (!(ps.text || files.length || renote || ps.poll)) {
- throw new ApiError(meta.errors.contentRequired);
- }
-
- let channel: Channel | undefined;
+ let channel: Channel | null = null;
if (ps.channelId != null) {
channel = await Channels.findOneBy({ id: ps.channelId });
diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
index 09a819466..cb402ecaa 100644
--- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
@@ -35,7 +35,11 @@ export const meta = {
export const paramDef = {
type: 'object',
properties: {
- withFiles: { type: 'boolean' },
+ withFiles: {
+ type: 'boolean',
+ default: false,
+ description: 'Only show notes that have attached files.',
+ },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
index 7c9c12296..f9893527e 100644
--- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
@@ -48,7 +48,11 @@ export const paramDef = {
includeMyRenotes: { type: 'boolean', default: true },
includeRenotedMyNotes: { type: 'boolean', default: true },
includeLocalRenotes: { type: 'boolean', default: true },
- withFiles: { type: 'boolean' },
+ withFiles: {
+ type: 'boolean',
+ default: false,
+ description: 'Only show notes that have attached files.',
+ },
},
required: [],
} as const;
diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
index bb0bbe2a2..03edf30b3 100644
--- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
@@ -37,7 +37,11 @@ export const meta = {
export const paramDef = {
type: 'object',
properties: {
- withFiles: { type: 'boolean' },
+ withFiles: {
+ type: 'boolean',
+ default: false,
+ description: 'Only show notes that have attached files.',
+ },
fileType: { type: 'array', items: {
type: 'string',
} },
diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
index 6380b331f..6244b55cf 100644
--- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
+++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
@@ -110,7 +110,7 @@ export default define(meta, paramDef, async (ps, user) => {
if (exist.length) {
if (poll.multiple) {
- if (exist.some(x => x.choice == ps.choice)) {
+ if (exist.some(x => x.choice === ps.choice)) {
throw new ApiError(meta.errors.alreadyVoted);
}
} else {
diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts
index 3555424fa..fbb065329 100644
--- a/packages/backend/src/server/api/endpoints/notes/reactions.ts
+++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts
@@ -1,8 +1,8 @@
+import { DeepPartial, FindOptionsWhere } from 'typeorm';
+import { NoteReactions } from '@/models/index.js';
+import { NoteReaction } from '@/models/entities/note-reaction.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
-import { NoteReactions } from '@/models/index.js';
-import { DeepPartial } from 'typeorm';
-import { NoteReaction } from '@/models/entities/note-reaction.js';
export const meta = {
tags: ['notes', 'reactions'],
@@ -45,7 +45,7 @@ export const paramDef = {
export default define(meta, paramDef, async (ps, user) => {
const query = {
noteId: ps.noteId,
- } as DeepPartial;
+ } as FindOptionsWhere;
if (ps.type) {
// ローカルリアクションはホスト名が . とされているが
diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts
index c6503eb05..bb85c9200 100644
--- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts
+++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts
@@ -25,21 +25,44 @@ export const meta = {
export const paramDef = {
type: 'object',
properties: {
- tag: { type: 'string' },
- query: { type: 'array', items: {
- type: 'array', items: {
- type: 'string',
- },
- } },
reply: { type: 'boolean', nullable: true, default: null },
renote: { type: 'boolean', nullable: true, default: null },
- withFiles: { type: 'boolean' },
+ withFiles: {
+ type: 'boolean',
+ default: false,
+ description: 'Only show notes that have attached files.',
+ },
poll: { type: 'boolean', nullable: true, default: null },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
},
- required: [],
+ anyOf: [
+ {
+ properties: {
+ tag: { type: 'string', minLength: 1 },
+ },
+ required: ['tag'],
+ },
+ {
+ properties: {
+ query: {
+ type: 'array',
+ description: 'The outer arrays are chained with OR, the inner arrays are chained with AND.',
+ items: {
+ type: 'array',
+ items: {
+ type: 'string',
+ minLength: 1,
+ },
+ minItems: 1,
+ },
+ minItems: 1,
+ },
+ },
+ required: ['query'],
+ },
+ ],
} as const;
// eslint-disable-next-line import/no-default-export
diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts
index e77892b15..af9b5f0a1 100644
--- a/packages/backend/src/server/api/endpoints/notes/search.ts
+++ b/packages/backend/src/server/api/endpoints/notes/search.ts
@@ -35,7 +35,11 @@ export const paramDef = {
untilId: { type: 'string', format: 'misskey:id' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
offset: { type: 'integer', default: 0 },
- host: { type: 'string', nullable: true },
+ host: {
+ type: 'string',
+ nullable: true,
+ description: 'The local host is represented with `null`.',
+ },
userId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
channelId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
},
diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts
index fde66b241..0f976d18b 100644
--- a/packages/backend/src/server/api/endpoints/notes/timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts
@@ -38,7 +38,11 @@ export const paramDef = {
includeMyRenotes: { type: 'boolean', default: true },
includeRenotedMyNotes: { type: 'boolean', default: true },
includeLocalRenotes: { type: 'boolean', default: true },
- withFiles: { type: 'boolean' },
+ withFiles: {
+ type: 'boolean',
+ default: false,
+ description: 'Only show notes that have attached files.',
+ },
},
required: [],
} as const;
diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts
index 068df6940..5e40e7106 100644
--- a/packages/backend/src/server/api/endpoints/notes/translate.ts
+++ b/packages/backend/src/server/api/endpoints/notes/translate.ts
@@ -1,12 +1,12 @@
-import define from '../../define.js';
-import { getNote } from '../../common/getters.js';
-import { ApiError } from '../../error.js';
+import { URLSearchParams } from 'node:url';
import fetch from 'node-fetch';
import config from '@/config/index.js';
import { getAgentByUrl } from '@/misc/fetch.js';
-import { URLSearchParams } from 'node:url';
import { fetchMeta } from '@/misc/fetch-meta.js';
import { Notes } from '@/models/index.js';
+import { ApiError } from '../../error.js';
+import { getNote } from '../../common/getters.js';
+import define from '../../define.js';
export const meta = {
tags: ['notes'],
@@ -75,11 +75,17 @@ export default define(meta, paramDef, async (ps, user) => {
Accept: 'application/json, */*',
},
body: params,
- timeout: 10000,
+ // TODO
+ //timeout: 10000,
agent: getAgentByUrl,
});
- const json = await res.json();
+ const json = (await res.json()) as {
+ translations: {
+ detected_source_language: string;
+ text: string;
+ }[];
+ };
return {
sourceLang: json.translations[0].detected_source_language,
diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
index 866e306d8..fd4a87903 100644
--- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
@@ -42,7 +42,11 @@ export const paramDef = {
includeMyRenotes: { type: 'boolean', default: true },
includeRenotedMyNotes: { type: 'boolean', default: true },
includeLocalRenotes: { type: 'boolean', default: true },
- withFiles: { type: 'boolean' },
+ withFiles: {
+ type: 'boolean',
+ default: false,
+ description: 'Only show notes that have attached files.',
+ },
},
required: ['listId'],
} as const;
@@ -59,12 +63,8 @@ export default define(meta, paramDef, async (ps, user) => {
}
//#region Construct query
- const listQuery = UserListJoinings.createQueryBuilder('joining')
- .select('joining.userId')
- .where('joining.userListId = :userListId', { userListId: list.id });
-
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
- .andWhere(`note.userId IN (${ listQuery.getQuery() })`)
+ .innerJoin(UserListJoinings.metadata.targetName, 'userListJoining', 'userListJoining.userId = note.userId')
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')
@@ -76,7 +76,7 @@ export default define(meta, paramDef, async (ps, user) => {
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
- .setParameters(listQuery.getParameters());
+ .andWhere('userListJoining.userListId = :userListId', { userListId: list.id });
generateVisibilityQuery(query, user);
diff --git a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
index abefe07be..4575cba43 100644
--- a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
+++ b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
@@ -1,4 +1,5 @@
import { publishMainStream } from '@/services/stream.js';
+import { pushNotification } from '@/services/push-notification.js';
import define from '../../define.js';
import { Notifications } from '@/models/index.js';
@@ -28,4 +29,5 @@ export default define(meta, paramDef, async (ps, user) => {
// 全ての通知を読みましたよというイベントを発行
publishMainStream(user.id, 'readAllNotifications');
+ pushNotification(user.id, 'readAllNotifications', undefined);
});
diff --git a/packages/backend/src/server/api/endpoints/notifications/read.ts b/packages/backend/src/server/api/endpoints/notifications/read.ts
index c7bc5dc0a..65e96d486 100644
--- a/packages/backend/src/server/api/endpoints/notifications/read.ts
+++ b/packages/backend/src/server/api/endpoints/notifications/read.ts
@@ -1,10 +1,12 @@
-import { publishMainStream } from '@/services/stream.js';
import define from '../../define.js';
-import { Notifications } from '@/models/index.js';
import { readNotification } from '../../common/read-notification.js';
-import { ApiError } from '../../error.js';
export const meta = {
+ desc: {
+ 'ja-JP': '通知を既読にします。',
+ 'en-US': 'Mark a notification as read.'
+ },
+
tags: ['notifications', 'account'],
requireCredential: true,
@@ -21,23 +23,26 @@ export const meta = {
} as const;
export const paramDef = {
- type: 'object',
- properties: {
- notificationId: { type: 'string', format: 'misskey:id' },
- },
- required: ['notificationId'],
+ oneOf: [
+ {
+ type: 'object',
+ properties: {
+ notificationId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['notificationId'],
+ },
+ {
+ type: 'object',
+ properties: {
+ notificationIds: { type: 'array', items: { type: 'string', format: 'misskey:id' } },
+ },
+ required: ['notificationIds'],
+ },
+ ],
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const notification = await Notifications.findOneBy({
- notifieeId: user.id,
- id: ps.notificationId,
- });
-
- if (notification == null) {
- throw new ApiError(meta.errors.noSuchNotification);
- }
-
- readNotification(user.id, [notification.id]);
+ if ('notificationId' in ps) return readNotification(user.id, [ps.notificationId]);
+ return readNotification(user.id, ps.notificationIds);
});
diff --git a/packages/backend/src/server/api/endpoints/pages/show.ts b/packages/backend/src/server/api/endpoints/pages/show.ts
index 1c218acfd..5d37e86b9 100644
--- a/packages/backend/src/server/api/endpoints/pages/show.ts
+++ b/packages/backend/src/server/api/endpoints/pages/show.ts
@@ -1,8 +1,8 @@
-import define from '../../define.js';
-import { ApiError } from '../../error.js';
+import { IsNull } from 'typeorm';
import { Pages, Users } from '@/models/index.js';
import { Page } from '@/models/entities/page.js';
-import { IsNull } from 'typeorm';
+import define from '../../define.js';
+import { ApiError } from '../../error.js';
export const meta = {
tags: ['pages'],
@@ -26,17 +26,26 @@ export const meta = {
export const paramDef = {
type: 'object',
- properties: {
- pageId: { type: 'string', format: 'misskey:id' },
- name: { type: 'string' },
- username: { type: 'string' },
- },
- required: [],
+ anyOf: [
+ {
+ properties: {
+ pageId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['pageId'],
+ },
+ {
+ properties: {
+ name: { type: 'string' },
+ username: { type: 'string' },
+ },
+ required: ['name', 'username'],
+ },
+ ],
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- let page: Page | undefined;
+ let page: Page | null = null;
if (ps.pageId) {
page = await Pages.findOneBy({ id: ps.pageId });
diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts
index 5de624312..26b1f20df 100644
--- a/packages/backend/src/server/api/endpoints/users/followers.ts
+++ b/packages/backend/src/server/api/endpoints/users/followers.ts
@@ -38,14 +38,29 @@ export const meta = {
export const paramDef = {
type: 'object',
properties: {
- userId: { type: 'string', format: 'misskey:id' },
- username: { type: 'string' },
- host: { type: 'string', nullable: true },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
},
- required: [],
+ anyOf: [
+ {
+ properties: {
+ userId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['userId'],
+ },
+ {
+ properties: {
+ username: { type: 'string' },
+ host: {
+ type: 'string',
+ nullable: true,
+ description: 'The local host is represented with `null`.',
+ },
+ },
+ required: ['username', 'host'],
+ },
+ ],
} as const;
// eslint-disable-next-line import/no-default-export
diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts
index 55460f7c6..42cf5216e 100644
--- a/packages/backend/src/server/api/endpoints/users/following.ts
+++ b/packages/backend/src/server/api/endpoints/users/following.ts
@@ -38,14 +38,29 @@ export const meta = {
export const paramDef = {
type: 'object',
properties: {
- userId: { type: 'string', format: 'misskey:id' },
- username: { type: 'string' },
- host: { type: 'string', nullable: true },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
},
- required: [],
+ anyOf: [
+ {
+ properties: {
+ userId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['userId'],
+ },
+ {
+ properties: {
+ username: { type: 'string' },
+ host: {
+ type: 'string',
+ nullable: true,
+ description: 'The local host is represented with `null`.',
+ },
+ },
+ required: ['username', 'host'],
+ },
+ ],
} as const;
// eslint-disable-next-line import/no-default-export
diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
index 897b5de3f..f74d80e2a 100644
--- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
+++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
@@ -28,7 +28,10 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
detail: { type: 'boolean', default: true },
},
- required: [],
+ anyOf: [
+ { required: ['username'] },
+ { required: ['host'] },
+ ],
} as const;
// TODO: avatar,bannerをJOINしたいけどエラーになる
diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts
index 775a4b29f..183ff1b8b 100644
--- a/packages/backend/src/server/api/endpoints/users/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/show.ts
@@ -23,9 +23,9 @@ export const meta = {
items: {
type: 'object',
ref: 'UserDetailed',
- }
+ },
},
- ]
+ ],
},
errors: {
@@ -46,15 +46,33 @@ export const meta = {
export const paramDef = {
type: 'object',
- properties: {
- userId: { type: 'string', format: 'misskey:id' },
- userIds: { type: 'array', uniqueItems: true, items: {
- type: 'string', format: 'misskey:id',
- } },
- username: { type: 'string' },
- host: { type: 'string', nullable: true },
- },
- required: [],
+ anyOf: [
+ {
+ properties: {
+ userId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['userId'],
+ },
+ {
+ properties: {
+ userIds: { type: 'array', uniqueItems: true, items: {
+ type: 'string', format: 'misskey:id',
+ } },
+ },
+ required: ['userIds'],
+ },
+ {
+ properties: {
+ username: { type: 'string' },
+ host: {
+ type: 'string',
+ nullable: true,
+ description: 'The local host is represented with `null`.',
+ },
+ },
+ required: ['username'],
+ },
+ ],
} as const;
// eslint-disable-next-line import/no-default-export
diff --git a/packages/backend/src/server/api/private/signin.ts b/packages/backend/src/server/api/private/signin.ts
index 3f7118ad2..7b66657ad 100644
--- a/packages/backend/src/server/api/private/signin.ts
+++ b/packages/backend/src/server/api/private/signin.ts
@@ -24,17 +24,17 @@ export default async (ctx: Koa.Context) => {
ctx.body = { error };
}
- if (typeof username != 'string') {
+ if (typeof username !== 'string') {
ctx.status = 400;
return;
}
- if (typeof password != 'string') {
+ if (typeof password !== 'string') {
ctx.status = 400;
return;
}
- if (token != null && typeof token != 'string') {
+ if (token != null && typeof token !== 'string') {
ctx.status = 400;
return;
}
diff --git a/packages/backend/src/server/api/service/discord.ts b/packages/backend/src/server/api/service/discord.ts
index 04197574c..97cbcbecd 100644
--- a/packages/backend/src/server/api/service/discord.ts
+++ b/packages/backend/src/server/api/service/discord.ts
@@ -1,16 +1,16 @@
import Koa from 'koa';
import Router from '@koa/router';
-import { getJson } from '@/misc/fetch.js';
import { OAuth2 } from 'oauth';
+import { v4 as uuid } from 'uuid';
+import { IsNull } from 'typeorm';
+import { getJson } from '@/misc/fetch.js';
import config from '@/config/index.js';
import { publishMainStream } from '@/services/stream.js';
-import { redisClient } from '../../../db/redis.js';
-import { v4 as uuid } from 'uuid';
-import signin from '../common/signin.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
import { Users, UserProfiles } from '@/models/index.js';
import { ILocalUser } from '@/models/entities/user.js';
-import { IsNull } from 'typeorm';
+import { redisClient } from '../../../db/redis.js';
+import signin from '../common/signin.js';
function getUserToken(ctx: Koa.BaseContext): string | null {
return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1];
@@ -54,7 +54,7 @@ router.get('/disconnect/discord', async ctx => {
integrations: profile.integrations,
});
- ctx.body = `Discordの連携を解除しました :v:`;
+ ctx.body = 'Discordの連携を解除しました :v:';
// Publish i updated event
publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, {
@@ -140,7 +140,7 @@ router.get('/dc/cb', async ctx => {
const code = ctx.query.code;
- if (!code) {
+ if (!code || typeof code !== 'string') {
ctx.throw(400, 'invalid session');
return;
}
@@ -174,17 +174,17 @@ router.get('/dc/cb', async ctx => {
}
}));
- const { id, username, discriminator } = await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, {
+ const { id, username, discriminator } = (await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, {
'Authorization': `Bearer ${accessToken}`,
- });
+ })) as Record;
- if (!id || !username || !discriminator) {
+ if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') {
ctx.throw(400, 'invalid session');
return;
}
const profile = await UserProfiles.createQueryBuilder()
- .where(`"integrations"->'discord'->>'id' = :id`, { id: id })
+ .where('"integrations"->\'discord\'->>\'id\' = :id', { id: id })
.andWhere('"userHost" IS NULL')
.getOne();
@@ -211,7 +211,7 @@ router.get('/dc/cb', async ctx => {
} else {
const code = ctx.query.code;
- if (!code) {
+ if (!code || typeof code !== 'string') {
ctx.throw(400, 'invalid session');
return;
}
@@ -245,10 +245,10 @@ router.get('/dc/cb', async ctx => {
}
}));
- const { id, username, discriminator } = await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, {
+ const { id, username, discriminator } = (await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, {
'Authorization': `Bearer ${accessToken}`,
- });
- if (!id || !username || !discriminator) {
+ })) as Record;
+ if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') {
ctx.throw(400, 'invalid session');
return;
}
diff --git a/packages/backend/src/server/api/service/github.ts b/packages/backend/src/server/api/service/github.ts
index 61bb768a6..04dbd1f7a 100644
--- a/packages/backend/src/server/api/service/github.ts
+++ b/packages/backend/src/server/api/service/github.ts
@@ -1,16 +1,16 @@
import Koa from 'koa';
import Router from '@koa/router';
-import { getJson } from '@/misc/fetch.js';
import { OAuth2 } from 'oauth';
+import { v4 as uuid } from 'uuid';
+import { IsNull } from 'typeorm';
+import { getJson } from '@/misc/fetch.js';
import config from '@/config/index.js';
import { publishMainStream } from '@/services/stream.js';
-import { redisClient } from '../../../db/redis.js';
-import { v4 as uuid } from 'uuid';
-import signin from '../common/signin.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
import { Users, UserProfiles } from '@/models/index.js';
import { ILocalUser } from '@/models/entities/user.js';
-import { IsNull } from 'typeorm';
+import { redisClient } from '../../../db/redis.js';
+import signin from '../common/signin.js';
function getUserToken(ctx: Koa.BaseContext): string | null {
return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1];
@@ -54,7 +54,7 @@ router.get('/disconnect/github', async ctx => {
integrations: profile.integrations,
});
- ctx.body = `GitHubの連携を解除しました :v:`;
+ ctx.body = 'GitHubの連携を解除しました :v:';
// Publish i updated event
publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, {
@@ -138,7 +138,7 @@ router.get('/gh/cb', async ctx => {
const code = ctx.query.code;
- if (!code) {
+ if (!code || typeof code !== 'string') {
ctx.throw(400, 'invalid session');
return;
}
@@ -167,16 +167,16 @@ router.get('/gh/cb', async ctx => {
}
}));
- const { login, id } = await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, {
+ const { login, id } = (await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, {
'Authorization': `bearer ${accessToken}`,
- });
- if (!login || !id) {
+ })) as Record;
+ if (typeof login !== 'string' || typeof id !== 'string') {
ctx.throw(400, 'invalid session');
return;
}
const link = await UserProfiles.createQueryBuilder()
- .where(`"integrations"->'github'->>'id' = :id`, { id: id })
+ .where('"integrations"->\'github\'->>\'id\' = :id', { id: id })
.andWhere('"userHost" IS NULL')
.getOne();
@@ -189,7 +189,7 @@ router.get('/gh/cb', async ctx => {
} else {
const code = ctx.query.code;
- if (!code) {
+ if (!code || typeof code !== 'string') {
ctx.throw(400, 'invalid session');
return;
}
@@ -219,11 +219,11 @@ router.get('/gh/cb', async ctx => {
}
}));
- const { login, id } = await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, {
+ const { login, id } = (await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, {
'Authorization': `bearer ${accessToken}`,
- });
+ })) as Record;
- if (!login || !id) {
+ if (typeof login !== 'string' || typeof id !== 'string') {
ctx.throw(400, 'invalid session');
return;
}
diff --git a/packages/backend/src/server/api/service/twitter.ts b/packages/backend/src/server/api/service/twitter.ts
index e72b71e2f..2b4f9f6da 100644
--- a/packages/backend/src/server/api/service/twitter.ts
+++ b/packages/backend/src/server/api/service/twitter.ts
@@ -2,14 +2,14 @@ import Koa from 'koa';
import Router from '@koa/router';
import { v4 as uuid } from 'uuid';
import autwh from 'autwh';
-import { redisClient } from '../../../db/redis.js';
+import { IsNull } from 'typeorm';
import { publishMainStream } from '@/services/stream.js';
import config from '@/config/index.js';
-import signin from '../common/signin.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
import { Users, UserProfiles } from '@/models/index.js';
import { ILocalUser } from '@/models/entities/user.js';
-import { IsNull } from 'typeorm';
+import signin from '../common/signin.js';
+import { redisClient } from '../../../db/redis.js';
function getUserToken(ctx: Koa.BaseContext): string | null {
return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1];
@@ -53,7 +53,7 @@ router.get('/disconnect/twitter', async ctx => {
integrations: profile.integrations,
});
- ctx.body = `Twitterの連携を解除しました :v:`;
+ ctx.body = 'Twitterの連携を解除しました :v:';
// Publish i updated event
publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, {
@@ -132,10 +132,16 @@ router.get('/tw/cb', async ctx => {
const twCtx = await get;
- const result = await twAuth!.done(JSON.parse(twCtx), ctx.query.oauth_verifier);
+ const verifier = ctx.query.oauth_verifier;
+ if (!verifier || typeof verifier !== 'string') {
+ ctx.throw(400, 'invalid session');
+ return;
+ }
+
+ const result = await twAuth!.done(JSON.parse(twCtx), verifier);
const link = await UserProfiles.createQueryBuilder()
- .where(`"integrations"->'twitter'->>'userId' = :id`, { id: result.userId })
+ .where('"integrations"->\'twitter\'->>\'userId\' = :id', { id: result.userId })
.andWhere('"userHost" IS NULL')
.getOne();
@@ -148,7 +154,7 @@ router.get('/tw/cb', async ctx => {
} else {
const verifier = ctx.query.oauth_verifier;
- if (verifier == null) {
+ if (!verifier || typeof verifier !== 'string') {
ctx.throw(400, 'invalid session');
return;
}
diff --git a/packages/backend/src/server/api/stream/channels/queue-stats.ts b/packages/backend/src/server/api/stream/channels/queue-stats.ts
index 043d03ab8..b67600474 100644
--- a/packages/backend/src/server/api/stream/channels/queue-stats.ts
+++ b/packages/backend/src/server/api/stream/channels/queue-stats.ts
@@ -1,7 +1,7 @@
-import { default as Xev } from 'xev';
+import Xev from 'xev';
import Channel from '../channel.js';
-const ev = new Xev.default();
+const ev = new Xev();
export default class extends Channel {
public readonly chName = 'queueStats';
diff --git a/packages/backend/src/server/api/stream/channels/server-stats.ts b/packages/backend/src/server/api/stream/channels/server-stats.ts
index 0da189576..db75a6fa3 100644
--- a/packages/backend/src/server/api/stream/channels/server-stats.ts
+++ b/packages/backend/src/server/api/stream/channels/server-stats.ts
@@ -1,7 +1,7 @@
-import { default as Xev } from 'xev';
+import Xev from 'xev';
import Channel from '../channel.js';
-const ev = new Xev.default();
+const ev = new Xev();
export default class extends Channel {
public readonly chName = 'serverStats';
diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts
index b80347828..2d23145f1 100644
--- a/packages/backend/src/server/api/stream/index.ts
+++ b/packages/backend/src/server/api/stream/index.ts
@@ -1,27 +1,25 @@
-import * as websocket from 'websocket';
-import { readNotification } from '../common/read-notification.js';
-import call from '../call.js';
-import readNote from '@/services/note/read.js';
-import Channel from './channel.js';
-import channels from './channels/index.js';
import { EventEmitter } from 'events';
+import * as websocket from 'websocket';
+import readNote from '@/services/note/read.js';
import { User } from '@/models/entities/user.js';
import { Channel as ChannelModel } from '@/models/entities/channel.js';
import { Users, Followings, Mutings, UserProfiles, ChannelFollowings, Blockings } from '@/models/index.js';
-import { ApiError } from '../error.js';
import { AccessToken } from '@/models/entities/access-token.js';
import { UserProfile } from '@/models/entities/user-profile.js';
import { publishChannelStream, publishGroupMessagingStream, publishMessagingStream } from '@/services/stream.js';
import { UserGroup } from '@/models/entities/user-group.js';
-import { StreamEventEmitter, StreamMessages } from './types.js';
import { Packed } from '@/misc/schema.js';
+import { readNotification } from '../common/read-notification.js';
+import channels from './channels/index.js';
+import Channel from './channel.js';
+import { StreamEventEmitter, StreamMessages } from './types.js';
/**
* Main stream connection
*/
export default class Connection {
public user?: User;
- public userProfile?: UserProfile;
+ public userProfile?: UserProfile | null;
public following: Set = new Set();
public muting: Set = new Set();
public blocking: Set = new Set(); // "被"blocking
@@ -84,7 +82,7 @@ export default class Connection {
this.muting.delete(data.body.id);
break;
- // TODO: block events
+ // TODO: block events
case 'followChannel':
this.followingChannels.add(data.body.id);
@@ -126,7 +124,6 @@ export default class Connection {
const { type, body } = obj;
switch (type) {
- case 'api': this.onApiRequest(body); break;
case 'readNotification': this.onReadNotification(body); break;
case 'subNote': this.onSubscribeNote(body); break;
case 's': this.onSubscribeNote(body); break; // alias
@@ -183,31 +180,6 @@ export default class Connection {
}
}
- /**
- * APIリクエスト要求時
- */
- private async onApiRequest(payload: any) {
- // 新鮮なデータを利用するためにユーザーをフェッチ
- const user = this.user ? await Users.findOneBy({ id: this.user.id }) : null;
-
- const endpoint = payload.endpoint || payload.ep; // alias
-
- // 呼び出し
- call(endpoint, user, this.token, payload.data).then(res => {
- this.sendMessageToWs(`api:${payload.id}`, { res });
- }).catch((e: ApiError) => {
- this.sendMessageToWs(`api:${payload.id}`, {
- error: {
- message: e.message,
- code: e.code,
- id: e.id,
- kind: e.kind,
- ...(e.info ? { info: e.info } : {}),
- },
- });
- });
- }
-
private onReadNotification(payload: any) {
if (!payload.id) return;
readNotification(this.user!.id, [payload.id]);
diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts
index bea863eb7..3b0a75d79 100644
--- a/packages/backend/src/server/api/stream/types.ts
+++ b/packages/backend/src/server/api/stream/types.ts
@@ -15,6 +15,7 @@ import { AbuseUserReport } from '@/models/entities/abuse-user-report.js';
import { Signin } from '@/models/entities/signin.js';
import { Page } from '@/models/entities/page.js';
import { Packed } from '@/misc/schema.js';
+import { Webhook } from '@/models/entities/webhook';
//#region Stream type-body definitions
export interface InternalStreamTypes {
@@ -23,6 +24,9 @@ export interface InternalStreamTypes {
userChangeModeratorState: { id: User['id']; isModerator: User['isModerator']; };
userTokenRegenerated: { id: User['id']; oldToken: User['token']; newToken: User['token']; };
remoteUserUpdated: { id: User['id']; };
+ webhookCreated: Webhook;
+ webhookDeleted: Webhook;
+ webhookUpdated: Webhook;
antennaCreated: Antenna;
antennaDeleted: Antenna;
antennaUpdated: Antenna;
diff --git a/packages/backend/src/server/api/streaming.ts b/packages/backend/src/server/api/streaming.ts
index 2a34edac6..f8e42d27f 100644
--- a/packages/backend/src/server/api/streaming.ts
+++ b/packages/backend/src/server/api/streaming.ts
@@ -1,4 +1,4 @@
-import * as http from 'http';
+import * as http from 'node:http';
import * as websocket from 'websocket';
import MainStreamConnection from './stream/index.js';
diff --git a/packages/backend/src/server/file/send-drive-file.ts b/packages/backend/src/server/file/send-drive-file.ts
index 6bc220b36..027d078ce 100644
--- a/packages/backend/src/server/file/send-drive-file.ts
+++ b/packages/backend/src/server/file/send-drive-file.ts
@@ -11,7 +11,7 @@ import { DriveFiles } from '@/models/index.js';
import { InternalStorage } from '@/services/drive/internal-storage.js';
import { downloadUrl } from '@/misc/download-url.js';
import { detectType } from '@/misc/get-file-info.js';
-import { convertToJpeg, convertToPng, convertToPngOrJpeg } from '@/services/drive/image-processor.js';
+import { convertToWebp, convertToJpeg, convertToPng } from '@/services/drive/image-processor.js';
import { GenerateVideoThumbnail } from '@/services/drive/generate-video-thumbnail.js';
import { StatusError } from '@/misc/fetch.js';
import { FILE_TYPE_BROWSERSAFE } from '@/const.js';
@@ -64,10 +64,8 @@ export default async function(ctx: Koa.Context) {
const convertFile = async () => {
if (isThumbnail) {
- if (['image/jpeg', 'image/webp'].includes(mime)) {
- return await convertToJpeg(path, 498, 280);
- } else if (['image/png', 'image/svg+xml'].includes(mime)) {
- return await convertToPngOrJpeg(path, 498, 280);
+ if (['image/jpeg', 'image/webp', 'image/png', 'image/svg+xml'].includes(mime)) {
+ return await convertToWebp(path, 498, 280);
} else if (mime.startsWith('video/')) {
return await GenerateVideoThumbnail(path);
}
diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts
index a68cebfeb..c1a2a6dff 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -3,30 +3,30 @@
*/
import * as fs from 'node:fs';
-import * as http from 'http';
+import * as http from 'node:http';
import Koa from 'koa';
import Router from '@koa/router';
import mount from 'koa-mount';
import koaLogger from 'koa-logger';
import * as slow from 'koa-slow';
-import activityPub from './activitypub.js';
-import nodeinfo from './nodeinfo.js';
-import wellKnown from './well-known.js';
+import { IsNull } from 'typeorm';
import config from '@/config/index.js';
-import apiServer from './api/index.js';
-import fileServer from './file/index.js';
-import proxyServer from './proxy/index.js';
-import webServer from './web/index.js';
import Logger from '@/services/logger.js';
-import { envOption } from '../env.js';
import { UserProfiles, Users } from '@/models/index.js';
import { genIdenticon } from '@/misc/gen-identicon.js';
import { createTemp } from '@/misc/create-temp.js';
import { publishMainStream } from '@/services/stream.js';
import * as Acct from '@/misc/acct.js';
+import { envOption } from '../env.js';
+import activityPub from './activitypub.js';
+import nodeinfo from './nodeinfo.js';
+import wellKnown from './well-known.js';
+import apiServer from './api/index.js';
+import fileServer from './file/index.js';
+import proxyServer from './proxy/index.js';
+import webServer from './web/index.js';
import { initializeStreamingServer } from './api/streaming.js';
-import { IsNull } from 'typeorm';
export const serverLogger = new Logger('server', 'gray', false);
@@ -81,7 +81,7 @@ router.get('/avatar/@:acct', async ctx => {
});
if (user) {
- ctx.redirect(Users.getAvatarUrl(user));
+ ctx.redirect(Users.getAvatarUrlSync(user));
} else {
ctx.redirect('/static-assets/user-unknown.png');
}
diff --git a/packages/backend/src/server/proxy/proxy-media.ts b/packages/backend/src/server/proxy/proxy-media.ts
index 3cc5b827a..48887bf12 100644
--- a/packages/backend/src/server/proxy/proxy-media.ts
+++ b/packages/backend/src/server/proxy/proxy-media.ts
@@ -1,7 +1,7 @@
import * as fs from 'node:fs';
import Koa from 'koa';
import { serverLogger } from '../index.js';
-import { IImage, convertToPng, convertToJpeg } from '@/services/drive/image-processor.js';
+import { IImage, convertToWebp } from '@/services/drive/image-processor.js';
import { createTemp } from '@/misc/create-temp.js';
import { downloadUrl } from '@/misc/download-url.js';
import { detectType } from '@/misc/get-file-info.js';
@@ -27,11 +27,11 @@ export async function proxyMedia(ctx: Koa.Context) {
let image: IImage;
if ('static' in ctx.query && ['image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp', 'image/svg+xml'].includes(mime)) {
- image = await convertToPng(path, 498, 280);
+ image = await convertToWebp(path, 498, 280);
} else if ('preview' in ctx.query && ['image/jpeg', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/svg+xml'].includes(mime)) {
- image = await convertToJpeg(path, 200, 200);
+ image = await convertToWebp(path, 200, 200);
} else if (['image/svg+xml'].includes(mime)) {
- image = await convertToPng(path, 2048, 2048);
+ image = await convertToWebp(path, 2048, 2048, 1);
} else if (!mime.startsWith('image/') || !FILE_TYPE_BROWSERSAFE.includes(mime)) {
throw new StatusError('Rejected type', 403, 'Rejected type');
} else {
diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js
index 751e8619b..a9ee0df4f 100644
--- a/packages/backend/src/server/web/boot.js
+++ b/packages/backend/src/server/web/boot.js
@@ -58,15 +58,11 @@
? `?salt=${localStorage.getItem('salt')}`
: '';
- const script = document.createElement('script');
- script.setAttribute('src', `/assets/app.${v}.js${salt}`);
- script.setAttribute('async', 'true');
- script.setAttribute('defer', 'true');
- script.addEventListener('error', async () => {
- await checkUpdate();
- renderError('APP_FETCH_FAILED');
- });
- document.head.appendChild(script);
+ import(`/assets/${CLIENT_ENTRY}${salt}`)
+ .catch(async () => {
+ await checkUpdate();
+ renderError('APP_FETCH_FAILED');
+ })
//#endregion
//#region Theme
diff --git a/packages/backend/src/server/web/feed.ts b/packages/backend/src/server/web/feed.ts
index eba8dc58d..4abe2885c 100644
--- a/packages/backend/src/server/web/feed.ts
+++ b/packages/backend/src/server/web/feed.ts
@@ -1,8 +1,8 @@
import { Feed } from 'feed';
+import { In, IsNull } from 'typeorm';
import config from '@/config/index.js';
import { User } from '@/models/entities/user.js';
-import { Notes, DriveFiles, UserProfiles } from '@/models/index.js';
-import { In, IsNull } from 'typeorm';
+import { Notes, DriveFiles, UserProfiles, Users } from '@/models/index.js';
export default async function(user: User) {
const author = {
@@ -29,7 +29,7 @@ export default async function(user: User) {
generator: 'Misskey',
description: `${user.notesCount} Notes, ${profile.ffVisibility === 'public' ? user.followingCount : '?'} Following, ${profile.ffVisibility === 'public' ? user.followersCount : '?'} Followers${profile.description ? ` · ${profile.description}` : ''}`,
link: author.link,
- image: user.avatarUrl ? user.avatarUrl : undefined,
+ image: await Users.getAvatarUrl(user),
feedLinks: {
json: `${author.link}.json`,
atom: `${author.link}.atom`,
diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts
index 48bf6f733..9e31f2389 100644
--- a/packages/backend/src/server/web/index.ts
+++ b/packages/backend/src/server/web/index.ts
@@ -4,6 +4,7 @@
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
+import { PathOrFileDescriptor, readFileSync } from 'node:fs';
import ms from 'ms';
import Koa from 'koa';
import Router from '@koa/router';
@@ -11,20 +12,20 @@ import send from 'koa-send';
import favicon from 'koa-favicon';
import views from 'koa-views';
import { createBullBoard } from '@bull-board/api';
-import { BullAdapter } from '@bull-board/api/bullAdapter.js';
+import { BullAdapter } from '@bull-board/api/bullAdapter.js';
import { KoaAdapter } from '@bull-board/koa';
-import packFeed from './feed.js';
+import { In, IsNull } from 'typeorm';
import { fetchMeta } from '@/misc/fetch-meta.js';
-import { genOpenapiSpec } from '../api/openapi/gen-spec.js';
import config from '@/config/index.js';
import { Users, Notes, UserProfiles, Pages, Channels, Clips, GalleryPosts } from '@/models/index.js';
import * as Acct from '@/misc/acct.js';
import { getNoteSummary } from '@/misc/get-note-summary.js';
+import { queues } from '@/queue/queues.js';
+import { genOpenapiSpec } from '../api/openapi/gen-spec.js';
import { urlPreviewHandler } from './url-preview.js';
import { manifestHandler } from './manifest.js';
-import { queues } from '@/queue/queues.js';
-import { IsNull } from 'typeorm';
+import packFeed from './feed.js';
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
@@ -32,6 +33,7 @@ const _dirname = dirname(_filename);
const staticAssets = `${_dirname}/../../../assets/`;
const clientAssets = `${_dirname}/../../../../client/assets/`;
const assets = `${_dirname}/../../../../../built/_client_dist_/`;
+const swAssets = `${_dirname}/../../../../../built/_sw_dist_/`;
// Init app
const app = new Koa();
@@ -72,6 +74,9 @@ app.use(views(_dirname + '/views', {
extension: 'pug',
options: {
version: config.version,
+ clientEntry: () => process.env.NODE_ENV === 'production' ?
+ config.clientEntry :
+ JSON.parse(readFileSync(`${_dirname}/../../../../../built/_client_dist_/manifest.json`, 'utf-8'))['src/init.ts'].file.replace(/^_client_dist_\//, ''),
config,
},
}));
@@ -127,7 +132,7 @@ router.get('/twemoji/(.*)', async ctx => {
return;
}
- ctx.set('Content-Security-Policy', `default-src 'none'; style-src 'unsafe-inline'`);
+ ctx.set('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\'');
await send(ctx as any, path, {
root: `${_dirname}/../../../node_modules/@discordapp/twemoji/dist/svg/`,
@@ -136,9 +141,10 @@ router.get('/twemoji/(.*)', async ctx => {
});
// ServiceWorker
-router.get('/sw.js', async ctx => {
- await send(ctx as any, `/sw.${config.version}.js`, {
- root: assets,
+router.get(`/sw.js`, async ctx => {
+ await send(ctx as any, `/sw.js`, {
+ root: swAssets,
+ maxage: ms('10 minutes'),
});
});
@@ -235,6 +241,7 @@ router.get(['/@:user', '/@:user/:sub'], async (ctx, next) => {
await ctx.render('user', {
user, profile, me,
+ avatarUrl: await Users.getAvatarUrl(user),
sub: ctx.params.sub,
instanceName: meta.name || 'Misskey',
icon: meta.iconUrl,
@@ -265,7 +272,10 @@ router.get('/users/:user', async ctx => {
// Note
router.get('/notes/:note', async (ctx, next) => {
- const note = await Notes.findOneBy({ id: ctx.params.note });
+ const note = await Notes.findOneBy({
+ id: ctx.params.note,
+ visibility: In(['public', 'home']),
+ });
if (note) {
const _note = await Notes.pack(note);
@@ -274,6 +284,7 @@ router.get('/notes/:note', async (ctx, next) => {
await ctx.render('note', {
note: _note,
profile,
+ avatarUrl: await Users.getAvatarUrl(await Users.findOneByOrFail({ id: note.userId })),
// TODO: Let locale changeable by instance setting
summary: getNoteSummary(_note),
instanceName: meta.name || 'Misskey',
@@ -281,11 +292,7 @@ router.get('/notes/:note', async (ctx, next) => {
themeColor: meta.themeColor,
});
- if (['public', 'home'].includes(note.visibility)) {
- ctx.set('Cache-Control', 'public, max-age=180');
- } else {
- ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
- }
+ ctx.set('Cache-Control', 'public, max-age=180');
return;
}
@@ -315,6 +322,7 @@ router.get('/@:user/pages/:page', async (ctx, next) => {
await ctx.render('page', {
page: _page,
profile,
+ avatarUrl: await Users.getAvatarUrl(await Users.findOneByOrFail({ id: page.userId })),
instanceName: meta.name || 'Misskey',
icon: meta.iconUrl,
themeColor: meta.themeColor,
@@ -346,6 +354,7 @@ router.get('/clips/:clip', async (ctx, next) => {
await ctx.render('clip', {
clip: _clip,
profile,
+ avatarUrl: await Users.getAvatarUrl(await Users.findOneByOrFail({ id: clip.userId })),
instanceName: meta.name || 'Misskey',
icon: meta.iconUrl,
themeColor: meta.themeColor,
@@ -370,6 +379,7 @@ router.get('/gallery/:post', async (ctx, next) => {
await ctx.render('gallery-post', {
post: _post,
profile,
+ avatarUrl: await Users.getAvatarUrl(await Users.findOneByOrFail({ id: post.userId })),
instanceName: meta.name || 'Misskey',
icon: meta.iconUrl,
themeColor: meta.themeColor,
@@ -434,7 +444,7 @@ router.get('/cli', async ctx => {
});
});
-const override = (source: string, target: string, depth: number = 0) =>
+const override = (source: string, target: string, depth = 0) =>
[, ...target.split('/').filter(x => x), ...source.split('/').filter(x => x).splice(depth)].join('/');
router.get('/flush', async ctx => {
diff --git a/packages/backend/src/server/web/manifest.ts b/packages/backend/src/server/web/manifest.ts
index bcbf9b76a..61d766006 100644
--- a/packages/backend/src/server/web/manifest.ts
+++ b/packages/backend/src/server/web/manifest.ts
@@ -1,16 +1,16 @@
import Koa from 'koa';
-import manifest from './manifest.json' assert { type: 'json' };
import { fetchMeta } from '@/misc/fetch-meta.js';
+import manifest from './manifest.json' assert { type: 'json' };
export const manifestHandler = async (ctx: Koa.Context) => {
- const json = JSON.parse(JSON.stringify(manifest));
+ const res = structuredClone(manifest);
const instance = await fetchMeta(true);
- json.short_name = instance.name || 'Misskey';
- json.name = instance.name || 'Misskey';
- if (instance.themeColor) json.theme_color = instance.themeColor;
+ res.short_name = instance.name || 'Misskey';
+ res.name = instance.name || 'Misskey';
+ if (instance.themeColor) res.theme_color = instance.themeColor;
ctx.set('Cache-Control', 'max-age=300');
- ctx.body = json;
+ ctx.body = res;
};
diff --git a/packages/backend/src/server/web/style.css b/packages/backend/src/server/web/style.css
index 43fbe1ab0..9c4cd4b9b 100644
--- a/packages/backend/src/server/web/style.css
+++ b/packages/backend/src/server/web/style.css
@@ -16,7 +16,7 @@ html {
transition: opacity 0.5s ease;
}
-#splash > img {
+#splashIcon {
position: absolute;
top: 0;
right: 0;
@@ -27,3 +27,48 @@ html {
height: 64px;
pointer-events: none;
}
+
+#splashSpinner {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ margin: auto;
+ display: inline-block;
+ width: 28px;
+ height: 28px;
+ transform: translateY(70px);
+}
+
+#splashSpinner:before,
+#splashSpinner:after {
+ content: " ";
+ display: block;
+ box-sizing: border-box;
+ width: 28px;
+ height: 28px;
+ border-radius: 50%;
+ border: solid 4px;
+}
+
+#splashSpinner:before {
+ border-color: currentColor;
+ opacity: 0.3;
+}
+
+#splashSpinner:after {
+ position: absolute;
+ top: 0;
+ border-color: currentColor transparent transparent transparent;
+ animation: splashSpinner 0.5s linear infinite;
+}
+
+@keyframes splashSpinner {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
diff --git a/packages/backend/src/server/web/url-preview.ts b/packages/backend/src/server/web/url-preview.ts
index 6bd8ead5b..1e259649f 100644
--- a/packages/backend/src/server/web/url-preview.ts
+++ b/packages/backend/src/server/web/url-preview.ts
@@ -56,7 +56,7 @@ export const urlPreviewHandler = async (ctx: Koa.Context) => {
function wrap(url?: string): string | null {
return url != null
? url.match(/^https?:\/\//)
- ? `${config.url}/proxy/preview.jpg?${query({
+ ? `${config.url}/proxy/preview.webp?${query({
url,
preview: '1',
})}`
diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug
index abacb1ccf..d79354d11 100644
--- a/packages/backend/src/server/web/views/base.pug
+++ b/packages/backend/src/server/web/views/base.pug
@@ -50,6 +50,10 @@ html
style
include ../style.css
+ script.
+ var VERSION = "#{version}";
+ var CLIENT_ENTRY = "#{clientEntry()}";
+
script
include ../boot.js
@@ -59,5 +63,6 @@ html
br
| Please turn on your JavaScript
div#splash
- img(src= icon || '/static-assets/splash.png')
+ img#splashIcon(src= icon || '/static-assets/splash.png')
+ div#splashSpinner
block content
diff --git a/packages/backend/src/server/web/views/clip.pug b/packages/backend/src/server/web/views/clip.pug
index 7a84d50f6..4c692bf59 100644
--- a/packages/backend/src/server/web/views/clip.pug
+++ b/packages/backend/src/server/web/views/clip.pug
@@ -16,7 +16,7 @@ block og
meta(property='og:title' content= title)
meta(property='og:description' content= clip.description)
meta(property='og:url' content= url)
- meta(property='og:image' content= user.avatarUrl)
+ meta(property='og:image' content= avatarUrl)
block meta
if profile.noCrawle
diff --git a/packages/backend/src/server/web/views/note.pug b/packages/backend/src/server/web/views/note.pug
index 34b03f983..65696ea13 100644
--- a/packages/backend/src/server/web/views/note.pug
+++ b/packages/backend/src/server/web/views/note.pug
@@ -17,7 +17,7 @@ block og
meta(property='og:title' content= title)
meta(property='og:description' content= summary)
meta(property='og:url' content= url)
- meta(property='og:image' content= user.avatarUrl)
+ meta(property='og:image' content= avatarUrl)
block meta
if user.host || isRenote || profile.noCrawle
diff --git a/packages/backend/src/server/web/views/page.pug b/packages/backend/src/server/web/views/page.pug
index b6c954802..4219e76a5 100644
--- a/packages/backend/src/server/web/views/page.pug
+++ b/packages/backend/src/server/web/views/page.pug
@@ -16,7 +16,7 @@ block og
meta(property='og:title' content= title)
meta(property='og:description' content= page.summary)
meta(property='og:url' content= url)
- meta(property='og:image' content= page.eyeCatchingImage ? page.eyeCatchingImage.thumbnailUrl : user.avatarUrl)
+ meta(property='og:image' content= page.eyeCatchingImage ? page.eyeCatchingImage.thumbnailUrl : avatarUrl)
block meta
if profile.noCrawle
diff --git a/packages/backend/src/server/web/views/user.pug b/packages/backend/src/server/web/views/user.pug
index 2adec0f88..119993fdb 100644
--- a/packages/backend/src/server/web/views/user.pug
+++ b/packages/backend/src/server/web/views/user.pug
@@ -3,7 +3,6 @@ extends ./base
block vars
- const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`;
- const url = `${config.url}/@${(user.host ? `${user.username}@${user.host}` : user.username)}`;
- - const img = user.avatarUrl || null;
block title
= `${title} | ${instanceName}`
@@ -16,7 +15,7 @@ block og
meta(property='og:title' content= title)
meta(property='og:description' content= profile.description)
meta(property='og:url' content= url)
- meta(property='og:image' content= img)
+ meta(property='og:image' content= avatarUrl)
block meta
if user.host || profile.noCrawle
diff --git a/packages/backend/src/server/well-known.ts b/packages/backend/src/server/well-known.ts
index 7530b4e0b..1d094f2ed 100644
--- a/packages/backend/src/server/well-known.ts
+++ b/packages/backend/src/server/well-known.ts
@@ -41,6 +41,7 @@ router.options(allPath, async ctx => {
router.get('/.well-known/host-meta', async ctx => {
ctx.set('Content-Type', xrd);
ctx.body = XRD({ element: 'Link', attributes: {
+ rel: 'lrdd',
type: xrd,
template: `${config.url}${webFingerPath}?resource={uri}`,
} });
diff --git a/packages/backend/src/services/blocking/create.ts b/packages/backend/src/services/blocking/create.ts
index 86c7d7967..b2be78b22 100644
--- a/packages/backend/src/services/blocking/create.ts
+++ b/packages/backend/src/services/blocking/create.ts
@@ -10,6 +10,8 @@ import { Blockings, Users, FollowRequests, Followings, UserListJoinings, UserLis
import { perUserFollowingChart } from '@/services/chart/index.js';
import { genId } from '@/misc/gen-id.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
+import { getActiveWebhooks } from '@/misc/webhook-cache.js';
+import { webhookDeliver } from '@/queue/index.js';
export default async function(blocker: User, blockee: User) {
await Promise.all([
@@ -57,9 +59,16 @@ async function cancelRequest(follower: User, followee: User) {
if (Users.isLocalUser(follower)) {
Users.pack(followee, follower, {
detail: true,
- }).then(packed => {
+ }).then(async packed => {
publishUserEvent(follower.id, 'unfollow', packed);
publishMainStream(follower.id, 'unfollow', packed);
+
+ const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow'));
+ for (const webhook of webhooks) {
+ webhookDeliver(webhook, 'unfollow', {
+ user: packed,
+ });
+ }
});
}
@@ -86,25 +95,27 @@ async function unFollow(follower: User, followee: User) {
return;
}
- Followings.delete(following.id);
-
- //#region Decrement following count
- Users.decrement({ id: follower.id }, 'followingCount', 1);
- //#endregion
-
- //#region Decrement followers count
- Users.decrement({ id: followee.id }, 'followersCount', 1);
- //#endregion
-
- perUserFollowingChart.update(follower, followee, false);
+ await Promise.all([
+ Followings.delete(following.id),
+ Users.decrement({ id: follower.id }, 'followingCount', 1),
+ Users.decrement({ id: followee.id }, 'followersCount', 1),
+ perUserFollowingChart.update(follower, followee, false),
+ ]);
// Publish unfollow event
if (Users.isLocalUser(follower)) {
Users.pack(followee, follower, {
detail: true,
- }).then(packed => {
+ }).then(async packed => {
publishUserEvent(follower.id, 'unfollow', packed);
publishMainStream(follower.id, 'unfollow', packed);
+
+ const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow'));
+ for (const webhook of webhooks) {
+ webhookDeliver(webhook, 'unfollow', {
+ user: packed,
+ });
+ }
});
}
diff --git a/packages/backend/src/services/create-notification.ts b/packages/backend/src/services/create-notification.ts
index 9a53db1f3..d53a4235b 100644
--- a/packages/backend/src/services/create-notification.ts
+++ b/packages/backend/src/services/create-notification.ts
@@ -1,5 +1,5 @@
import { publishMainStream } from '@/services/stream.js';
-import pushSw from './push-notification.js';
+import { pushNotification } from '@/services/push-notification.js';
import { Notifications, Mutings, UserProfiles, Users } from '@/models/index.js';
import { genId } from '@/misc/gen-id.js';
import { User } from '@/models/entities/user.js';
@@ -52,8 +52,8 @@ export async function createNotification(
//#endregion
publishMainStream(notifieeId, 'unreadNotification', packed);
+ pushNotification(notifieeId, 'notification', packed);
- pushSw(notifieeId, 'notification', packed);
if (type === 'follow') sendEmailNotification.follow(notifieeId, await Users.findOneByOrFail({ id: data.notifierId! }));
if (type === 'receiveFollowRequest') sendEmailNotification.receiveFollowRequest(notifieeId, await Users.findOneByOrFail({ id: data.notifierId! }));
}, 2000);
diff --git a/packages/backend/src/services/drive/add-file.ts b/packages/backend/src/services/drive/add-file.ts
index 549b11c9f..cfbcb60dd 100644
--- a/packages/backend/src/services/drive/add-file.ts
+++ b/packages/backend/src/services/drive/add-file.ts
@@ -7,7 +7,7 @@ import { deleteFile } from './delete-file.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
import { GenerateVideoThumbnail } from './generate-video-thumbnail.js';
import { driveLogger } from './logger.js';
-import { IImage, convertSharpToJpeg, convertSharpToWebp, convertSharpToPng, convertSharpToPngOrJpeg } from './image-processor.js';
+import { IImage, convertSharpToJpeg, convertSharpToWebp, convertSharpToPng } from './image-processor.js';
import { contentDisposition } from '@/misc/content-disposition.js';
import { getFileInfo } from '@/misc/get-file-info.js';
import { DriveFiles, DriveFolders, Users, Instances, UserProfiles } from '@/models/index.js';
@@ -179,6 +179,7 @@ export async function generateAlts(path: string, type: string, generateWeb: bool
}
let img: sharp.Sharp | null = null;
+ let satisfyWebpublic: boolean;
try {
img = sharp(path);
@@ -192,6 +193,13 @@ export async function generateAlts(path: string, type: string, generateWeb: bool
thumbnail: null,
};
}
+
+ satisfyWebpublic = !!(
+ type !== 'image/svg+xml' && type !== 'image/webp' &&
+ !(metadata.exif || metadata.iptc || metadata.xmp || metadata.tifftagPhotoshop) &&
+ metadata.width && metadata.width <= 2048 &&
+ metadata.height && metadata.height <= 2048
+ );
} catch (err) {
logger.warn(`sharp failed: ${err}`);
return {
@@ -203,15 +211,15 @@ export async function generateAlts(path: string, type: string, generateWeb: bool
// #region webpublic
let webpublic: IImage | null = null;
- if (generateWeb) {
+ if (generateWeb && !satisfyWebpublic) {
logger.info(`creating web image`);
try {
- if (['image/jpeg'].includes(type)) {
+ if (['image/jpeg', 'image/webp'].includes(type)) {
webpublic = await convertSharpToJpeg(img, 2048, 2048);
- } else if (['image/webp'].includes(type)) {
- webpublic = await convertSharpToWebp(img, 2048, 2048);
- } else if (['image/png', 'image/svg+xml'].includes(type)) {
+ } else if (['image/png'].includes(type)) {
+ webpublic = await convertSharpToPng(img, 2048, 2048);
+ } else if (['image/svg+xml'].includes(type)) {
webpublic = await convertSharpToPng(img, 2048, 2048);
} else {
logger.debug(`web image not created (not an required image)`);
@@ -220,7 +228,8 @@ export async function generateAlts(path: string, type: string, generateWeb: bool
logger.warn(`web image not created (an error occured)`, err as Error);
}
} else {
- logger.info(`web image not created (from remote)`);
+ if (satisfyWebpublic) logger.info(`web image not created (original satisfies webpublic)`);
+ else logger.info(`web image not created (from remote)`);
}
// #endregion webpublic
@@ -228,10 +237,8 @@ export async function generateAlts(path: string, type: string, generateWeb: bool
let thumbnail: IImage | null = null;
try {
- if (['image/jpeg', 'image/webp'].includes(type)) {
- thumbnail = await convertSharpToJpeg(img, 498, 280);
- } else if (['image/png', 'image/svg+xml'].includes(type)) {
- thumbnail = await convertSharpToPngOrJpeg(img, 498, 280);
+ if (['image/jpeg', 'image/webp', 'image/png', 'image/svg+xml'].includes(type)) {
+ thumbnail = await convertSharpToWebp(img, 498, 280);
} else {
logger.debug(`thumbnail not created (not an required file)`);
}
diff --git a/packages/backend/src/services/drive/generate-video-thumbnail.ts b/packages/backend/src/services/drive/generate-video-thumbnail.ts
index 04a7a8334..da93bc97c 100644
--- a/packages/backend/src/services/drive/generate-video-thumbnail.ts
+++ b/packages/backend/src/services/drive/generate-video-thumbnail.ts
@@ -27,6 +27,7 @@ export async function GenerateVideoThumbnail(path: string): Promise {
const outPath = `${outDir}/output.png`;
+ // JPEGに変換 (Webpでもいいが、MastodonはWebpをサポートせず表示できなくなる)
const thumbnail = await convertToJpeg(outPath, 498, 280);
// cleanup
diff --git a/packages/backend/src/services/drive/image-processor.ts b/packages/backend/src/services/drive/image-processor.ts
index 146dcfb6c..2c564ea59 100644
--- a/packages/backend/src/services/drive/image-processor.ts
+++ b/packages/backend/src/services/drive/image-processor.ts
@@ -38,11 +38,11 @@ export async function convertSharpToJpeg(sharp: sharp.Sharp, width: number, heig
* Convert to WebP
* with resize, remove metadata, resolve orientation, stop animation
*/
-export async function convertToWebp(path: string, width: number, height: number): Promise {
- return convertSharpToWebp(await sharp(path), width, height);
+export async function convertToWebp(path: string, width: number, height: number, quality: number = 85): Promise {
+ return convertSharpToWebp(await sharp(path), width, height, quality);
}
-export async function convertSharpToWebp(sharp: sharp.Sharp, width: number, height: number): Promise {
+export async function convertSharpToWebp(sharp: sharp.Sharp, width: number, height: number, quality: number = 85): Promise {
const data = await sharp
.resize(width, height, {
fit: 'inside',
@@ -50,7 +50,7 @@ export async function convertSharpToWebp(sharp: sharp.Sharp, width: number, heig
})
.rotate()
.webp({
- quality: 85,
+ quality,
})
.toBuffer();
@@ -85,23 +85,3 @@ export async function convertSharpToPng(sharp: sharp.Sharp, width: number, heigh
type: 'image/png',
};
}
-
-/**
- * Convert to PNG or JPEG
- * with resize, remove metadata, resolve orientation, stop animation
- */
-export async function convertToPngOrJpeg(path: string, width: number, height: number): Promise {
- return convertSharpToPngOrJpeg(await sharp(path), width, height);
-}
-
-export async function convertSharpToPngOrJpeg(sharp: sharp.Sharp, width: number, height: number): Promise {
- const stats = await sharp.stats();
- const metadata = await sharp.metadata();
-
- // 不透明で300x300pxの範囲を超えていればJPEG
- if (stats.isOpaque && ((metadata.width && metadata.width >= 300) || (metadata.height && metadata!.height >= 300))) {
- return await convertSharpToJpeg(sharp, width, height);
- } else {
- return await convertSharpToPng(sharp, width, height);
- }
-}
diff --git a/packages/backend/src/services/drive/upload-from-url.ts b/packages/backend/src/services/drive/upload-from-url.ts
index 5007fff6e..79b1b8c2e 100644
--- a/packages/backend/src/services/drive/upload-from-url.ts
+++ b/packages/backend/src/services/drive/upload-from-url.ts
@@ -29,7 +29,7 @@ export async function uploadFromUrl({
sensitive = false,
force = false,
isLink = false,
- comment = null
+ comment = null,
}: Args): Promise {
let name = new URL(url).pathname.split('/').pop() || null;
if (name == null || !DriveFiles.validateFileName(name)) {
@@ -38,7 +38,7 @@ export async function uploadFromUrl({
// If the comment is same as the name, skip comment
// (image.name is passed in when receiving attachment)
- if (comment !== null && name == comment) {
+ if (comment !== null && name === comment) {
comment = null;
}
diff --git a/packages/backend/src/services/fetch-instance-metadata.ts b/packages/backend/src/services/fetch-instance-metadata.ts
index 2b6f82a91..d5294c5fe 100644
--- a/packages/backend/src/services/fetch-instance-metadata.ts
+++ b/packages/backend/src/services/fetch-instance-metadata.ts
@@ -97,7 +97,7 @@ async function fetchNodeinfo(instance: Instance): Promise {
} else {
throw e.statusCode || e.message;
}
- });
+ }) as Record;
if (wellknown.links == null || !Array.isArray(wellknown.links)) {
throw 'No wellknown links';
@@ -121,7 +121,7 @@ async function fetchNodeinfo(instance: Instance): Promise {
logger.succ(`Successfuly fetched nodeinfo of ${instance.host}`);
- return info;
+ return info as NodeInfo;
} catch (e) {
logger.error(`Failed to fetch nodeinfo of ${instance.host}: ${e}`);
@@ -142,12 +142,12 @@ async function fetchDom(instance: Instance): Promise {
return doc;
}
-async function fetchManifest(instance: Instance): Promise | null> {
+async function fetchManifest(instance: Instance): Promise | null> {
const url = 'https://' + instance.host;
const manifestUrl = url + '/manifest.json';
- const manifest = await getJson(manifestUrl);
+ const manifest = await getJson(manifestUrl) as Record;
return manifest;
}
@@ -167,7 +167,8 @@ async function fetchFaviconUrl(instance: Instance, doc: DOMWindow['document'] |
const faviconUrl = url + '/favicon.ico';
const favicon = await fetch(faviconUrl, {
- timeout: 10000,
+ // TODO
+ //timeout: 10000,
agent: getAgentByUrl,
});
diff --git a/packages/backend/src/services/following/create.ts b/packages/backend/src/services/following/create.ts
index 0daf30dda..72c24676b 100644
--- a/packages/backend/src/services/following/create.ts
+++ b/packages/backend/src/services/following/create.ts
@@ -15,6 +15,8 @@ import { genId } from '@/misc/gen-id.js';
import { createNotification } from '../create-notification.js';
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
import { Packed } from '@/misc/schema.js';
+import { getActiveWebhooks } from '@/misc/webhook-cache.js';
+import { webhookDeliver } from '@/queue/index.js';
const logger = new Logger('following/create');
@@ -65,8 +67,10 @@ export async function insertFollowingDoc(followee: { id: User['id']; host: User[
if (alreadyFollowed) return;
//#region Increment counts
- Users.increment({ id: follower.id }, 'followingCount', 1);
- Users.increment({ id: followee.id }, 'followersCount', 1);
+ await Promise.all([
+ Users.increment({ id: follower.id }, 'followingCount', 1),
+ Users.increment({ id: followee.id }, 'followersCount', 1),
+ ]);
//#endregion
//#region Update instance stats
@@ -89,15 +93,31 @@ export async function insertFollowingDoc(followee: { id: User['id']; host: User[
if (Users.isLocalUser(follower)) {
Users.pack(followee.id, follower, {
detail: true,
- }).then(packed => {
+ }).then(async packed => {
publishUserEvent(follower.id, 'follow', packed as Packed<"UserDetailedNotMe">);
publishMainStream(follower.id, 'follow', packed as Packed<"UserDetailedNotMe">);
+
+ const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('follow'));
+ for (const webhook of webhooks) {
+ webhookDeliver(webhook, 'follow', {
+ user: packed,
+ });
+ }
});
}
// Publish followed event
if (Users.isLocalUser(followee)) {
- Users.pack(follower.id, followee).then(packed => publishMainStream(followee.id, 'followed', packed));
+ Users.pack(follower.id, followee).then(async packed => {
+ publishMainStream(followee.id, 'followed', packed);
+
+ const webhooks = (await getActiveWebhooks()).filter(x => x.userId === followee.id && x.on.includes('followed'));
+ for (const webhook of webhooks) {
+ webhookDeliver(webhook, 'followed', {
+ user: packed,
+ });
+ }
+ });
// 通知を作成
createNotification(followee.id, 'follow', {
diff --git a/packages/backend/src/services/following/delete.ts b/packages/backend/src/services/following/delete.ts
index 35fd664b5..91b5a3d61 100644
--- a/packages/backend/src/services/following/delete.ts
+++ b/packages/backend/src/services/following/delete.ts
@@ -3,12 +3,13 @@ import { renderActivity } from '@/remote/activitypub/renderer/index.js';
import renderFollow from '@/remote/activitypub/renderer/follow.js';
import renderUndo from '@/remote/activitypub/renderer/undo.js';
import renderReject from '@/remote/activitypub/renderer/reject.js';
-import { deliver } from '@/queue/index.js';
+import { deliver, webhookDeliver } from '@/queue/index.js';
import Logger from '../logger.js';
import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc.js';
import { User } from '@/models/entities/user.js';
import { Followings, Users, Instances } from '@/models/index.js';
import { instanceChart, perUserFollowingChart } from '@/services/chart/index.js';
+import { getActiveWebhooks } from '@/misc/webhook-cache.js';
const logger = new Logger('following/delete');
@@ -31,9 +32,16 @@ export default async function(follower: { id: User['id']; host: User['host']; ur
if (!silent && Users.isLocalUser(follower)) {
Users.pack(followee.id, follower, {
detail: true,
- }).then(packed => {
+ }).then(async packed => {
publishUserEvent(follower.id, 'unfollow', packed);
publishMainStream(follower.id, 'unfollow', packed);
+
+ const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow'));
+ for (const webhook of webhooks) {
+ webhookDeliver(webhook, 'unfollow', {
+ user: packed,
+ });
+ }
});
}
@@ -50,12 +58,11 @@ export default async function(follower: { id: User['id']; host: User['host']; ur
}
export async function decrementFollowing(follower: { id: User['id']; host: User['host']; }, followee: { id: User['id']; host: User['host']; }) {
- //#region Decrement following count
- Users.decrement({ id: follower.id }, 'followingCount', 1);
- //#endregion
-
- //#region Decrement followers count
- Users.decrement({ id: followee.id }, 'followersCount', 1);
+ //#region Decrement following / followers counts
+ await Promise.all([
+ Users.decrement({ id: follower.id }, 'followingCount', 1),
+ Users.decrement({ id: followee.id }, 'followersCount', 1),
+ ]);
//#endregion
//#region Update instance stats
diff --git a/packages/backend/src/services/following/reject.ts b/packages/backend/src/services/following/reject.ts
index 2d1db3c34..691fca245 100644
--- a/packages/backend/src/services/following/reject.ts
+++ b/packages/backend/src/services/following/reject.ts
@@ -1,11 +1,12 @@
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
import renderFollow from '@/remote/activitypub/renderer/follow.js';
import renderReject from '@/remote/activitypub/renderer/reject.js';
-import { deliver } from '@/queue/index.js';
+import { deliver, webhookDeliver } from '@/queue/index.js';
import { publishMainStream, publishUserEvent } from '@/services/stream.js';
import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js';
import { Users, FollowRequests, Followings } from '@/models/index.js';
import { decrementFollowing } from './delete.js';
+import { getActiveWebhooks } from '@/misc/webhook-cache.js';
type Local = ILocalUser | {
id: ILocalUser['id'];
@@ -111,4 +112,11 @@ async function publishUnfollow(followee: Both, follower: Local) {
publishUserEvent(follower.id, 'unfollow', packedFollowee);
publishMainStream(follower.id, 'unfollow', packedFollowee);
+
+ const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow'));
+ for (const webhook of webhooks) {
+ webhookDeliver(webhook, 'unfollow', {
+ user: packedFollowee,
+ });
+ }
}
diff --git a/packages/backend/src/services/messages/create.ts b/packages/backend/src/services/messages/create.ts
index e5cd5a30d..e6b320492 100644
--- a/packages/backend/src/services/messages/create.ts
+++ b/packages/backend/src/services/messages/create.ts
@@ -5,7 +5,7 @@ import { MessagingMessages, UserGroupJoinings, Mutings, Users } from '@/models/i
import { genId } from '@/misc/gen-id.js';
import { MessagingMessage } from '@/models/entities/messaging-message.js';
import { publishMessagingStream, publishMessagingIndexStream, publishMainStream, publishGroupMessagingStream } from '@/services/stream.js';
-import pushNotification from '../push-notification.js';
+import { pushNotification } from '@/services/push-notification.js';
import { Not } from 'typeorm';
import { Note } from '@/models/entities/note.js';
import renderNote from '@/remote/activitypub/renderer/note.js';
diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts
index 2ed194b7e..ceb5e8cc7 100644
--- a/packages/backend/src/services/note/create.ts
+++ b/packages/backend/src/services/note/create.ts
@@ -35,9 +35,11 @@ import { Channel } from '@/models/entities/channel.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { getAntennas } from '@/misc/antenna-cache.js';
import { endedPollNotificationQueue } from '@/queue/queues.js';
+import { webhookDeliver } from '@/queue/index.js';
import { Cache } from '@/misc/cache.js';
import { UserProfile } from '@/models/entities/user-profile.js';
import { db } from '@/db/postgre.js';
+import { getActiveWebhooks } from '@/misc/webhook-cache.js';
const mutedWordsCache = new Cache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5);
@@ -185,6 +187,8 @@ export default async (user: { id: User['id']; username: User['username']; host:
if (data.text) {
data.text = data.text.trim();
+ } else {
+ data.text = null;
}
let tags = data.apHashtags;
@@ -345,6 +349,15 @@ export default async (user: { id: User['id']; username: User['username']; host:
publishNotesStream(noteObj);
+ getActiveWebhooks().then(webhooks => {
+ webhooks = webhooks.filter(x => x.userId === user.id && x.on.includes('note'));
+ for (const webhook of webhooks) {
+ webhookDeliver(webhook, 'note', {
+ note: noteObj,
+ });
+ }
+ });
+
const nm = new NotificationManager(user, note);
const nmRelatedPromises = [];
@@ -365,6 +378,13 @@ export default async (user: { id: User['id']; username: User['username']; host:
if (!threadMuted) {
nm.push(data.reply.userId, 'reply');
publishMainStream(data.reply.userId, 'reply', noteObj);
+
+ const webhooks = (await getActiveWebhooks()).filter(x => x.userId === data.reply!.userId && x.on.includes('reply'));
+ for (const webhook of webhooks) {
+ webhookDeliver(webhook, 'reply', {
+ note: noteObj,
+ });
+ }
}
}
}
@@ -384,6 +404,13 @@ export default async (user: { id: User['id']; username: User['username']; host:
// Publish event
if ((user.id !== data.renote.userId) && data.renote.userHost === null) {
publishMainStream(data.renote.userId, 'renote', noteObj);
+
+ const webhooks = (await getActiveWebhooks()).filter(x => x.userId === data.renote!.userId && x.on.includes('renote'));
+ for (const webhook of webhooks) {
+ webhookDeliver(webhook, 'renote', {
+ note: noteObj,
+ });
+ }
}
}
@@ -620,6 +647,13 @@ async function createMentionedEvents(mentionedUsers: MinimumUser[], note: Note,
publishMainStream(u.id, 'mention', detailPackedNote);
+ const webhooks = (await getActiveWebhooks()).filter(x => x.userId === u.id && x.on.includes('mention'));
+ for (const webhook of webhooks) {
+ webhookDeliver(webhook, 'mention', {
+ note: detailPackedNote,
+ });
+ }
+
// Create notification
nm.push(u.id, 'mention');
}
diff --git a/packages/backend/src/services/note/delete.ts b/packages/backend/src/services/note/delete.ts
index ffd609dd8..496320016 100644
--- a/packages/backend/src/services/note/delete.ts
+++ b/packages/backend/src/services/note/delete.ts
@@ -1,3 +1,4 @@
+import { Brackets, In } from 'typeorm';
import { publishNoteStream } from '@/services/stream.js';
import renderDelete from '@/remote/activitypub/renderer/delete.js';
import renderAnnounce from '@/remote/activitypub/renderer/announce.js';
@@ -5,15 +6,14 @@ import renderUndo from '@/remote/activitypub/renderer/undo.js';
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
import renderTombstone from '@/remote/activitypub/renderer/tombstone.js';
import config from '@/config/index.js';
-import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc.js';
import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js';
import { Note, IMentionedRemoteUsers } from '@/models/entities/note.js';
import { Notes, Users, Instances } from '@/models/index.js';
import { notesChart, perUserNotesChart, instanceChart } from '@/services/chart/index.js';
import { deliverToFollowers, deliverToUser } from '@/remote/activitypub/deliver-manager.js';
import { countSameRenotes } from '@/misc/count-same-renotes.js';
+import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc.js';
import { deliverToRelays } from '../relay.js';
-import { Brackets, In } from 'typeorm';
/**
* 投稿を削除します。
@@ -40,7 +40,7 @@ export default async function(user: { id: User['id']; uri: User['uri']; host: Us
//#region ローカルの投稿なら削除アクティビティを配送
if (Users.isLocalUser(user) && !note.localOnly) {
- let renote: Note | null;
+ let renote: Note | null = null;
// if deletd note is renote
if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) {
@@ -113,7 +113,7 @@ async function getMentionedRemoteUsers(note: Note) {
const uris = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri);
if (uris.length > 0) {
where.push(
- { uri: In(uris) }
+ { uri: In(uris) },
);
}
diff --git a/packages/backend/src/services/push-notification.ts b/packages/backend/src/services/push-notification.ts
index 41122c92e..5c3bafbb3 100644
--- a/packages/backend/src/services/push-notification.ts
+++ b/packages/backend/src/services/push-notification.ts
@@ -5,8 +5,15 @@ import { fetchMeta } from '@/misc/fetch-meta.js';
import { Packed } from '@/misc/schema.js';
import { getNoteSummary } from '@/misc/get-note-summary.js';
-type notificationType = 'notification' | 'unreadMessagingMessage';
-type notificationBody = Packed<'Notification'> | Packed<'MessagingMessage'>;
+// Defined also packages/sw/types.ts#L14-L21
+type pushNotificationsTypes = {
+ 'notification': Packed<'Notification'>;
+ 'unreadMessagingMessage': Packed<'MessagingMessage'>;
+ 'readNotifications': { notificationIds: string[] };
+ 'readAllNotifications': undefined;
+ 'readAllMessagingMessages': undefined;
+ 'readAllMessagingMessagesOfARoom': { userId: string } | { groupId: string };
+};
// プッシュメッセージサーバーには文字数制限があるため、内容を削減します
function truncateNotification(notification: Packed<'Notification'>): any {
@@ -17,12 +24,11 @@ function truncateNotification(notification: Packed<'Notification'>): any {
...notification.note,
// textをgetNoteSummaryしたものに置き換える
text: getNoteSummary(notification.type === 'renote' ? notification.note.renote as Packed<'Note'> : notification.note),
- ...{
- cw: undefined,
- reply: undefined,
- renote: undefined,
- user: undefined as any, // 通知を受け取ったユーザーである場合が多いのでこれも捨てる
- }
+
+ cw: undefined,
+ reply: undefined,
+ renote: undefined,
+ user: undefined as any, // 通知を受け取ったユーザーである場合が多いのでこれも捨てる
}
};
}
@@ -30,7 +36,7 @@ function truncateNotification(notification: Packed<'Notification'>): any {
return notification;
}
-export default async function(userId: string, type: notificationType, body: notificationBody) {
+export async function pushNotification(userId: string, type: T, body: pushNotificationsTypes[T]) {
const meta = await fetchMeta();
if (!meta.enableServiceWorker || meta.swPublicKey == null || meta.swPrivateKey == null) return;
diff --git a/packages/backend/src/services/relay.ts b/packages/backend/src/services/relay.ts
index 1ab45588d..08bf72cc2 100644
--- a/packages/backend/src/services/relay.ts
+++ b/packages/backend/src/services/relay.ts
@@ -88,7 +88,7 @@ export async function deliverToRelays(user: { id: User['id']; host: null; }, act
}));
if (relays.length === 0) return;
- const copy = JSON.parse(JSON.stringify(activity));
+ const copy = structuredClone(activity);
if (!copy.to) copy.to = ['https://www.w3.org/ns/activitystreams#Public'];
const signed = await attachLdSignature(copy, user);
diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts
index 32a030f93..09e812f43 100644
--- a/packages/backend/test/utils.ts
+++ b/packages/backend/test/utils.ts
@@ -4,7 +4,7 @@ import * as misskey from 'misskey-js';
import fetch from 'node-fetch';
import FormData from 'form-data';
import * as childProcess from 'child_process';
-import * as http from 'http';
+import * as http from 'node:http';
import loadConfig from '../src/config/load.js';
import { SIGKILL } from 'constants';
import { entities } from '../src/db/postgre.js';
diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json
index 3120851aa..22338a497 100644
--- a/packages/backend/tsconfig.json
+++ b/packages/backend/tsconfig.json
@@ -25,9 +25,14 @@
"rootDir": "./src",
"baseUrl": "./",
"paths": {
- "@/*": ["./src/*"]
+ "@/*": [
+ "./src/*"
+ ]
},
"outDir": "./built",
+ "types": [
+ "node"
+ ],
"typeRoots": [
"./node_modules/@types",
"./src/@types"
diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock
index 3d5d38d69..fd91be84a 100644
--- a/packages/backend/yarn.lock
+++ b/packages/backend/yarn.lock
@@ -35,20 +35,20 @@
lodash "^4.17.19"
to-fast-properties "^2.0.0"
-"@bull-board/api@3.10.1":
- version "3.10.1"
- resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-3.10.1.tgz#c9608d501c887abcfa8f1907bc3dedee179bdea3"
- integrity sha512-ZYjNBdoBQu+UVbLAHQuEhJL96C+i7vYioc2n7FL/XoVea44XIw2WiKFcFxq0LnActPErja26QyZBQht23ph1lg==
+"@bull-board/api@3.10.4":
+ version "3.10.4"
+ resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-3.10.4.tgz#f29d95a9624224ceec0f3ff26ef2c2bba8106921"
+ integrity sha512-JJjMg8O/ELeaqkuL1Wsdn6rdQfH+/2+BfnFD0B7j4ZCtLVAPfsOUZYpLqSKUgaNizwp1nTw0e3L/EI0yvX5aiw==
dependencies:
redis-info "^3.0.8"
-"@bull-board/koa@3.10.1":
- version "3.10.1"
- resolved "https://registry.yarnpkg.com/@bull-board/koa/-/koa-3.10.1.tgz#205641ae9721ec71303c4f16dc27eca1f71ca131"
- integrity sha512-+mxdnu7idjd75WqUklJbPzrQU6NJzgQCT+BLKCyqOBsWzpfEwaac6QaIXOiuPwgwG2VjH90HWIcWr+2BQB9c1w==
+"@bull-board/koa@3.10.4":
+ version "3.10.4"
+ resolved "https://registry.yarnpkg.com/@bull-board/koa/-/koa-3.10.4.tgz#8e54600bfd8e003a8d5838ae6e65f9ec8c9979f7"
+ integrity sha512-NO0kzgVrl5lGNnX6maBAuP6aecGvROGka3RJSALubDfsrQ3aWNuY2BjUMUvm4ZDVfAeYT3wPaak8rdRCwxYE2g==
dependencies:
- "@bull-board/api" "3.10.1"
- "@bull-board/ui" "3.10.1"
+ "@bull-board/api" "3.10.4"
+ "@bull-board/ui" "3.10.4"
ejs "^3.1.6"
koa "^2.13.1"
koa-mount "^4.0.0"
@@ -56,12 +56,12 @@
koa-static "^5.0.0"
koa-views "^7.0.1"
-"@bull-board/ui@3.10.1":
- version "3.10.1"
- resolved "https://registry.yarnpkg.com/@bull-board/ui/-/ui-3.10.1.tgz#edf7c7752a78d9829f7a944bb87a0e70812b749f"
- integrity sha512-K2qEAvTuyHZxUdK31HaBb9sdTFSOSKAZkxsl/LeiT4FGNF/h54iYGmWF9+HSFytggcnGdM0XnK3wLihCaIQAOQ==
+"@bull-board/ui@3.10.4":
+ version "3.10.4"
+ resolved "https://registry.yarnpkg.com/@bull-board/ui/-/ui-3.10.4.tgz#6455b4e75fdbec1bc2ee84fde2a6a283b3c77bc9"
+ integrity sha512-nqnE3wqqpso7ORPcmcGVesYeFkHwv3AsBdRV2W0VLtfBPGzMdqZ1sJeSTAmlanFZnvTprU4Eg/G0DcEeMUTGhA==
dependencies:
- "@bull-board/api" "3.10.1"
+ "@bull-board/api" "3.10.4"
"@cspotcode/source-map-consumer@0.8.0":
version "0.8.0"
@@ -110,10 +110,10 @@
pump "^3.0.0"
secure-json-parse "^2.1.0"
-"@eslint/eslintrc@^1.2.1":
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.1.tgz#8b5e1c49f4077235516bc9ec7d41378c0f69b8c6"
- integrity sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==
+"@eslint/eslintrc@^1.2.2":
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.2.tgz#4989b9e8c0216747ee7cca314ae73791bb281aae"
+ integrity sha512-lTVWHs7O2hjBFZunXTZYnYqtB9GakA1lnxIf+gKq2nY5gxkkNi/lQvveW6t8gFdOHTg6nG50Xs95PrLqVpcaLg==
dependencies:
ajv "^6.12.4"
debug "^4.3.2"
@@ -244,10 +244,10 @@
require-from-string "^2.0.2"
uri-js "^4.2.2"
-"@redocly/openapi-core@1.0.0-beta.90":
- version "1.0.0-beta.90"
- resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.90.tgz#edf53b23314368e190b005e1958c1f4a7dfaa2c3"
- integrity sha512-MvkME+AWCBexyJyNp/sVFRUBjxCSk5CQ+CAozkwm0t/HusXp9G+kH26+e9giD6Fms129smr1qp3pCAUbwJZzZA==
+"@redocly/openapi-core@1.0.0-beta.93":
+ version "1.0.0-beta.93"
+ resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.93.tgz#882db8684598217f621adc7349288229253a0038"
+ integrity sha512-xQj7UnjPj3mKtkyRrm+bjzEoyo0CVNjGP4pV6BzQ0vgKf0Jqq7apFC703psyBH+JscYr7NKK1hPQU76ylhFDdg==
dependencies:
"@redocly/ajv" "^8.6.4"
"@types/node" "^14.11.8"
@@ -527,6 +527,11 @@
resolved "https://registry.yarnpkg.com/@types/jsonld/-/jsonld-1.5.6.tgz#4396c0b17128abf5773bb68b5453b88fc565b0d4"
integrity sha512-OUcfMjRie5IOrJulUQwVNvV57SOdKcTfBj3pjXNxzXqeOIrY2aGDNGW/Tlp83EQPkz4tCE6YWVrGuc/ZeaAQGg==
+"@types/jsrsasign@10.2.1":
+ version "10.2.1"
+ resolved "https://registry.yarnpkg.com/@types/jsrsasign/-/jsrsasign-10.2.1.tgz#b82882523dfb5c476673dbef344ad838f96fb43d"
+ integrity sha512-piCIOMY0+d2wwRNcRw56VBqFYCYYeZ1c/NlUKVHTV3Y9j1RE2qpgCQvClI6yhH2sk8OoXiah43i9FmnC5tL2RQ==
+
"@types/keygrip@*":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.2.tgz#513abfd256d7ad0bf1ee1873606317b33b1b2a72"
@@ -649,10 +654,10 @@
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d"
integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==
-"@types/mocha@9.1.0":
- version "9.1.0"
- resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.0.tgz#baf17ab2cca3fcce2d322ebc30454bff487efad5"
- integrity sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==
+"@types/mocha@9.1.1":
+ version "9.1.1"
+ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.1.tgz#e7c4f1001eefa4b8afbd1eee27a237fee3bf29c4"
+ integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==
"@types/node-fetch@3.0.3":
version "3.0.3"
@@ -666,10 +671,10 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.6.2.tgz#331b7b9f8621c638284787c5559423822fdffc50"
integrity sha512-LSw8TZt12ZudbpHc6EkIyDM3nHVWKYrAvGy6EAJfNfjusbwnThqjqxUKKRwuV3iWYeW/LYMzNgaq3MaLffQ2xA==
-"@types/node@17.0.23":
- version "17.0.23"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.23.tgz#3b41a6e643589ac6442bdbd7a4a3ded62f33f7da"
- integrity sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==
+"@types/node@17.0.25":
+ version "17.0.25"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.25.tgz#527051f3c2f77aa52e5dc74e45a3da5fb2301448"
+ integrity sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==
"@types/node@^14.11.8":
version "14.17.9"
@@ -777,6 +782,11 @@
dependencies:
htmlparser2 "^6.0.0"
+"@types/semver@7.3.9":
+ version "7.3.9"
+ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.9.tgz#152c6c20a7688c30b967ec1841d31ace569863fc"
+ integrity sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==
+
"@types/serve-static@*":
version "1.13.3"
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1"
@@ -785,10 +795,10 @@
"@types/express-serve-static-core" "*"
"@types/mime" "*"
-"@types/sharp@0.30.0":
- version "0.30.0"
- resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.30.0.tgz#58cb016c8fdc558b4c5771ad1f3668336685c843"
- integrity sha512-bZ0Y/JVlrOyqwlBMJ2taEgnwFavjLnyZmLOLecmOesuG5kR2Lx9b2fM4osgfVjLJi8UlE+t3R1JzRVMxF6MbfA==
+"@types/sharp@0.30.2":
+ version "0.30.2"
+ resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.30.2.tgz#df5ff34140b3bad165482e6f3d26b08e42a0503a"
+ integrity sha512-uLCBwjDg/BTcQit0dpNGvkIjvH3wsb8zpaJePCjvONBBSfaKHoxXBIuq1MT8DMQEfk2fKYnpC9QExCgFhkGkMQ==
dependencies:
"@types/node" "*"
@@ -804,11 +814,6 @@
dependencies:
"@types/node" "*"
-"@types/throttle-debounce@2.1.0":
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/@types/throttle-debounce/-/throttle-debounce-2.1.0.tgz#1c3df624bfc4b62f992d3012b84c56d41eab3776"
- integrity sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ==
-
"@types/tinycolor2@1.4.3":
version "1.4.3"
resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.3.tgz#ed4a0901f954b126e6a914b4839c77462d56e706"
@@ -850,14 +855,14 @@
dependencies:
"@types/node" "*"
-"@typescript-eslint/eslint-plugin@5.16.0":
- version "5.16.0"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.16.0.tgz#78f246dd8d1b528fc5bfca99a8a64d4023a3d86d"
- integrity sha512-SJoba1edXvQRMmNI505Uo4XmGbxCK9ARQpkvOd00anxzri9RNQk0DDCxD+LIl+jYhkzOJiOMMKYEHnHEODjdCw==
+"@typescript-eslint/eslint-plugin@5.20.0":
+ version "5.20.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.20.0.tgz#022531a639640ff3faafaf251d1ce00a2ef000a1"
+ integrity sha512-fapGzoxilCn3sBtC6NtXZX6+P/Hef7VDbyfGqTTpzYydwhlkevB+0vE0EnmHPVTVSy68GUncyJ/2PcrFBeCo5Q==
dependencies:
- "@typescript-eslint/scope-manager" "5.16.0"
- "@typescript-eslint/type-utils" "5.16.0"
- "@typescript-eslint/utils" "5.16.0"
+ "@typescript-eslint/scope-manager" "5.20.0"
+ "@typescript-eslint/type-utils" "5.20.0"
+ "@typescript-eslint/utils" "5.20.0"
debug "^4.3.2"
functional-red-black-tree "^1.0.1"
ignore "^5.1.8"
@@ -865,69 +870,69 @@
semver "^7.3.5"
tsutils "^3.21.0"
-"@typescript-eslint/parser@5.16.0":
- version "5.16.0"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.16.0.tgz#e4de1bde4b4dad5b6124d3da227347616ed55508"
- integrity sha512-fkDq86F0zl8FicnJtdXakFs4lnuebH6ZADDw6CYQv0UZeIjHvmEw87m9/29nk2Dv5Lmdp0zQ3zDQhiMWQf/GbA==
+"@typescript-eslint/parser@5.20.0":
+ version "5.20.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.20.0.tgz#4991c4ee0344315c2afc2a62f156565f689c8d0b"
+ integrity sha512-UWKibrCZQCYvobmu3/N8TWbEeo/EPQbS41Ux1F9XqPzGuV7pfg6n50ZrFo6hryynD8qOTTfLHtHjjdQtxJ0h/w==
dependencies:
- "@typescript-eslint/scope-manager" "5.16.0"
- "@typescript-eslint/types" "5.16.0"
- "@typescript-eslint/typescript-estree" "5.16.0"
+ "@typescript-eslint/scope-manager" "5.20.0"
+ "@typescript-eslint/types" "5.20.0"
+ "@typescript-eslint/typescript-estree" "5.20.0"
debug "^4.3.2"
-"@typescript-eslint/scope-manager@5.16.0":
- version "5.16.0"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.16.0.tgz#7e7909d64bd0c4d8aef629cdc764b9d3e1d3a69a"
- integrity sha512-P+Yab2Hovg8NekLIR/mOElCDPyGgFZKhGoZA901Yax6WR6HVeGLbsqJkZ+Cvk5nts/dAlFKm8PfL43UZnWdpIQ==
+"@typescript-eslint/scope-manager@5.20.0":
+ version "5.20.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.20.0.tgz#79c7fb8598d2942e45b3c881ced95319818c7980"
+ integrity sha512-h9KtuPZ4D/JuX7rpp1iKg3zOH0WNEa+ZIXwpW/KWmEFDxlA/HSfCMhiyF1HS/drTICjIbpA6OqkAhrP/zkCStg==
dependencies:
- "@typescript-eslint/types" "5.16.0"
- "@typescript-eslint/visitor-keys" "5.16.0"
+ "@typescript-eslint/types" "5.20.0"
+ "@typescript-eslint/visitor-keys" "5.20.0"
-"@typescript-eslint/type-utils@5.16.0":
- version "5.16.0"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.16.0.tgz#b482bdde1d7d7c0c7080f7f2f67ea9580b9e0692"
- integrity sha512-SKygICv54CCRl1Vq5ewwQUJV/8padIWvPgCxlWPGO/OgQLCijY9G7lDu6H+mqfQtbzDNlVjzVWQmeqbLMBLEwQ==
+"@typescript-eslint/type-utils@5.20.0":
+ version "5.20.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.20.0.tgz#151c21cbe9a378a34685735036e5ddfc00223be3"
+ integrity sha512-WxNrCwYB3N/m8ceyoGCgbLmuZwupvzN0rE8NBuwnl7APgjv24ZJIjkNzoFBXPRCGzLNkoU/WfanW0exvp/+3Iw==
dependencies:
- "@typescript-eslint/utils" "5.16.0"
+ "@typescript-eslint/utils" "5.20.0"
debug "^4.3.2"
tsutils "^3.21.0"
-"@typescript-eslint/types@5.16.0":
- version "5.16.0"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.16.0.tgz#5827b011982950ed350f075eaecb7f47d3c643ee"
- integrity sha512-oUorOwLj/3/3p/HFwrp6m/J2VfbLC8gjW5X3awpQJ/bSG+YRGFS4dpsvtQ8T2VNveV+LflQHjlLvB6v0R87z4g==
+"@typescript-eslint/types@5.20.0":
+ version "5.20.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.20.0.tgz#fa39c3c2aa786568302318f1cb51fcf64258c20c"
+ integrity sha512-+d8wprF9GyvPwtoB4CxBAR/s0rpP25XKgnOvMf/gMXYDvlUC3rPFHupdTQ/ow9vn7UDe5rX02ovGYQbv/IUCbg==
-"@typescript-eslint/typescript-estree@5.16.0":
- version "5.16.0"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.16.0.tgz#32259459ec62f5feddca66adc695342f30101f61"
- integrity sha512-SE4VfbLWUZl9MR+ngLSARptUv2E8brY0luCdgmUevU6arZRY/KxYoLI/3V/yxaURR8tLRN7bmZtJdgmzLHI6pQ==
+"@typescript-eslint/typescript-estree@5.20.0":
+ version "5.20.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.20.0.tgz#ab73686ab18c8781bbf249c9459a55dc9417d6b0"
+ integrity sha512-36xLjP/+bXusLMrT9fMMYy1KJAGgHhlER2TqpUVDYUQg4w0q/NW/sg4UGAgVwAqb8V4zYg43KMUpM8vV2lve6w==
dependencies:
- "@typescript-eslint/types" "5.16.0"
- "@typescript-eslint/visitor-keys" "5.16.0"
+ "@typescript-eslint/types" "5.20.0"
+ "@typescript-eslint/visitor-keys" "5.20.0"
debug "^4.3.2"
globby "^11.0.4"
is-glob "^4.0.3"
semver "^7.3.5"
tsutils "^3.21.0"
-"@typescript-eslint/utils@5.16.0":
- version "5.16.0"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.16.0.tgz#42218b459d6d66418a4eb199a382bdc261650679"
- integrity sha512-iYej2ER6AwmejLWMWzJIHy3nPJeGDuCqf8Jnb+jAQVoPpmWzwQOfa9hWVB8GIQE5gsCv/rfN4T+AYb/V06WseQ==
+"@typescript-eslint/utils@5.20.0":
+ version "5.20.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.20.0.tgz#b8e959ed11eca1b2d5414e12417fd94cae3517a5"
+ integrity sha512-lHONGJL1LIO12Ujyx8L8xKbwWSkoUKFSO+0wDAqGXiudWB2EO7WEUT+YZLtVbmOmSllAjLb9tpoIPwpRe5Tn6w==
dependencies:
"@types/json-schema" "^7.0.9"
- "@typescript-eslint/scope-manager" "5.16.0"
- "@typescript-eslint/types" "5.16.0"
- "@typescript-eslint/typescript-estree" "5.16.0"
+ "@typescript-eslint/scope-manager" "5.20.0"
+ "@typescript-eslint/types" "5.20.0"
+ "@typescript-eslint/typescript-estree" "5.20.0"
eslint-scope "^5.1.1"
eslint-utils "^3.0.0"
-"@typescript-eslint/visitor-keys@5.16.0":
- version "5.16.0"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.16.0.tgz#f27dc3b943e6317264c7492e390c6844cd4efbbb"
- integrity sha512-jqxO8msp5vZDhikTwq9ubyMHqZ67UIvawohr4qF3KhlpL7gzSjOd+8471H3nh5LyABkaI85laEKKU8SnGUK5/g==
+"@typescript-eslint/visitor-keys@5.20.0":
+ version "5.20.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.20.0.tgz#70236b5c6b67fbaf8b2f58bf3414b76c1e826c2a"
+ integrity sha512-1flRpNF+0CAQkMNlTJ6L/Z5jiODG/e5+7mk6XwtPOUS3UrTz3UOiAg9jG2VtKsWI6rZQfy4C6a232QNRZTRGlg==
dependencies:
- "@typescript-eslint/types" "5.16.0"
+ "@typescript-eslint/types" "5.20.0"
eslint-visitor-keys "^3.0.0"
"@ungap/promise-all-settled@1.1.2":
@@ -1079,7 +1084,7 @@ ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
-ansi-styles@^4.0.0, ansi-styles@^4.1.0:
+ansi-styles@^4.0.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==
@@ -1087,6 +1092,13 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
"@types/color-name" "^1.1.1"
color-convert "^2.0.1"
+ansi-styles@^4.1.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
+ integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+ dependencies:
+ color-convert "^2.0.1"
+
any-promise@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
@@ -1136,13 +1148,13 @@ archiver-utils@^2.1.0:
normalize-path "^3.0.0"
readable-stream "^2.0.0"
-archiver@5.3.0:
- version "5.3.0"
- resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.0.tgz#dd3e097624481741df626267564f7dd8640a45ba"
- integrity sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==
+archiver@5.3.1:
+ version "5.3.1"
+ resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.1.tgz#21e92811d6f09ecfce649fbefefe8c79e57cbbb6"
+ integrity sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==
dependencies:
archiver-utils "^2.1.0"
- async "^3.2.0"
+ async "^3.2.3"
buffer-crc32 "^0.2.1"
readable-stream "^3.6.0"
readdir-glob "^1.0.0"
@@ -1232,11 +1244,6 @@ assert-plus@1.0.0, assert-plus@^1.0.0:
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
-async@0.9.x:
- version "0.9.2"
- resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
- integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=
-
async@>=0.2.9:
version "3.2.0"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
@@ -1249,10 +1256,10 @@ async@^2.6.0:
dependencies:
lodash "^4.17.14"
-async@^3.2.0:
- version "3.2.1"
- resolved "https://registry.yarnpkg.com/async/-/async-3.2.1.tgz#d3274ec66d107a47476a4c49136aacdb00665fc8"
- integrity sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==
+async@^3.2.3:
+ version "3.2.3"
+ resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9"
+ integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==
asynckit@^0.4.0:
version "0.4.0"
@@ -1271,10 +1278,10 @@ autwh@0.1.0:
dependencies:
oauth "0.9.15"
-aws-sdk@2.1100.0:
- version "2.1100.0"
- resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1100.0.tgz#20bbabc12fbc316067ba02af66bf371a455af9e3"
- integrity sha512-StLSQCYFmFPxjoMntIb+8jUZ0vzmq3xkrwG5e/4qU1bSGWCmhhjvz6c+4j38AnIy8MFV1+tV8RArbhLUEV2dGw==
+aws-sdk@2.1120.0:
+ version "2.1120.0"
+ resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1120.0.tgz#a299f595448019c4b4b69fa9aa57fd58658497a6"
+ integrity sha512-3cKXUFxC3CDBbJ/JlXEKmJZKFZhqGii7idGaLxvV5/OzqEDUstYkHGX3TCJdQRHrRwpFvRVOekXSwLxBltqXuQ==
dependencies:
buffer "4.9.2"
events "1.1.1"
@@ -1301,9 +1308,9 @@ babel-walk@3.0.0-canary-5:
"@babel/types" "^7.9.6"
balanced-match@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
- integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
+ integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
base32.js@0.0.1:
version "0.0.1"
@@ -1332,11 +1339,6 @@ bcryptjs@2.4.3:
resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb"
integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=
-big-integer@^1.6.16:
- version "1.6.48"
- resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e"
- integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==
-
big-integer@^1.6.17:
version "1.6.51"
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686"
@@ -1402,6 +1404,13 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0"
concat-map "0.0.1"
+brace-expansion@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
+ integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
+ dependencies:
+ balanced-match "^1.0.0"
+
braces@^3.0.1, braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
@@ -1409,15 +1418,14 @@ braces@^3.0.1, braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
-broadcast-channel@4.10.0:
- version "4.10.0"
- resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.10.0.tgz#d19fb902df227df40b1b580351713d30c302d198"
- integrity sha512-hOUh312XyHk6JTVyX9cyXaH1UYs+2gHVtnW16oQAu9FL7ALcXGXc/YoJWqlkV8vUn14URQPMmRi4A9q4UrwVEQ==
+broadcast-channel@4.11.0:
+ version "4.11.0"
+ resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.11.0.tgz#b9ebc7ce1326120088e61d2197477496908a1a9e"
+ integrity sha512-4FS1Zk+ttekfXHq5I2R7KhN9AsnZUFVV5SczrTtnZPuf5w+jw+fqM1PJHuHzwEXJezJeCbJxoZMDcFqsIN2c1Q==
dependencies:
"@babel/runtime" "^7.16.0"
detect-node "^2.1.0"
- microseconds "0.2.0"
- nano-time "1.0.0"
+ microtime "3.0.0"
oblivious-set "1.0.0"
p-queue "6.6.2"
rimraf "3.0.2"
@@ -1495,10 +1503,10 @@ bufferutil@^4.0.1:
dependencies:
node-gyp-build "~3.7.0"
-bull@4.8.1:
- version "4.8.1"
- resolved "https://registry.yarnpkg.com/bull/-/bull-4.8.1.tgz#83daaefc3118876450b21d7a02bc11ea28a2440e"
- integrity sha512-ojH5AfOchKQsQwwE+thViS1pMpvREGC+Ov1+3HXsQqn5Q27ZSGkgMriMqc6c9J9rvQ/+D732pZE+TN1+2LRWVg==
+bull@4.8.2:
+ version "4.8.2"
+ resolved "https://registry.yarnpkg.com/bull/-/bull-4.8.2.tgz#0d02fe389777abe29d50fd46d123bc62e074cfcd"
+ integrity sha512-S7CNIL9+vsbLKwOGkUI6mawY5iABKQJLZn5a7KPnxAZrDhFXkrxsHHXLCKUR/+Oqys3Vk5ElWdj0SLtK84b1Nw==
dependencies:
cron-parser "^4.2.1"
debuglog "^1.0.0"
@@ -1591,11 +1599,6 @@ cacheable-request@^7.0.2:
normalize-url "^6.0.1"
responselike "^2.0.0"
-cafy@15.2.1:
- version "15.2.1"
- resolved "https://registry.yarnpkg.com/cafy/-/cafy-15.2.1.tgz#5a55eaeb721c604c7dca652f3d555c392e5f995a"
- integrity sha512-g2zOmFb63p6XcZ/zeMWKYP8YKQYNWnhJmi6K71Ql4EAFTAay31xF0PBPtdBCCfQ0fiETgWTMxKtySAVI/Od6aQ==
-
call-bind@^1.0.0, call-bind@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
@@ -1683,7 +1686,7 @@ chalk@^4.0.0, chalk@^4.1.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
-chalk@^4.1.2:
+chalk@^4.0.2, chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@@ -1864,10 +1867,10 @@ color-support@^1.1.2:
resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
-color@^4.2.1:
- version "4.2.1"
- resolved "https://registry.yarnpkg.com/color/-/color-4.2.1.tgz#498aee5fce7fc982606c8875cab080ac0547c884"
- integrity sha512-MFJr0uY4RvTQUKvPq7dh9grVOTYSFeXja2mBXioCGjnjJoXrAp9jJ1NQTDR73c9nwBSAQiNKloKl5zq9WB9UPw==
+color@^4.2.3:
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a"
+ integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==
dependencies:
color-convert "^2.0.1"
color-string "^1.9.0"
@@ -2476,11 +2479,11 @@ ee-first@1.1.1:
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
ejs@^3.1.6:
- version "3.1.6"
- resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a"
- integrity sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==
+ version "3.1.7"
+ resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.7.tgz#c544d9c7f715783dd92f0bddcf73a59e6962d006"
+ integrity sha512-BIar7R6abbUxDA3bfXrO4DSgwo8I+fB5/1zgujl3HLLjwd6+9iOnrT+t3grn2qbk9vOgBubXOFwX2m9axoFaGw==
dependencies:
- jake "^10.6.1"
+ jake "^10.8.5"
emoji-regex@^8.0.0:
version "8.0.0"
@@ -2650,32 +2653,32 @@ eslint-import-resolver-node@^0.3.6:
debug "^3.2.7"
resolve "^1.20.0"
-eslint-module-utils@^2.7.2:
- version "2.7.2"
- resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.2.tgz#1d0aa455dcf41052339b63cada8ab5fd57577129"
- integrity sha512-zquepFnWCY2ISMFwD/DqzaM++H+7PDzOpUvotJWm/y1BAFt5R4oeULgdrTejKqLkz7MA/tgstsUMNYc7wNdTrg==
+eslint-module-utils@^2.7.3:
+ version "2.7.3"
+ resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz#ad7e3a10552fdd0642e1e55292781bd6e34876ee"
+ integrity sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==
dependencies:
debug "^3.2.7"
find-up "^2.1.0"
-eslint-plugin-import@2.25.4:
- version "2.25.4"
- resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz#322f3f916a4e9e991ac7af32032c25ce313209f1"
- integrity sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==
+eslint-plugin-import@2.26.0:
+ version "2.26.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz#f812dc47be4f2b72b478a021605a59fc6fe8b88b"
+ integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==
dependencies:
array-includes "^3.1.4"
array.prototype.flat "^1.2.5"
debug "^2.6.9"
doctrine "^2.1.0"
eslint-import-resolver-node "^0.3.6"
- eslint-module-utils "^2.7.2"
+ eslint-module-utils "^2.7.3"
has "^1.0.3"
- is-core-module "^2.8.0"
+ is-core-module "^2.8.1"
is-glob "^4.0.3"
- minimatch "^3.0.4"
+ minimatch "^3.1.2"
object.values "^1.1.5"
- resolve "^1.20.0"
- tsconfig-paths "^3.12.0"
+ resolve "^1.22.0"
+ tsconfig-paths "^3.14.1"
eslint-scope@^5.1.1:
version "5.1.1"
@@ -2715,12 +2718,12 @@ eslint-visitor-keys@^3.3.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
-eslint@8.12.0:
- version "8.12.0"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.12.0.tgz#c7a5bd1cfa09079aae64c9076c07eada66a46e8e"
- integrity sha512-it1oBL9alZg1S8UycLm5YDMAkIhtH6FtAzuZs6YvoGVldWjbS08BkAdb/ymP9LlAyq8koANu32U7Ib/w+UNh8Q==
+eslint@8.14.0:
+ version "8.14.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.14.0.tgz#62741f159d9eb4a79695b28ec4989fcdec623239"
+ integrity sha512-3/CE4aJX7LNEiE3i6FeodHmI/38GZtWCsAtsymScmzYapx8q1nVVb+eLcLSzATmCPXw5pT4TqVs1E0OmxAd9tw==
dependencies:
- "@eslint/eslintrc" "^1.2.1"
+ "@eslint/eslintrc" "^1.2.2"
"@humanwhocodes/config-array" "^0.9.2"
ajv "^6.10.0"
chalk "^4.0.0"
@@ -2961,11 +2964,11 @@ file-type@17.1.1:
token-types "^5.0.0-alpha.2"
filelist@^1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.2.tgz#80202f21462d4d1c2e214119b1807c1bc0380e5b"
- integrity sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.3.tgz#448607750376484932f67ef1b9ff07386b036c83"
+ integrity sha512-LwjCsruLWQULGYKy7TX0OPtrL9kLpojOFKc5VCTxdFTV7w5zbsgqVKfnkKG7Qgjtq50gKfO56hJv88OfcGb70Q==
dependencies:
- minimatch "^3.0.4"
+ minimatch "^5.0.1"
fill-range@^7.0.1:
version "7.0.1"
@@ -3705,10 +3708,10 @@ ip-address@^7.1.0:
jsbn "1.1.0"
sprintf-js "1.1.2"
-ip-cidr@3.0.4:
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/ip-cidr/-/ip-cidr-3.0.4.tgz#a915c47e00f47ea8d5f8ed662ea6161471c44375"
- integrity sha512-pKNiqmBlTvEkhaLAa3+FOmYSY0/jjADVxxjA3NbujZZTT8mjLI90Q+6mwg6kd0fNm0RuAOkWJ1u1a/ETmlrPNQ==
+ip-cidr@3.0.7:
+ version "3.0.7"
+ resolved "https://registry.yarnpkg.com/ip-cidr/-/ip-cidr-3.0.7.tgz#22708dd4f2d3f6397c0fb7d647b44e3c565937e9"
+ integrity sha512-0cBBICDnmmpAdULMbMVdi4f0mSG+VWY/QBPL/OIIjuom14x7Y63VhpS/uSAOycasXOeGXah5y0eu//PDU51aNw==
dependencies:
ip-address "^7.1.0"
jsbn "^1.1.0"
@@ -3777,10 +3780,10 @@ is-core-module@^2.2.0:
dependencies:
has "^1.0.3"
-is-core-module@^2.8.0:
- version "2.8.0"
- resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548"
- integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==
+is-core-module@^2.8.1:
+ version "2.8.1"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211"
+ integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==
dependencies:
has "^1.0.3"
@@ -3987,13 +3990,13 @@ isexe@^2.0.0:
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
-jake@^10.6.1:
- version "10.8.2"
- resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b"
- integrity sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==
+jake@^10.8.5:
+ version "10.8.5"
+ resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46"
+ integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==
dependencies:
- async "0.9.x"
- chalk "^2.4.2"
+ async "^3.2.3"
+ chalk "^4.0.2"
filelist "^1.0.1"
minimatch "^3.0.4"
@@ -4177,10 +4180,10 @@ jsprim@^2.0.2:
json-schema "0.4.0"
verror "1.10.0"
-jsrsasign@8.0.20:
- version "8.0.20"
- resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-8.0.20.tgz#37d8029c9d8f794d8ac8d8998bce319921491f11"
- integrity sha512-JTXt9+nqdynIB8wFsS6e8ffHhIjilhywXwdaEVHSj9OVmwldG2H0EoCqkQ+KXkm2tVqREfH/HEmklY4k1/6Rcg==
+jsrsasign@10.5.19:
+ version "10.5.19"
+ resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-10.5.19.tgz#61cd378190c3e65bd1a26a088696736e4437a806"
+ integrity sha512-GgOdly2Ee9nS+qxOjLkQKaoSTKqlk6lFKcKLPlNJOApoOUcqL2z+l4dAcBzYnZkA3tg+LwFOyQnqbuFn5IPdvw==
jstransformer@1.0.0:
version "1.0.0"
@@ -4655,10 +4658,13 @@ micromatch@^4.0.0, micromatch@^4.0.2:
braces "^3.0.1"
picomatch "^2.0.5"
-microseconds@0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39"
- integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==
+microtime@3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/microtime/-/microtime-3.0.0.tgz#d140914bde88aa89b4f9fd2a18620b435af0f39b"
+ integrity sha512-SirJr7ZL4ow2iWcb54bekS4aWyBQNVcEDBiwAz9D/sTgY59A+uE8UJU15cp5wyZmPBwg/3zf8lyCJ5NUe1nVlQ==
+ dependencies:
+ node-addon-api "^1.2.0"
+ node-gyp-build "^3.8.0"
mime-db@1.44.0:
version "1.44.0"
@@ -4711,13 +4717,20 @@ minimatch@4.2.1:
dependencies:
brace-expansion "^1.1.7"
-minimatch@^3.0.4:
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
- integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
+minimatch@^3.0.4, minimatch@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
+ integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
dependencies:
brace-expansion "^1.1.7"
+minimatch@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b"
+ integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==
+ dependencies:
+ brace-expansion "^2.0.1"
+
minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6:
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
@@ -4839,9 +4852,9 @@ mocha@9.2.2:
yargs-unparser "2.0.0"
moment@^2.22.2:
- version "2.24.0"
- resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
- integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
+ version "2.29.3"
+ resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.3.tgz#edd47411c322413999f7a5940d526de183c031f3"
+ integrity sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==
ms@2.0.0:
version "2.0.0"
@@ -4911,13 +4924,6 @@ nan@^2.14.2, nan@^2.15.0:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee"
integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==
-nano-time@1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef"
- integrity sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8=
- dependencies:
- big-integer "^1.6.16"
-
nanoid@3.3.1, nanoid@^3.1.30:
version "3.3.1"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35"
@@ -4969,6 +4975,11 @@ node-abi@^3.3.0:
dependencies:
semver "^7.3.5"
+node-addon-api@^1.2.0:
+ version "1.7.2"
+ resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d"
+ integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==
+
node-addon-api@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f"
@@ -5012,6 +5023,11 @@ node-fetch@^2.6.1:
dependencies:
whatwg-url "^5.0.0"
+node-gyp-build@^3.8.0:
+ version "3.9.0"
+ resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.9.0.tgz#53a350187dd4d5276750da21605d1cb681d09e25"
+ integrity sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A==
+
node-gyp-build@^4.2.3:
version "4.3.0"
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3"
@@ -5419,7 +5435,7 @@ path-key@^4.0.0:
resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18"
integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==
-path-parse@^1.0.6:
+path-parse@^1.0.6, path-parse@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
@@ -6075,6 +6091,15 @@ resolve@^1.15.1, resolve@^1.20.0:
is-core-module "^2.2.0"
path-parse "^1.0.6"
+resolve@^1.22.0:
+ version "1.22.0"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
+ integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
+ dependencies:
+ is-core-module "^2.8.1"
+ path-parse "^1.0.7"
+ supports-preserve-symlinks-flag "^1.0.0"
+
responselike@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723"
@@ -6197,10 +6222,10 @@ seedrandom@3.0.5:
resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7"
integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==
-semver@7.3.5, semver@^7.3.5:
- version "7.3.5"
- resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
- integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
+semver@7.3.7, semver@^7.3.7:
+ version "7.3.7"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
+ integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
dependencies:
lru-cache "^6.0.0"
@@ -6216,6 +6241,13 @@ semver@^7.3.2, semver@^7.3.4:
dependencies:
lru-cache "^6.0.0"
+semver@^7.3.5:
+ version "7.3.5"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
+ integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
+ dependencies:
+ lru-cache "^6.0.0"
+
serialize-javascript@6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8"
@@ -6251,16 +6283,16 @@ sha.js@^2.4.11:
inherits "^2.0.1"
safe-buffer "^5.0.1"
-sharp@0.30.3:
- version "0.30.3"
- resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.30.3.tgz#315a1817423a4d1cde5119a21c99c234a7a6fb37"
- integrity sha512-rjpfJFK58ZOFSG8sxYSo3/JQb4ej095HjXp9X7gVu7gEn1aqSG8TCW29h/Rr31+PXrFADo1H/vKfw0uhMQWFtg==
+sharp@0.30.4:
+ version "0.30.4"
+ resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.30.4.tgz#73d9daa63bbc20da189c9328d75d5d395fc8fb73"
+ integrity sha512-3Onig53Y6lji4NIZo69s14mERXXY/GV++6CzOYx/Rd8bnTwbhFbL09WZd7Ag/CCnA0WxFID8tkY0QReyfL6v0Q==
dependencies:
- color "^4.2.1"
+ color "^4.2.3"
detect-libc "^2.0.1"
node-addon-api "^4.3.0"
prebuild-install "^7.0.1"
- semver "^7.3.5"
+ semver "^7.3.7"
simple-get "^4.0.1"
tar-fs "^2.1.1"
tunnel-agent "^0.6.0"
@@ -6602,6 +6634,11 @@ supports-color@^7.1.0:
dependencies:
has-flag "^4.0.0"
+supports-preserve-symlinks-flag@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
+ integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
+
symbol-tree@^3.2.4:
version "3.2.4"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
@@ -6614,10 +6651,10 @@ syslog-pro@1.0.0:
dependencies:
moment "^2.22.2"
-systeminformation@5.11.9:
- version "5.11.9"
- resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.11.9.tgz#95f2334e739dd224178948a2afaced7d9abfdf9d"
- integrity sha512-eeMtL9UJFR/LYG+2rpeAgZ0Va4ojlNQTkYiQH/xbbPwDjDMsaetj3Pkc+C1aH5G8mav6HvDY8kI4Vl4noksSkA==
+systeminformation@5.11.14:
+ version "5.11.14"
+ resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.11.14.tgz#21fcb6f05d33e17d69c236b9c1b3d9c53d1d2b3a"
+ integrity sha512-m8CJx3fIhKohanB0ExTk5q53uI1J0g5B09p77kU+KxnxRVpADVqTAwCg1PFelqKsj4LHd+qmVnumb511Hg4xow==
tapable@^2.2.0:
version "2.2.0"
@@ -6697,11 +6734,6 @@ thenify-all@^1.0.0:
dependencies:
any-promise "^1.0.0"
-throttle-debounce@3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-3.0.1.tgz#32f94d84dfa894f786c9a1f290e7a645b6a19abb"
- integrity sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==
-
through@2:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
@@ -6821,7 +6853,7 @@ tsc-alias@1.4.1:
mylas "^2.1.4"
normalize-path "^3.0.0"
-tsconfig-paths@3.14.1:
+tsconfig-paths@3.14.1, tsconfig-paths@^3.14.1:
version "3.14.1"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a"
integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==
@@ -6831,16 +6863,6 @@ tsconfig-paths@3.14.1:
minimist "^1.2.6"
strip-bom "^3.0.0"
-tsconfig-paths@^3.12.0:
- version "3.12.0"
- resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz#19769aca6ee8f6a1a341e38c8fa45dd9fb18899b"
- integrity sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==
- dependencies:
- "@types/json5" "^0.0.29"
- json5 "^1.0.1"
- minimist "^1.2.0"
- strip-bom "^3.0.0"
-
tslib@^1.8.1:
version "1.11.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
@@ -6939,10 +6961,10 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
-typeorm@0.3.4:
- version "0.3.4"
- resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.4.tgz#6608f7efb15c40f3fa2863cefb45ff78a208c40c"
- integrity sha512-6v3HH12viDhIQwQDod/B0Plt1o7IYIVDxP7zwatD6fzN+IDdqTTinW/sWNw84Edpbhh2t7XILTaQEqj0NXFP/Q==
+typeorm@0.3.6:
+ version "0.3.6"
+ resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.6.tgz#65203443a1b684bb746785913fe2b0877aa991c0"
+ integrity sha512-DRqgfqcelMiGgWSMbBmVoJNFN2nPNA3EeY2gC324ndr2DZoGRTb9ILtp2oGVGnlA+cu5zgQ6it5oqKFNkte7Aw==
dependencies:
"@sqltools/formatter" "^1.2.2"
app-root-path "^3.0.0"
@@ -7284,10 +7306,10 @@ ws@^8.2.3:
resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b"
integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==
-xev@2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/xev/-/xev-2.0.1.tgz#24484173a22115bc8a990ef5d4d5129695b827a7"
- integrity sha512-icDf9M67bDge0F2qf02WKZq+s7mMO/SbPv67ZQPym6JThLEOdlWWLdB7VTVgRJp3ekgaiVItCAyH6aoKCPvfIA==
+xev@3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/xev/-/xev-3.0.2.tgz#3f4080bd8bed0d3479c674050e3696da98d22a4d"
+ integrity sha512-8kxuH95iMXzHZj+fwqfA4UrPcYOy6bGIgfWzo9Ji23JoEc30ge/Z++Ubkiuy8c0+M64nXmmxrmJ7C8wnuBhluw==
xml-js@^1.6.11:
version "1.6.11"
diff --git a/packages/client/@types/theme.d.ts b/packages/client/@types/theme.d.ts
new file mode 100644
index 000000000..b8b906b82
--- /dev/null
+++ b/packages/client/@types/theme.d.ts
@@ -0,0 +1,5 @@
+import { Theme } from '../src/scripts/theme';
+
+declare module '@/themes/*.json5' {
+ export = Theme;
+}
diff --git a/packages/client/package.json b/packages/client/package.json
index 6551b6aca..1d62d78d8 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -1,9 +1,9 @@
{
"private": true,
"scripts": {
- "watch": "webpack --watch",
- "build": "webpack",
- "lint": "eslint --quiet src/**/*.{ts,vue}"
+ "watch": "vite build --watch --mode development",
+ "build": "vite build",
+ "lint": "eslint --quiet \"src/**/*.{ts,vue}\""
},
"resolutions": {
"chokidar": "^3.3.1",
@@ -12,56 +12,35 @@
"dependencies": {
"@discordapp/twemoji": "13.1.1",
"@fortawesome/fontawesome-free": "6.1.1",
+ "@rollup/plugin-alias": "3.1.9",
+ "@rollup/plugin-json": "4.1.0",
"@syuilo/aiscript": "0.11.1",
- "@types/escape-regexp": "0.0.1",
- "@types/glob": "7.2.0",
- "@types/gulp": "4.0.9",
- "@types/gulp-rename": "2.0.1",
- "@types/is-url": "1.2.30",
- "@types/katex": "0.11.1",
- "@types/matter-js": "0.17.7",
- "@types/mocha": "9.1.0",
- "@types/oauth": "0.9.1",
- "@types/parse5": "6.0.3",
- "@types/punycode": "2.1.0",
- "@types/qrcode": "1.4.2",
- "@types/random-seed": "0.3.3",
- "@types/seedrandom": "3.0.2",
- "@types/throttle-debounce": "2.1.0",
- "@types/tinycolor2": "1.4.3",
- "@types/uuid": "8.3.4",
- "@types/webpack": "5.28.0",
- "@types/webpack-stream": "3.2.12",
- "@types/websocket": "1.0.5",
- "@types/ws": "8.5.3",
- "@typescript-eslint/parser": "5.16.0",
- "@vue/compiler-sfc": "3.2.31",
+ "@typescript-eslint/parser": "5.20.0",
+ "@vitejs/plugin-vue": "2.3.1",
+ "@vue/compiler-sfc": "3.2.33",
"abort-controller": "3.0.0",
"autobind-decorator": "2.4.0",
"autosize": "5.0.1",
"autwh": "0.1.0",
"blurhash": "1.1.5",
- "broadcast-channel": "4.10.0",
+ "broadcast-channel": "4.11.0",
+ "browser-image-resizer": "git+https://github.com/misskey-dev/browser-image-resizer#v2.2.1-misskey.2",
"chart.js": "3.7.1",
"chartjs-adapter-date-fns": "2.0.0",
"chartjs-plugin-gradient": "0.2.2",
"chartjs-plugin-zoom": "1.2.1",
"compare-versions": "4.1.3",
"content-disposition": "0.5.4",
- "css-loader": "6.7.1",
- "cssnano": "5.1.5",
"date-fns": "2.28.0",
"escape-regexp": "0.0.1",
- "eslint": "8.11.0",
- "eslint-plugin-vue": "8.5.0",
+ "eslint": "8.14.0",
+ "eslint-plugin-vue": "8.7.1",
"eventemitter3": "4.0.7",
"feed": "4.2.2",
"glob": "7.2.0",
"idb-keyval": "6.1.0",
"insert-text-at-cursor": "0.3.0",
- "ip-cidr": "3.0.4",
"json5": "2.2.1",
- "json5-loader": "4.0.1",
"katex": "0.15.3",
"matter-js": "0.18.0",
"mfm-js": "0.21.0",
@@ -70,11 +49,9 @@
"ms": "2.1.3",
"nested-property": "4.0.0",
"parse5": "6.0.1",
- "photoswipe": "git+https://github.com/dimsemenov/photoswipe#v5-beta",
+ "photoswipe": "5.2.4",
"portscanner": "2.2.0",
- "postcss": "8.4.12",
- "postcss-loader": "6.2.1",
- "prismjs": "1.27.0",
+ "prismjs": "1.28.0",
"private-ip": "2.3.3",
"promise-limit": "2.7.0",
"pug": "3.0.2",
@@ -84,19 +61,17 @@
"random-seed": "0.3.0",
"reflect-metadata": "0.1.13",
"rndstr": "1.0.0",
+ "rollup": "2.70.2",
"s-age": "1.1.2",
- "sass": "1.49.9",
- "sass-loader": "12.6.0",
+ "sass": "1.50.1",
"seedrandom": "3.0.5",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
- "style-loader": "3.3.1",
"syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0",
- "three": "0.139.0",
- "throttle-debounce": "3.0.1",
+ "three": "0.139.2",
+ "throttle-debounce": "4.0.1",
"tinycolor2": "1.4.2",
- "ts-loader": "9.2.8",
"tsc-alias": "1.5.0",
"tsconfig-paths": "3.14.1",
"twemoji-parser": "14.0.0",
@@ -104,23 +79,38 @@
"uuid": "8.3.2",
"v-debounce": "0.1.2",
"vanilla-tilt": "1.7.2",
- "vue": "3.2.31",
- "vue-loader": "17.0.0",
+ "vite": "2.9.6",
+ "vue": "3.2.33",
"vue-prism-editor": "2.0.0-alpha.2",
"vue-router": "4.0.14",
- "vue-style-loader": "4.1.3",
- "vue-svg-loader": "0.17.0-beta.2",
"vuedraggable": "4.0.1",
- "webpack": "5.70.0",
- "webpack-cli": "4.9.2",
"websocket": "1.0.34",
"ws": "8.5.0"
},
"devDependencies": {
- "@typescript-eslint/eslint-plugin": "5.16.0",
+ "@types/escape-regexp": "0.0.1",
+ "@types/glob": "7.2.0",
+ "@types/gulp": "4.0.9",
+ "@types/gulp-rename": "2.0.1",
+ "@types/is-url": "1.2.30",
+ "@types/katex": "0.14.0",
+ "@types/matter-js": "0.17.7",
+ "@types/mocha": "9.1.1",
+ "@types/oauth": "0.9.1",
+ "@types/parse5": "6.0.3",
+ "@types/punycode": "2.1.0",
+ "@types/qrcode": "1.4.2",
+ "@types/random-seed": "0.3.3",
+ "@types/seedrandom": "3.0.2",
+ "@types/throttle-debounce": "4.0.0",
+ "@types/tinycolor2": "1.4.3",
+ "@types/uuid": "8.3.4",
+ "@types/websocket": "1.0.5",
+ "@types/ws": "8.5.3",
+ "@typescript-eslint/eslint-plugin": "5.20.0",
"cross-env": "7.0.3",
- "cypress": "9.5.2",
- "eslint-plugin-import": "2.25.4",
+ "cypress": "9.5.4",
+ "eslint-plugin-import": "2.26.0",
"start-server-and-test": "1.14.0"
}
}
diff --git a/packages/client/src/account.ts b/packages/client/src/account.ts
index 4772c0baa..6f806ccc5 100644
--- a/packages/client/src/account.ts
+++ b/packages/client/src/account.ts
@@ -1,5 +1,5 @@
import { del, get, set } from '@/scripts/idb-proxy';
-import { reactive } from 'vue';
+import { defineAsyncComponent, reactive } from 'vue';
import * as misskey from 'misskey-js';
import { apiUrl } from '@/config';
import { waiting, api, popup, popupMenu, success, alert } from '@/os';
@@ -72,7 +72,7 @@ export async function addAccount(id: Account['id'], token: Account['token']) {
}
}
-function fetchAccount(token): Promise {
+function fetchAccount(token: string): Promise {
return new Promise((done, fail) => {
// Fetch user
fetch(`${apiUrl}/i`, {
@@ -141,7 +141,7 @@ export async function openAccountMenu(opts: {
onChoose?: (account: misskey.entities.UserDetailed) => void;
}, ev: MouseEvent) {
function showSigninDialog() {
- popup(import('@/components/signin-dialog.vue'), {}, {
+ popup(defineAsyncComponent(() => import('@/components/signin-dialog.vue')), {}, {
done: res => {
addAccount(res.id, res.i);
success();
@@ -150,7 +150,7 @@ export async function openAccountMenu(opts: {
}
function createAccount() {
- popup(import('@/components/signup-dialog.vue'), {}, {
+ popup(defineAsyncComponent(() => import('@/components/signup-dialog.vue')), {}, {
done: res => {
addAccount(res.id, res.i);
switchAccountWithToken(res.i);
diff --git a/packages/client/src/components/abuse-report.vue b/packages/client/src/components/abuse-report.vue
index b67cef209..46d45b690 100644
--- a/packages/client/src/components/abuse-report.vue
+++ b/packages/client/src/components/abuse-report.vue
@@ -43,20 +43,20 @@ export default defineComponent({
MkSwitch,
},
- emits: ['resolved'],
-
props: {
report: {
type: Object,
required: true,
}
- }
+ },
+
+ emits: ['resolved'],
data() {
return {
forward: this.report.forwarded,
};
- }
+ },
methods: {
acct,
diff --git a/packages/client/src/components/analog-clock.vue b/packages/client/src/components/analog-clock.vue
index 59b8e9730..18dd1e3f4 100644
--- a/packages/client/src/components/analog-clock.vue
+++ b/packages/client/src/components/analog-clock.vue
@@ -42,7 +42,7 @@
diff --git a/packages/client/src/components/drive.file.vue b/packages/client/src/components/drive.file.vue
index 262eae0de..aaf7ca3ca 100644
--- a/packages/client/src/components/drive.file.vue
+++ b/packages/client/src/components/drive.file.vue
@@ -31,7 +31,7 @@
diff --git a/packages/client/src/components/global/header.vue b/packages/client/src/components/global/header.vue
index e558614c1..63db19a52 100644
--- a/packages/client/src/components/global/header.vue
+++ b/packages/client/src/components/global/header.vue
@@ -38,7 +38,7 @@
-
diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts
index 37076652f..6ac410762 100644
--- a/packages/client/src/components/mfm.ts
+++ b/packages/client/src/components/mfm.ts
@@ -91,7 +91,8 @@ export default defineComponent({
let style;
switch (token.props.name) {
case 'tada': {
- style = `font-size: 150%;` + (this.$store.state.animatedMfm ? 'animation: tada 1s linear infinite both;' : '');
+ const speed = validTime(token.props.args.speed) || '1s';
+ style = 'font-size: 150%;' + (this.$store.state.animatedMfm ? `animation: tada ${speed} linear infinite both;` : '');
break;
}
case 'jelly': {
@@ -123,11 +124,13 @@ export default defineComponent({
break;
}
case 'jump': {
- style = this.$store.state.animatedMfm ? 'animation: mfm-jump 0.75s linear infinite;' : '';
+ const speed = validTime(token.props.args.speed) || '0.75s';
+ style = this.$store.state.animatedMfm ? `animation: mfm-jump ${speed} linear infinite;` : '';
break;
}
case 'bounce': {
- style = this.$store.state.animatedMfm ? 'animation: mfm-bounce 0.75s linear infinite; transform-origin: center bottom;' : '';
+ const speed = validTime(token.props.args.speed) || '0.75s';
+ style = this.$store.state.animatedMfm ? `animation: mfm-bounce ${speed} linear infinite; transform-origin: center bottom;` : '';
break;
}
case 'flip': {
@@ -168,7 +171,8 @@ export default defineComponent({
}, genEl(token.children));
}
case 'rainbow': {
- style = this.$store.state.animatedMfm ? 'animation: mfm-rainbow 1s linear infinite;' : '';
+ const speed = validTime(token.props.args.speed) || '1s';
+ style = this.$store.state.animatedMfm ? `animation: mfm-rainbow ${speed} linear infinite;` : '';
break;
}
case 'sparkle': {
diff --git a/packages/client/src/components/notification.vue b/packages/client/src/components/notification.vue
index 1a360f990..3791c576e 100644
--- a/packages/client/src/components/notification.vue
+++ b/packages/client/src/components/notification.vue
@@ -72,7 +72,7 @@
diff --git a/packages/client/src/pages/admin/emojis.vue b/packages/client/src/pages/admin/emojis.vue
index a080ee9c2..43dd83fc0 100644
--- a/packages/client/src/pages/admin/emojis.vue
+++ b/packages/client/src/pages/admin/emojis.vue
@@ -63,7 +63,7 @@
diff --git a/packages/client/src/pages/messaging/messaging-room.form.vue b/packages/client/src/pages/messaging/messaging-room.form.vue
index 3dad8478c..4df4b675e 100644
--- a/packages/client/src/pages/messaging/messaging-room.form.vue
+++ b/packages/client/src/pages/messaging/messaging-room.form.vue
@@ -1,5 +1,6 @@
-
@@ -29,7 +30,7 @@
import { onMounted, watch } from 'vue';
import * as Misskey from 'misskey-js';
import autosize from 'autosize';
-import insertTextAtCursor from 'insert-text-at-cursor';
+//import insertTextAtCursor from 'insert-text-at-cursor';
import { formatTimeString } from '@/scripts/format-time-string';
import { selectFile } from '@/scripts/select-file';
import * as os from '@/os';
@@ -37,7 +38,8 @@ import { stream } from '@/stream';
import { throttle } from 'throttle-debounce';
import { defaultStore } from '@/store';
import { i18n } from '@/i18n';
-import { Autocomplete } from '@/scripts/autocomplete';
+//import { Autocomplete } from '@/scripts/autocomplete';
+import { uploadFile } from '@/scripts/upload';
const props = defineProps<{
user?: Misskey.entities.UserDetailed | null;
@@ -55,27 +57,27 @@ const typing = throttle(3000, () => {
});
let draftKey = $computed(() => props.user ? 'user:' + props.user.id : 'group:' + props.group?.id);
-let canSend = $computed(() => (text != null && text != '') || file != null);
+let canSend = $computed(() => (text != null && text !== '') || file != null);
watch([$$(text), $$(file)], saveDraft);
-async function onPaste(e: ClipboardEvent) {
- if (!e.clipboardData) return;
+async function onPaste(ev: ClipboardEvent) {
+ if (!ev.clipboardData) return;
- const data = e.clipboardData;
- const items = data.items;
+ const clipboardData = ev.clipboardData;
+ const items = clipboardData.items;
- if (items.length == 1) {
- if (items[0].kind == 'file') {
- const file = items[0].getAsFile();
- if (!file) return;
- const lio = file.name.lastIndexOf('.');
- const ext = lio >= 0 ? file.name.slice(lio) : '';
- const formatted = `${formatTimeString(new Date(file.lastModified), defaultStore.state.pastedFileName).replace(/{{number}}/g, '1')}${ext}`;
- if (formatted) upload(file, formatted);
+ if (items.length === 1) {
+ if (items[0].kind === 'file') {
+ const pastedFile = items[0].getAsFile();
+ if (!pastedFile) return;
+ const lio = pastedFile.name.lastIndexOf('.');
+ const ext = lio >= 0 ? pastedFile.name.slice(lio) : '';
+ const formatted = `${formatTimeString(new Date(pastedFile.lastModified), defaultStore.state.pastedFileName).replace(/{{number}}/g, '1')}${ext}`;
+ if (formatted) upload(pastedFile, formatted);
}
} else {
- if (items[0].kind == 'file') {
+ if (items[0].kind === 'file') {
os.alert({
type: 'error',
text: i18n.ts.onlyOneFileCanBeAttached
@@ -84,27 +86,27 @@ async function onPaste(e: ClipboardEvent) {
}
}
-function onDragover(e: DragEvent) {
- if (!e.dataTransfer) return;
+function onDragover(ev: DragEvent) {
+ if (!ev.dataTransfer) return;
- const isFile = e.dataTransfer.items[0].kind == 'file';
- const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_;
+ const isFile = ev.dataTransfer.items[0].kind === 'file';
+ const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
if (isFile || isDriveFile) {
- e.preventDefault();
- e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move';
+ ev.preventDefault();
+ ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move';
}
}
-function onDrop(e: DragEvent): void {
- if (!e.dataTransfer) return;
+function onDrop(ev: DragEvent): void {
+ if (!ev.dataTransfer) return;
// ファイルだったら
- if (e.dataTransfer.files.length == 1) {
- e.preventDefault();
- upload(e.dataTransfer.files[0]);
+ if (ev.dataTransfer.files.length === 1) {
+ ev.preventDefault();
+ upload(ev.dataTransfer.files[0]);
return;
- } else if (e.dataTransfer.files.length > 1) {
- e.preventDefault();
+ } else if (ev.dataTransfer.files.length > 1) {
+ ev.preventDefault();
os.alert({
type: 'error',
text: i18n.ts.onlyOneFileCanBeAttached
@@ -113,17 +115,17 @@ function onDrop(e: DragEvent): void {
}
//#region ドライブのファイル
- const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
- if (driveFile != null && driveFile != '') {
+ const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
+ if (driveFile != null && driveFile !== '') {
file = JSON.parse(driveFile);
- e.preventDefault();
+ ev.preventDefault();
}
//#endregion
}
-function onKeydown(e: KeyboardEvent) {
+function onKeydown(ev: KeyboardEvent) {
typing();
- if ((e.key === 'Enter') && (e.ctrlKey || e.metaKey) && canSend) {
+ if ((ev.key === 'Enter') && (ev.ctrlKey || ev.metaKey) && canSend) {
send();
}
}
@@ -132,8 +134,8 @@ function onCompositionUpdate() {
typing();
}
-function chooseFile(e: MouseEvent) {
- selectFile(e.currentTarget ?? e.target, i18n.ts.selectFile).then(selectedFile => {
+function chooseFile(ev: MouseEvent) {
+ selectFile(ev.currentTarget ?? ev.target, i18n.ts.selectFile).then(selectedFile => {
file = selectedFile;
});
}
@@ -143,7 +145,7 @@ function onChangeFile() {
}
function upload(fileToUpload: File, name?: string) {
- os.upload(fileToUpload, defaultStore.state.uploadFolder, name).then(res => {
+ uploadFile(fileToUpload, defaultStore.state.uploadFolder, name).then(res => {
file = res;
});
}
@@ -171,25 +173,26 @@ function clear() {
}
function saveDraft() {
- const data = JSON.parse(localStorage.getItem('message_drafts') || '{}');
+ const drafts = JSON.parse(localStorage.getItem('message_drafts') || '{}');
- data[draftKey] = {
+ drafts[draftKey] = {
updatedAt: new Date(),
+ // eslint-disable-next-line id-denylist
data: {
text: text,
file: file
}
}
- localStorage.setItem('message_drafts', JSON.stringify(data));
+ localStorage.setItem('message_drafts', JSON.stringify(drafts));
}
function deleteDraft() {
- const data = JSON.parse(localStorage.getItem('message_drafts') || '{}');
+ const drafts = JSON.parse(localStorage.getItem('message_drafts') || '{}');
- delete data[draftKey];
+ delete drafts[draftKey];
- localStorage.setItem('message_drafts', JSON.stringify(data));
+ localStorage.setItem('message_drafts', JSON.stringify(drafts));
}
async function insertEmoji(ev: MouseEvent) {
diff --git a/packages/client/src/pages/messaging/messaging-room.vue b/packages/client/src/pages/messaging/messaging-room.vue
index 6106c970d..f85d4af3c 100644
--- a/packages/client/src/pages/messaging/messaging-room.vue
+++ b/packages/client/src/pages/messaging/messaging-room.vue
@@ -1,12 +1,13 @@
-
-
+
@@ -14,11 +15,11 @@
-
+
- {{ user.username }}
+ {{ typer.username }}
-
+
@@ -133,8 +134,8 @@ async function fetch() {
connection.on('message', onMessage);
connection.on('read', onRead);
connection.on('deleted', onDeleted);
- connection.on('typers', typers => {
- typers = typers.filter(u => u.id !== $i?.id);
+ connection.on('typers', _typers => {
+ typers = _typers.filter(u => u.id !== $i?.id);
});
document.addEventListener('visibilitychange', onVisibilitychange);
@@ -149,27 +150,27 @@ async function fetch() {
});
}
-function onDragover(e: DragEvent) {
- if (!e.dataTransfer) return;
+function onDragover(ev: DragEvent) {
+ if (!ev.dataTransfer) return;
- const isFile = e.dataTransfer.items[0].kind == 'file';
- const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_;
+ const isFile = ev.dataTransfer.items[0].kind === 'file';
+ const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
if (isFile || isDriveFile) {
- e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move';
+ ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move';
} else {
- e.dataTransfer.dropEffect = 'none';
+ ev.dataTransfer.dropEffect = 'none';
}
}
-function onDrop(e: DragEvent): void {
- if (!e.dataTransfer) return;
+function onDrop(ev: DragEvent): void {
+ if (!ev.dataTransfer) return;
// ファイルだったら
- if (e.dataTransfer.files.length == 1) {
- formEl.upload(e.dataTransfer.files[0]);
+ if (ev.dataTransfer.files.length === 1) {
+ formEl.upload(ev.dataTransfer.files[0]);
return;
- } else if (e.dataTransfer.files.length > 1) {
+ } else if (ev.dataTransfer.files.length > 1) {
os.alert({
type: 'error',
text: i18n.ts.onlyOneFileCanBeAttached
@@ -178,8 +179,8 @@ function onDrop(e: DragEvent): void {
}
//#region ドライブのファイル
- const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
- if (driveFile != null && driveFile != '') {
+ const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
+ if (driveFile != null && driveFile !== '') {
const file = JSON.parse(driveFile);
formEl.file = file;
}
@@ -192,7 +193,7 @@ function onMessage(message) {
const _isBottom = isBottomVisible(rootEl, 64);
pagingComponent.prepend(message);
- if (message.userId != $i?.id && !document.hidden) {
+ if (message.userId !== $i?.id && !document.hidden) {
connection?.send('read', {
id: message.id
});
@@ -203,7 +204,7 @@ function onMessage(message) {
nextTick(() => {
thisScrollToBottom();
});
- } else if (message.userId != $i?.id) {
+ } else if (message.userId !== $i?.id) {
// Notify
notifyNewMessage();
}
@@ -213,8 +214,8 @@ function onRead(x) {
if (user) {
if (!Array.isArray(x)) x = [x];
for (const id of x) {
- if (pagingComponent.items.some(x => x.id == id)) {
- const exist = pagingComponent.items.map(x => x.id).indexOf(id);
+ if (pagingComponent.items.some(y => y.id === id)) {
+ const exist = pagingComponent.items.map(y => y.id).indexOf(id);
pagingComponent.items[exist] = {
...pagingComponent.items[exist],
isRead: true,
@@ -223,8 +224,8 @@ function onRead(x) {
}
} else if (group) {
for (const id of x.ids) {
- if (pagingComponent.items.some(x => x.id == id)) {
- const exist = pagingComponent.items.map(x => x.id).indexOf(id);
+ if (pagingComponent.items.some(y => y.id === id)) {
+ const exist = pagingComponent.items.map(y => y.id).indexOf(id);
pagingComponent.items[exist] = {
...pagingComponent.items[exist],
reads: [...pagingComponent.items[exist].reads, x.userId]
diff --git a/packages/client/src/pages/mfm-cheat-sheet.vue b/packages/client/src/pages/mfm-cheat-sheet.vue
index 83ae5741c..aa35ec215 100644
--- a/packages/client/src/pages/mfm-cheat-sheet.vue
+++ b/packages/client/src/pages/mfm-cheat-sheet.vue
@@ -325,20 +325,20 @@ export default defineComponent({
preview_inlineMath: '\\(x= \\frac{-b\' \\pm \\sqrt{(b\')^2-ac}}{a}\\)',
preview_quote: `> ${this.$ts._mfm.dummy}`,
preview_search: `${this.$ts._mfm.dummy} 検索`,
- preview_jelly: `$[jelly 🍮]`,
- preview_tada: `$[tada 🍮]`,
- preview_jump: `$[jump 🍮]`,
- preview_bounce: `$[bounce 🍮]`,
- preview_shake: `$[shake 🍮]`,
- preview_twitch: `$[twitch 🍮]`,
- preview_spin: `$[spin 🍮] $[spin.left 🍮] $[spin.alternate 🍮]\n$[spin.x 🍮] $[spin.x,left 🍮] $[spin.x,alternate 🍮]\n$[spin.y 🍮] $[spin.y,left 🍮] $[spin.y,alternate 🍮]`,
+ preview_jelly: `$[jelly 🍮] $[jelly.speed=5s 🍮]`,
+ preview_tada: `$[tada 🍮] $[tada.speed=5s 🍮]`,
+ preview_jump: `$[jump 🍮] $[jump.speed=5s 🍮]`,
+ preview_bounce: `$[bounce 🍮] $[bounce.speed=5s 🍮]`,
+ preview_shake: `$[shake 🍮] $[shake.speed=5s 🍮]`,
+ preview_twitch: `$[twitch 🍮] $[twitch.speed=5s 🍮]`,
+ preview_spin: `$[spin 🍮] $[spin.left 🍮] $[spin.alternate 🍮]\n$[spin.x 🍮] $[spin.x,left 🍮] $[spin.x,alternate 🍮]\n$[spin.y 🍮] $[spin.y,left 🍮] $[spin.y,alternate 🍮]\n\n$[spin.speed=5s 🍮]`,
preview_flip: `$[flip ${this.$ts._mfm.dummy}]\n$[flip.v ${this.$ts._mfm.dummy}]\n$[flip.h,v ${this.$ts._mfm.dummy}]`,
preview_font: `$[font.serif ${this.$ts._mfm.dummy}]\n$[font.monospace ${this.$ts._mfm.dummy}]\n$[font.cursive ${this.$ts._mfm.dummy}]\n$[font.fantasy ${this.$ts._mfm.dummy}]`,
preview_x2: `$[x2 🍮]`,
preview_x3: `$[x3 🍮]`,
preview_x4: `$[x4 🍮]`,
preview_blur: `$[blur ${this.$ts._mfm.dummy}]`,
- preview_rainbow: `$[rainbow 🍮]`,
+ preview_rainbow: `$[rainbow 🍮] $[rainbow.speed=5s 🍮]`,
preview_sparkle: `$[sparkle 🍮]`,
preview_rotate: `$[rotate 🍮]`,
}
diff --git a/packages/client/src/pages/reset-password.vue b/packages/client/src/pages/reset-password.vue
index 7d008ae75..b3e2ca8d6 100644
--- a/packages/client/src/pages/reset-password.vue
+++ b/packages/client/src/pages/reset-password.vue
@@ -12,7 +12,7 @@
diff --git a/packages/client/src/pages/settings/2fa.vue b/packages/client/src/pages/settings/2fa.vue
index 10599d99f..9ebf5101c 100644
--- a/packages/client/src/pages/settings/2fa.vue
+++ b/packages/client/src/pages/settings/2fa.vue
@@ -1,49 +1,49 @@
-
{{ $ts._2fa.registerDevice }}
+
{{ i18n.ts._2fa.registerDevice }}
- {{ $ts._2fa.alreadyRegistered }}
- {{ $ts.unregister }}
+ {{ i18n.ts._2fa.alreadyRegistered }}
+ {{ i18n.ts.unregister }}
- {{ $ts.securityKey }}
- {{ $ts._2fa.securityKeyInfo }}
+ {{ i18n.ts.securityKey }}
+ {{ i18n.ts._2fa.securityKeyInfo }}
{{ key.name }}
-
{{ $ts.lastUsed }}
-
{{ $ts.unregister }}
+
{{ i18n.ts.lastUsed }}
+
{{ i18n.ts.unregister }}
- {{ $ts.passwordLessLogin }}
+ {{ i18n.ts.passwordLessLogin }}
- {{ $ts.error }} {{ registration.error }}
- {{ $ts._2fa.registerKey }}
+ {{ i18n.ts.error }} {{ registration.error }}
+ {{ i18n.ts._2fa.registerKey }}
-
- {{ $ts.tapSecurityKey }}
+ {{ i18n.ts.tapSecurityKey }}
-
- {{ $ts.securityKeyName }}
+ {{ i18n.ts.securityKeyName }}
- {{ $ts.registerSecurityKey }}
+ {{ i18n.ts.registerSecurityKey }}
-
+
-
-
+
Authy
@@ -52,19 +52,19 @@
- - {{ $ts._2fa.step2 }}
- - {{ $ts._2fa.step3 }}
- {{ $ts.token }}
- {{ $ts.done }}
+ - {{ i18n.ts._2fa.step2 }}
+ - {{ i18n.ts._2fa.step3 }}
+ {{ i18n.ts.token }}
+ {{ i18n.ts.done }}
-
{{ $ts._2fa.step4 }}
+
{{ i18n.ts._2fa.step4 }}
-
diff --git a/packages/client/src/pages/settings/account-info.vue b/packages/client/src/pages/settings/account-info.vue
index c98ad056f..12142b4dc 100644
--- a/packages/client/src/pages/settings/account-info.vue
+++ b/packages/client/src/pages/settings/account-info.vue
@@ -7,163 +7,150 @@
- {{ $ts.registeredDate }}
+ {{ i18n.ts.registeredDate }}
- {{ $ts.statistics }}
+ {{ i18n.ts.statistics }}
- {{ $ts.notesCount }}
+ {{ i18n.ts.notesCount }}
{{ number(stats.notesCount) }}
- {{ $ts.repliesCount }}
+ {{ i18n.ts.repliesCount }}
{{ number(stats.repliesCount) }}
- {{ $ts.renotesCount }}
+ {{ i18n.ts.renotesCount }}
{{ number(stats.renotesCount) }}
- {{ $ts.repliedCount }}
+ {{ i18n.ts.repliedCount }}
{{ number(stats.repliedCount) }}
- {{ $ts.renotedCount }}
+ {{ i18n.ts.renotedCount }}
{{ number(stats.renotedCount) }}
- {{ $ts.pollVotesCount }}
+ {{ i18n.ts.pollVotesCount }}
{{ number(stats.pollVotesCount) }}
- {{ $ts.pollVotedCount }}
+ {{ i18n.ts.pollVotedCount }}
{{ number(stats.pollVotedCount) }}
- {{ $ts.sentReactionsCount }}
+ {{ i18n.ts.sentReactionsCount }}
{{ number(stats.sentReactionsCount) }}
- {{ $ts.receivedReactionsCount }}
+ {{ i18n.ts.receivedReactionsCount }}
{{ number(stats.receivedReactionsCount) }}
- {{ $ts.noteFavoritesCount }}
+ {{ i18n.ts.noteFavoritesCount }}
{{ number(stats.noteFavoritesCount) }}
- {{ $ts.followingCount }}
+ {{ i18n.ts.followingCount }}
{{ number(stats.followingCount) }}
- {{ $ts.followingCount }} ({{ $ts.local }})
+ {{ i18n.ts.followingCount }} ({{ i18n.ts.local }})
{{ number(stats.localFollowingCount) }}
- {{ $ts.followingCount }} ({{ $ts.remote }})
+ {{ i18n.ts.followingCount }} ({{ i18n.ts.remote }})
{{ number(stats.remoteFollowingCount) }}
- {{ $ts.followersCount }}
+ {{ i18n.ts.followersCount }}
{{ number(stats.followersCount) }}
- {{ $ts.followersCount }} ({{ $ts.local }})
+ {{ i18n.ts.followersCount }} ({{ i18n.ts.local }})
{{ number(stats.localFollowersCount) }}
- {{ $ts.followersCount }} ({{ $ts.remote }})
+ {{ i18n.ts.followersCount }} ({{ i18n.ts.remote }})
{{ number(stats.remoteFollowersCount) }}
- {{ $ts.pageLikesCount }}
+ {{ i18n.ts.pageLikesCount }}
{{ number(stats.pageLikesCount) }}
- {{ $ts.pageLikedCount }}
+ {{ i18n.ts.pageLikedCount }}
{{ number(stats.pageLikedCount) }}
- {{ $ts.driveFilesCount }}
+ {{ i18n.ts.driveFilesCount }}
{{ number(stats.driveFilesCount) }}
- {{ $ts.driveUsage }}
+ {{ i18n.ts.driveUsage }}
{{ bytes(stats.driveUsage) }}
- {{ $ts.other }}
+ {{ i18n.ts.other }}
emailVerified
- {{ $i.emailVerified ? $ts.yes : $ts.no }}
+ {{ $i.emailVerified ? i18n.ts.yes : i18n.ts.no }}
twoFactorEnabled
- {{ $i.twoFactorEnabled ? $ts.yes : $ts.no }}
+ {{ $i.twoFactorEnabled ? i18n.ts.yes : i18n.ts.no }}
securityKeys
- {{ $i.securityKeys ? $ts.yes : $ts.no }}
+ {{ $i.securityKeys ? i18n.ts.yes : i18n.ts.no }}
usePasswordLessLogin
- {{ $i.usePasswordLessLogin ? $ts.yes : $ts.no }}
+ {{ $i.usePasswordLessLogin ? i18n.ts.yes : i18n.ts.no }}
isModerator
- {{ $i.isModerator ? $ts.yes : $ts.no }}
+ {{ $i.isModerator ? i18n.ts.yes : i18n.ts.no }}
isAdmin
- {{ $i.isAdmin ? $ts.yes : $ts.no }}
+ {{ $i.isAdmin ? i18n.ts.yes : i18n.ts.no }}
-
diff --git a/packages/client/src/pages/settings/accounts.vue b/packages/client/src/pages/settings/accounts.vue
index a744a031d..ecb2d036f 100644
--- a/packages/client/src/pages/settings/accounts.vue
+++ b/packages/client/src/pages/settings/accounts.vue
@@ -1,7 +1,7 @@
-
diff --git a/packages/client/src/pages/settings/general.vue b/packages/client/src/pages/settings/general.vue
index c8f6f5832..64b8cc310 100644
--- a/packages/client/src/pages/settings/general.vue
+++ b/packages/client/src/pages/settings/general.vue
@@ -1,10 +1,10 @@
-
diff --git a/packages/client/src/pages/settings/import-export.vue b/packages/client/src/pages/settings/import-export.vue
index c153b4d28..127cbcd4c 100644
--- a/packages/client/src/pages/settings/import-export.vue
+++ b/packages/client/src/pages/settings/import-export.vue
@@ -37,8 +37,8 @@
-
diff --git a/packages/client/src/pages/settings/index.vue b/packages/client/src/pages/settings/index.vue
index 42e40c5ac..e6670ea93 100644
--- a/packages/client/src/pages/settings/index.vue
+++ b/packages/client/src/pages/settings/index.vue
@@ -2,19 +2,22 @@
-
+
{{ $ts.emailNotConfiguredWarning }} {{ $ts.configure }}
-
+
-
@@ -23,7 +26,7 @@
diff --git a/packages/client/src/pages/settings/integration.vue b/packages/client/src/pages/settings/integration.vue
index ca36c9166..75c620094 100644
--- a/packages/client/src/pages/settings/integration.vue
+++ b/packages/client/src/pages/settings/integration.vue
@@ -1,133 +1,98 @@
-
diff --git a/packages/client/src/pages/settings/menu.vue b/packages/client/src/pages/settings/menu.vue
index 6e38cd5df..2288c3f71 100644
--- a/packages/client/src/pages/settings/menu.vue
+++ b/packages/client/src/pages/settings/menu.vue
@@ -1,24 +1,24 @@
- {{ $ts.menu }}
-
+ {{ i18n.ts.menu }}
+
- {{ $ts.display }}
-
-
-
-
+ {{ i18n.ts.display }}
+
+
+
+
- {{ $ts.default }}
+ {{ i18n.ts.default }}
-
diff --git a/packages/client/src/pages/settings/notifications.vue b/packages/client/src/pages/settings/notifications.vue
index 12171530b..6fe2ac55a 100644
--- a/packages/client/src/pages/settings/notifications.vue
+++ b/packages/client/src/pages/settings/notifications.vue
@@ -1,71 +1,59 @@
- {{ $ts.notificationSetting }}
+ {{ i18n.ts.notificationSetting }}
- {{ $ts.markAsReadAllNotifications }}
- {{ $ts.markAsReadAllUnreadNotes }}
- {{ $ts.markAsReadAllTalkMessages }}
+ {{ i18n.ts.markAsReadAllNotifications }}
+ {{ i18n.ts.markAsReadAllUnreadNotes }}
+ {{ i18n.ts.markAsReadAllTalkMessages }}
diff --git a/packages/client/src/pages/settings/other.vue b/packages/client/src/pages/settings/other.vue
index a9903acc7..82e174a5b 100644
--- a/packages/client/src/pages/settings/other.vue
+++ b/packages/client/src/pages/settings/other.vue
@@ -1,66 +1,44 @@
-
- {{ $ts.showFeaturedNotesInTimeline }}
+
+ {{ i18n.ts.showFeaturedNotesInTimeline }}
- {{ $ts.accountInfo }}
+ {{ i18n.ts.accountInfo }}
- {{ $ts.closeAccount }}
+ {{ i18n.ts.closeAccount }}
-
diff --git a/packages/client/src/pages/settings/plugin.install.vue b/packages/client/src/pages/settings/plugin.install.vue
index d35d20d17..6ece53146 100644
--- a/packages/client/src/pages/settings/plugin.install.vue
+++ b/packages/client/src/pages/settings/plugin.install.vue
@@ -1,19 +1,19 @@
-
diff --git a/packages/client/src/pages/settings/plugin.vue b/packages/client/src/pages/settings/plugin.vue
index 7a3ab9d15..873a022cb 100644
--- a/packages/client/src/pages/settings/plugin.vue
+++ b/packages/client/src/pages/settings/plugin.vue
@@ -1,38 +1,38 @@
-
diff --git a/packages/client/src/pages/settings/reaction.vue b/packages/client/src/pages/settings/reaction.vue
index a188ba353..963ac81df 100644
--- a/packages/client/src/pages/settings/reaction.vue
+++ b/packages/client/src/pages/settings/reaction.vue
@@ -54,7 +54,7 @@
diff --git a/packages/client/src/pages/settings/sounds.vue b/packages/client/src/pages/settings/sounds.vue
index 490a1b551..d01e87c1f 100644
--- a/packages/client/src/pages/settings/sounds.vue
+++ b/packages/client/src/pages/settings/sounds.vue
@@ -1,24 +1,24 @@
- {{ $ts.masterVolume }}
+ {{ i18n.ts.masterVolume }}
- {{ $ts.sounds }}
+ {{ i18n.ts.sounds }}
{{ $t('_sfx.' + type) }}
- {{ sounds[type].type || $ts.none }}
+ {{ sounds[type].type || i18n.ts.none }}
- {{ $ts.default }}
+ {{ i18n.ts.default }}
-
diff --git a/packages/client/src/pages/settings/theme.install.vue b/packages/client/src/pages/settings/theme.install.vue
index 2d3514342..0ef098f31 100644
--- a/packages/client/src/pages/settings/theme.install.vue
+++ b/packages/client/src/pages/settings/theme.install.vue
@@ -13,7 +13,7 @@
diff --git a/packages/client/src/pages/settings/theme.vue b/packages/client/src/pages/settings/theme.vue
index d134a092b..64b384bdc 100644
--- a/packages/client/src/pages/settings/theme.vue
+++ b/packages/client/src/pages/settings/theme.vue
@@ -85,12 +85,11 @@
-
diff --git a/packages/client/src/pages/settings/webhook.edit.vue b/packages/client/src/pages/settings/webhook.edit.vue
new file mode 100644
index 000000000..bb3a25407
--- /dev/null
+++ b/packages/client/src/pages/settings/webhook.edit.vue
@@ -0,0 +1,89 @@
+
+
+
+
+
diff --git a/packages/client/src/pages/settings/webhook.new.vue b/packages/client/src/pages/settings/webhook.new.vue
new file mode 100644
index 000000000..9bb492c49
--- /dev/null
+++ b/packages/client/src/pages/settings/webhook.new.vue
@@ -0,0 +1,81 @@
+
+
+
+
+
diff --git a/packages/client/src/pages/settings/webhook.vue b/packages/client/src/pages/settings/webhook.vue
new file mode 100644
index 000000000..c9af8b676
--- /dev/null
+++ b/packages/client/src/pages/settings/webhook.vue
@@ -0,0 +1,52 @@
+
+
+
+
+ Create webhook
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ webhook.name || webhook.url }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/client/src/pages/settings/word-mute.vue b/packages/client/src/pages/settings/word-mute.vue
index c11707b6c..97a15da5b 100644
--- a/packages/client/src/pages/settings/word-mute.vue
+++ b/packages/client/src/pages/settings/word-mute.vue
@@ -1,35 +1,35 @@
-
diff --git a/packages/client/src/pages/theme-editor.vue b/packages/client/src/pages/theme-editor.vue
index a53e23c1c..5f9f1b978 100644
--- a/packages/client/src/pages/theme-editor.vue
+++ b/packages/client/src/pages/theme-editor.vue
@@ -67,9 +67,9 @@
diff --git a/packages/client/src/ui/classic.header.vue b/packages/client/src/ui/classic.header.vue
index c7fddbc49..57008aeae 100644
--- a/packages/client/src/ui/classic.header.vue
+++ b/packages/client/src/ui/classic.header.vue
@@ -39,7 +39,7 @@