diff --git a/.gitignore b/.gitignore index 135bf9660a..52139614c2 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,8 @@ api-docs.json files ormconfig.json packages/backend/assets/instance.css +packages/backend/assets/sounds/None.mp3 + # blender backups *.blend1 diff --git a/README.md b/README.md index 91a2c88092..55f599235e 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,8 @@ psql postgres -c "create database calckey with encoding = 'UTF8';" - To add custom CSS for all users, edit `./custom/assets/instance.css`. - To add static assets (such as images for the splash screen), place them in the `./custom/assets/` directory. They'll then be available on `https://yourinstance.tld/static-assets/filename.ext`. - To add custom locales, place them in the `./custom/locales/` directory. If you name your custom locale the same as an existing locale, it will overwrite it. If you give it a unique name, it will be added to the list. Also make sure that the first part of the filename matches the locale you're basing it on. (Example: `en-FOO.yml`) +- To add custom error images, place them in the `./custom/assets/badges` directory, replacing the files already there. +- To add custom sounds, place only mp3 files in the `./custom/assets/sounds` directory. - To update custom assets without rebuilding, just run `pnpm run gulp`. ## π§βπ¬ Configuring a new instance diff --git a/custom/assets/badges/error.png b/custom/assets/badges/error.png new file mode 100644 index 0000000000..e90912b405 Binary files /dev/null and b/custom/assets/badges/error.png differ diff --git a/custom/assets/badges/info.png b/custom/assets/badges/info.png new file mode 100644 index 0000000000..ac70544e4a Binary files /dev/null and b/custom/assets/badges/info.png differ diff --git a/custom/assets/badges/not-found.png b/custom/assets/badges/not-found.png new file mode 100644 index 0000000000..73d611e0e1 Binary files /dev/null and b/custom/assets/badges/not-found.png differ diff --git a/packages/client/assets/sounds/None.mp3 b/custom/assets/sounds/None.mp3 similarity index 100% rename from packages/client/assets/sounds/None.mp3 rename to custom/assets/sounds/None.mp3 diff --git a/gulpfile.js b/gulpfile.js index 89a6acb839..87063c0bc0 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -15,8 +15,9 @@ gulp.task('copy:backend:views', () => gulp.src('./packages/backend/src/server/web/views/**/*').pipe(gulp.dest('./packages/backend/built/server/web/views')) ); + gulp.task('copy:backend:custom', () => - gulp.src('./custom/assets/*').pipe(gulp.dest('./packages/backend/assets/')) + gulp.src('./custom/assets/**/*').pipe(gulp.dest('./packages/backend/assets/')) ); gulp.task('copy:client:fonts', () => diff --git a/locales/en-US.yml b/locales/en-US.yml index f43f01fb72..173404e7c4 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -32,6 +32,7 @@ uploading: "Uploading..." save: "Save" users: "Users" addUser: "Add a user" +addInstance: "Add an instance" favorite: "Add to bookmarks" favorites: "Bookmarks" unfavorite: "Remove from bookmarks" @@ -160,6 +161,7 @@ proxyAccount: "Proxy account" proxyAccountDescription: "A proxy account is an account that acts as a remote follower for users under certain conditions. For example, when a user adds a remote user to the list, the remote user's activity will not be delivered to the instance if no local user is following that user, so the proxy account will follow instead." host: "Host" selectUser: "Select a user" +selectInstance: "Select an instance" recipient: "Recipient(s)" annotation: "Comments" federation: "Federation" @@ -197,6 +199,7 @@ muteAndBlock: "Mutes and Blocks" mutedUsers: "Muted users" blockedUsers: "Blocked users" noUsers: "There are no users" +noInstances: "There are no instances" editProfile: "Edit profile" noteDeleteConfirm: "Are you sure you want to delete this post?" pinLimitExceeded: "You cannot pin any more posts" @@ -363,6 +366,7 @@ notifyAntenna: "Notify about new posts" withFileAntenna: "Only posts with files" enableServiceworker: "Enable Push-Notifications for your Browser" antennaUsersDescription: "List one username per line" +antennaInstancesDescription: "List one instance host per line" caseSensitive: "Case sensitive" withReplies: "Include replies" connectedTo: "Following account(s) are connected" @@ -1294,12 +1298,14 @@ _auth: pleaseGoBack: "Please go back to the application" callback: "Returning to the application" denied: "Access denied" + copyAsk: "Please paste the following authorization code to the application" _antennaSources: all: "All posts" homeTimeline: "Posts from followed users" users: "Posts from specific users" userList: "Posts from a specified list of users" userGroup: "Posts from users in a specified group" + instances: "Posts from all users on an instance" _weekday: sunday: "Sunday" monday: "Monday" diff --git a/package.json b/package.json index 7cd7efc285..e8da6c5a73 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "calckey", - "version": "13.2.0-dev", + "version": "13.2.0-dev4", "codename": "aqua", "repository": { "type": "git", @@ -39,7 +39,7 @@ "@bull-board/api": "^4.10.2", "@bull-board/ui": "^4.10.2", "@tensorflow/tfjs": "^3.21.0", - "calckey-js": "^0.0.20", + "calckey-js": "^0.0.22", "js-yaml": "4.1.0", "phosphor-icons": "^1.4.2", "seedrandom": "^3.0.5" diff --git a/packages/client/assets/sounds/aisha/1.mp3 b/packages/backend/assets/sounds/aisha/1.mp3 similarity index 100% rename from packages/client/assets/sounds/aisha/1.mp3 rename to packages/backend/assets/sounds/aisha/1.mp3 diff --git a/packages/client/assets/sounds/aisha/2.mp3 b/packages/backend/assets/sounds/aisha/2.mp3 similarity index 100% rename from packages/client/assets/sounds/aisha/2.mp3 rename to packages/backend/assets/sounds/aisha/2.mp3 diff --git a/packages/client/assets/sounds/aisha/3.mp3 b/packages/backend/assets/sounds/aisha/3.mp3 similarity index 100% rename from packages/client/assets/sounds/aisha/3.mp3 rename to packages/backend/assets/sounds/aisha/3.mp3 diff --git a/packages/client/assets/sounds/noizenecio/kick_gaba.mp3 b/packages/backend/assets/sounds/noizenecio/kick_gaba.mp3 similarity index 100% rename from packages/client/assets/sounds/noizenecio/kick_gaba.mp3 rename to packages/backend/assets/sounds/noizenecio/kick_gaba.mp3 diff --git a/packages/client/assets/sounds/noizenecio/kick_gaba2.mp3 b/packages/backend/assets/sounds/noizenecio/kick_gaba2.mp3 similarity index 100% rename from packages/client/assets/sounds/noizenecio/kick_gaba2.mp3 rename to packages/backend/assets/sounds/noizenecio/kick_gaba2.mp3 diff --git a/packages/client/assets/sounds/syuilo/down.mp3 b/packages/backend/assets/sounds/syuilo/down.mp3 similarity index 100% rename from packages/client/assets/sounds/syuilo/down.mp3 rename to packages/backend/assets/sounds/syuilo/down.mp3 diff --git a/packages/client/assets/sounds/syuilo/kick.mp3 b/packages/backend/assets/sounds/syuilo/kick.mp3 similarity index 100% rename from packages/client/assets/sounds/syuilo/kick.mp3 rename to packages/backend/assets/sounds/syuilo/kick.mp3 diff --git a/packages/client/assets/sounds/syuilo/pirori-square-wet.mp3 b/packages/backend/assets/sounds/syuilo/pirori-square-wet.mp3 similarity index 100% rename from packages/client/assets/sounds/syuilo/pirori-square-wet.mp3 rename to packages/backend/assets/sounds/syuilo/pirori-square-wet.mp3 diff --git a/packages/client/assets/sounds/syuilo/pirori-wet.mp3 b/packages/backend/assets/sounds/syuilo/pirori-wet.mp3 similarity index 100% rename from packages/client/assets/sounds/syuilo/pirori-wet.mp3 rename to packages/backend/assets/sounds/syuilo/pirori-wet.mp3 diff --git a/packages/client/assets/sounds/syuilo/pirori.mp3 b/packages/backend/assets/sounds/syuilo/pirori.mp3 similarity index 100% rename from packages/client/assets/sounds/syuilo/pirori.mp3 rename to packages/backend/assets/sounds/syuilo/pirori.mp3 diff --git a/packages/client/assets/sounds/syuilo/poi1.mp3 b/packages/backend/assets/sounds/syuilo/poi1.mp3 similarity index 100% rename from packages/client/assets/sounds/syuilo/poi1.mp3 rename to packages/backend/assets/sounds/syuilo/poi1.mp3 diff --git a/packages/client/assets/sounds/syuilo/poi2.mp3 b/packages/backend/assets/sounds/syuilo/poi2.mp3 similarity index 100% rename from packages/client/assets/sounds/syuilo/poi2.mp3 rename to packages/backend/assets/sounds/syuilo/poi2.mp3 diff --git a/packages/client/assets/sounds/syuilo/pope1.mp3 b/packages/backend/assets/sounds/syuilo/pope1.mp3 similarity index 100% rename from packages/client/assets/sounds/syuilo/pope1.mp3 rename to packages/backend/assets/sounds/syuilo/pope1.mp3 diff --git a/packages/client/assets/sounds/syuilo/pope2.mp3 b/packages/backend/assets/sounds/syuilo/pope2.mp3 similarity index 100% rename from packages/client/assets/sounds/syuilo/pope2.mp3 rename to packages/backend/assets/sounds/syuilo/pope2.mp3 diff --git a/packages/client/assets/sounds/syuilo/popo.mp3 b/packages/backend/assets/sounds/syuilo/popo.mp3 similarity index 100% rename from packages/client/assets/sounds/syuilo/popo.mp3 rename to packages/backend/assets/sounds/syuilo/popo.mp3 diff --git a/packages/client/assets/sounds/syuilo/queue-jammed.mp3 b/packages/backend/assets/sounds/syuilo/queue-jammed.mp3 similarity index 100% rename from packages/client/assets/sounds/syuilo/queue-jammed.mp3 rename to packages/backend/assets/sounds/syuilo/queue-jammed.mp3 diff --git a/packages/client/assets/sounds/syuilo/reverved.mp3 b/packages/backend/assets/sounds/syuilo/reverved.mp3 similarity index 100% rename from packages/client/assets/sounds/syuilo/reverved.mp3 rename to packages/backend/assets/sounds/syuilo/reverved.mp3 diff --git a/packages/client/assets/sounds/syuilo/ryukyu.mp3 b/packages/backend/assets/sounds/syuilo/ryukyu.mp3 similarity index 100% rename from packages/client/assets/sounds/syuilo/ryukyu.mp3 rename to packages/backend/assets/sounds/syuilo/ryukyu.mp3 diff --git a/packages/client/assets/sounds/syuilo/snare.mp3 b/packages/backend/assets/sounds/syuilo/snare.mp3 similarity index 100% rename from packages/client/assets/sounds/syuilo/snare.mp3 rename to packages/backend/assets/sounds/syuilo/snare.mp3 diff --git a/packages/client/assets/sounds/syuilo/square-pico.mp3 b/packages/backend/assets/sounds/syuilo/square-pico.mp3 similarity index 100% rename from packages/client/assets/sounds/syuilo/square-pico.mp3 rename to packages/backend/assets/sounds/syuilo/square-pico.mp3 diff --git a/packages/client/assets/sounds/syuilo/triple.mp3 b/packages/backend/assets/sounds/syuilo/triple.mp3 similarity index 100% rename from packages/client/assets/sounds/syuilo/triple.mp3 rename to packages/backend/assets/sounds/syuilo/triple.mp3 diff --git a/packages/client/assets/sounds/syuilo/up.mp3 b/packages/backend/assets/sounds/syuilo/up.mp3 similarity index 100% rename from packages/client/assets/sounds/syuilo/up.mp3 rename to packages/backend/assets/sounds/syuilo/up.mp3 diff --git a/packages/client/assets/sounds/syuilo/waon.mp3 b/packages/backend/assets/sounds/syuilo/waon.mp3 similarity index 100% rename from packages/client/assets/sounds/syuilo/waon.mp3 rename to packages/backend/assets/sounds/syuilo/waon.mp3 diff --git a/packages/backend/migration/1676093997212-AntennaInstances.js b/packages/backend/migration/1676093997212-AntennaInstances.js new file mode 100644 index 0000000000..7553e12557 --- /dev/null +++ b/packages/backend/migration/1676093997212-AntennaInstances.js @@ -0,0 +1,17 @@ +export class AntennaInstances1676093997212 { + name = 'AntennaInstances1676093997212' + + async up(queryRunner) { + await queryRunner.query(`ALTER TYPE "antenna_src_enum" ADD VALUE 'instances'`); + await queryRunner.query(`ALTER TABLE "antenna" ADD "instances" jsonb NOT NULL DEFAULT '[]'`); + } + + async down(queryRunner) { + await queryRunner.query(`DELETE FROM "antenna" WHERE "src" = 'instances'`); + await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "instances"`); + await queryRunner.query(`CREATE TYPE "public"."antenna_src_enum_old" AS ENUM('home', 'all', 'users', 'list', 'group')`); + await queryRunner.query(`ALTER TABLE "antenna" ALTER COLUMN "src" TYPE "public"."antenna_src_enum_old" USING "src"::"text"::"public"."antenna_src_enum_old"`); + await queryRunner.query(`DROP TYPE "public"."antenna_src_enum"`); + await queryRunner.query(`ALTER TYPE "public"."antenna_src_enum_old" RENAME TO "antenna_src_enum"`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index 80484f95ce..55a64191a3 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -47,7 +47,7 @@ "blurhash": "1.1.5", "bull": "4.10.2", "cacheable-lookup": "7.0.0", - "calckey-js": "^0.0.20", + "calckey-js": "^0.0.22", "cbor": "8.1.0", "chalk": "5.2.0", "chalk-template": "0.4.0", @@ -79,7 +79,7 @@ "koa-send": "5.0.1", "koa-slow": "2.1.0", "koa-views": "7.0.2", - "@cutls/megalodon": "5.1.15", + "@calckey/megalodon": "5.1.2", "mfm-js": "0.23.2", "mime-types": "2.1.35", "multer": "1.4.4-lts.1", diff --git a/packages/backend/src/misc/check-hit-antenna.ts b/packages/backend/src/misc/check-hit-antenna.ts index aa38d9e275..adcdd190f8 100644 --- a/packages/backend/src/misc/check-hit-antenna.ts +++ b/packages/backend/src/misc/check-hit-antenna.ts @@ -80,6 +80,13 @@ export async function checkHitAntenna( ) ) return false; + } else if (antenna.src === "instances") { + const instances = antenna.instances + .filter((x) => x !== "") + .map((host) => { + return host.toLowerCase(); + }); + if (!instances.includes(noteUser.host?.toLowerCase() ?? "")) return false; } const keywords = antenna.keywords diff --git a/packages/backend/src/models/entities/antenna.ts b/packages/backend/src/models/entities/antenna.ts index 45d9553e4e..c653b2a051 100644 --- a/packages/backend/src/models/entities/antenna.ts +++ b/packages/backend/src/models/entities/antenna.ts @@ -40,8 +40,8 @@ export class Antenna { }) public name: string; - @Column('enum', { enum: ['home', 'all', 'users', 'list', 'group'] }) - public src: "home" | "all" | "users" | "list" | "group"; + @Column('enum', { enum: ['home', 'all', 'users', 'list', 'group', 'instances'] }) + public src: "home" | "all" | "users" | "list" | "group" | "instances"; @Column({ ...id(), @@ -73,6 +73,11 @@ export class Antenna { }) public users: string[]; + @Column('jsonb', { + default: [], + }) + public instances: string[]; + @Column('jsonb', { default: [], }) diff --git a/packages/backend/src/models/repositories/antenna.ts b/packages/backend/src/models/repositories/antenna.ts index 57ce2fc9e8..c325e25895 100644 --- a/packages/backend/src/models/repositories/antenna.ts +++ b/packages/backend/src/models/repositories/antenna.ts @@ -25,6 +25,7 @@ export const AntennaRepository = db.getRepository(Antenna).extend({ userListId: antenna.userListId, userGroupId: userGroupJoining ? userGroupJoining.userGroupId : null, users: antenna.users, + instances: antenna.instances, caseSensitive: antenna.caseSensitive, notify: antenna.notify, withReplies: antenna.withReplies, diff --git a/packages/backend/src/models/schema/antenna.ts b/packages/backend/src/models/schema/antenna.ts index c7eed092e9..990e2daa2c 100644 --- a/packages/backend/src/models/schema/antenna.ts +++ b/packages/backend/src/models/schema/antenna.ts @@ -52,7 +52,7 @@ export const packedAntennaSchema = { type: "string", optional: false, nullable: false, - enum: ["home", "all", "users", "list", "group"], + enum: ["home", "all", "users", "list", "group", "instances"], }, userListId: { type: "string", @@ -76,6 +76,16 @@ export const packedAntennaSchema = { nullable: false, }, }, + instances: { + type: "array", + optional: false, + nullable: false, + items: { + type: "string", + optional: false, + nullable: false, + }, + }, caseSensitive: { type: "boolean", optional: false, diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index 28ce46e300..10ab5c66f5 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -125,6 +125,24 @@ export async function createNote( logger.info(`Creating the Note: ${note.id}`); + // Skip if note is made before 2007 (1yr before Fedi was created) + // OR skip if note is made 3 days in advance + if (note.published) { + const DateChecker = new Date(note.published); + const FutureCheck = new Date(); + FutureCheck.setDate(FutureCheck.getDate() + 3); // Allow some wiggle room for misconfigured hosts + if (DateChecker.getFullYear() < 2007) { + logger.warn( + "Note somehow made before Activitypub was created; discarding", + ); + return null; + } + if (DateChecker > FutureCheck) { + logger.warn("Note somehow made after today; discarding"); + return null; + } + } + // Fetch author const actor = (await resolvePerson( getOneApId(note.attributedTo), diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 6ee1a977e5..7fb5fd3209 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -222,6 +222,7 @@ import * as ep___messaging_messages_create from "./endpoints/messaging/messages/ import * as ep___messaging_messages_delete from "./endpoints/messaging/messages/delete.js"; import * as ep___messaging_messages_read from "./endpoints/messaging/messages/read.js"; import * as ep___meta from "./endpoints/meta.js"; +import * as ep___sounds from "./endpoints/get-sounds.js"; import * as ep___miauth_genToken from "./endpoints/miauth/gen-token.js"; import * as ep___mute_create from "./endpoints/mute/create.js"; import * as ep___mute_delete from "./endpoints/mute/delete.js"; @@ -668,6 +669,7 @@ const eps = [ ["users/stats", ep___users_stats], ["admin/drive-capacity-override", ep___admin_driveCapOverride], ["fetch-rss", ep___fetchRss], + ["get-sounds", ep___sounds], ]; export interface IEndpointMeta { diff --git a/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts b/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts index 1f6ac5f27e..c8be344696 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts @@ -1,7 +1,7 @@ import define from "../../define.js"; import { Users } from "@/models/index.js"; -import { User } from "@/models/entities/user.js"; import { insertModerationLog } from "@/services/insert-moderation-log.js"; +import { publishInternalEvent } from "@/services/stream.js"; export const meta = { tags: ["admin"], @@ -29,17 +29,14 @@ export default define(meta, paramDef, async (ps, me) => { throw new Error("user is not local user"); } - /*if (user.isAdmin) { - throw new Error('cannot suspend admin'); - } - if (user.isModerator) { - throw new Error('cannot suspend moderator'); - }*/ - await Users.update(user.id, { driveCapacityOverrideMb: ps.overrideMb, }); + publishInternalEvent("localUserUpdated", { + id: user.id, + }); + insertModerationLog(me, "change-drive-capacity-override", { targetId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index 171fc2c643..c1ba7bcdfd 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -37,7 +37,10 @@ export const paramDef = { type: "object", properties: { name: { type: "string", minLength: 1, maxLength: 100 }, - src: { type: "string", enum: ["home", "all", "users", "list", "group"] }, + src: { + type: "string", + enum: ["home", "all", "users", "list", "group", "instances"], + }, userListId: { type: "string", format: "misskey:id", nullable: true }, userGroupId: { type: "string", format: "misskey:id", nullable: true }, keywords: { @@ -64,6 +67,12 @@ export const paramDef = { type: "string", }, }, + instances: { + type: "array", + items: { + type: "string", + }, + }, caseSensitive: { type: "boolean" }, withReplies: { type: "boolean" }, withFile: { type: "boolean" }, @@ -75,6 +84,7 @@ export const paramDef = { "keywords", "excludeKeywords", "users", + "instances", "caseSensitive", "withReplies", "withFile", @@ -118,6 +128,7 @@ export default define(meta, paramDef, async (ps, user) => { keywords: ps.keywords, excludeKeywords: ps.excludeKeywords, users: ps.users, + instances: ps.instances, caseSensitive: ps.caseSensitive, withReplies: ps.withReplies, withFile: ps.withFile, diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index 6c48cd3696..f491c0b638 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -43,7 +43,10 @@ export const paramDef = { properties: { antennaId: { type: "string", format: "misskey:id" }, name: { type: "string", minLength: 1, maxLength: 100 }, - src: { type: "string", enum: ["home", "all", "users", "list", "group"] }, + src: { + type: "string", + enum: ["home", "all", "users", "list", "group", "instances"], + }, userListId: { type: "string", format: "misskey:id", nullable: true }, userGroupId: { type: "string", format: "misskey:id", nullable: true }, keywords: { @@ -70,6 +73,12 @@ export const paramDef = { type: "string", }, }, + instances: { + type: "array", + items: { + type: "string", + }, + }, caseSensitive: { type: "boolean" }, withReplies: { type: "boolean" }, withFile: { type: "boolean" }, @@ -82,6 +91,7 @@ export const paramDef = { "keywords", "excludeKeywords", "users", + "instances", "caseSensitive", "withReplies", "withFile", @@ -131,6 +141,7 @@ export default define(meta, paramDef, async (ps, user) => { keywords: ps.keywords, excludeKeywords: ps.excludeKeywords, users: ps.users, + instances: ps.instances, caseSensitive: ps.caseSensitive, withReplies: ps.withReplies, withFile: ps.withFile, diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts index 3ab37ff6f2..8f6184b196 100644 --- a/packages/backend/src/server/api/endpoints/federation/instances.ts +++ b/packages/backend/src/server/api/endpoints/federation/instances.ts @@ -102,10 +102,13 @@ export default define(meta, paramDef, async (ps, me) => { if (typeof ps.blocked === "boolean") { const meta = await fetchMeta(true); if (ps.blocked) { + if (meta.blockedHosts.length === 0) { + return []; + } query.andWhere("instance.host IN (:...blocks)", { blocks: meta.blockedHosts, }); - } else { + } else if (meta.blockedHosts.length > 0) { query.andWhere("instance.host NOT IN (:...blocks)", { blocks: meta.blockedHosts, }); diff --git a/packages/backend/src/server/api/endpoints/get-sounds.ts b/packages/backend/src/server/api/endpoints/get-sounds.ts new file mode 100644 index 0000000000..f7edd38609 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/get-sounds.ts @@ -0,0 +1,30 @@ +import { readdir } from "fs/promises"; +import define from "../define.js"; + +export const meta = { + tags: ["meta"], + requireCredential: false, + requireCredentialPrivateMode: false, +} as const; + +export const paramDef = { + type: "object", + properties: {}, + required: [], +} as const; + +export default define(meta, paramDef, async () => { + const music_files: (string | null)[] = [null]; + const directory = ( + await readdir("./assets/sounds", { withFileTypes: true }) + ).filter((potentialFolder) => potentialFolder.isDirectory()); + for await (const folder of directory) { + const files = (await readdir(`./assets/sounds/${folder.name}`)).filter( + (potentialSong) => potentialSong.endsWith(".mp3"), + ); + for await (const file of files) { + music_files.push(`${folder.name}/${file.replace(".mp3", "")}`); + } + } + return music_files; +}); diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts index cd7e44296e..a6d764bf37 100644 --- a/packages/backend/src/server/api/endpoints/notes/featured.ts +++ b/packages/backend/src/server/api/endpoints/notes/featured.ts @@ -27,6 +27,11 @@ export const paramDef = { properties: { limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, offset: { type: "integer", default: 0 }, + origin: { + type: "string", + enum: ["combined", "local", "remote"], + default: "local", + }, }, required: [], } as const; @@ -37,7 +42,7 @@ export default define(meta, paramDef, async (ps, user) => { const query = Notes.createQueryBuilder("note") .addSelect("note.score") - .where("note.userHost IS NULL") + // .where("note.userHost IS NULL") .andWhere("note.score > 0") .andWhere("note.createdAt > :date", { date: new Date(Date.now() - day) }) .andWhere("note.visibility = 'public'") @@ -53,6 +58,15 @@ export default define(meta, paramDef, async (ps, user) => { .leftJoinAndSelect("renoteUser.avatar", "renoteUserAvatar") .leftJoinAndSelect("renoteUser.banner", "renoteUserBanner"); + switch (ps.origin) { + case "local": + query.andWhere("note.userHost IS NULL"); + break; + case "remote": + query.andWhere("note.userHost IS NOT NULL"); + break; + } + if (user) generateMutedUserQuery(query, user); if (user) generateBlockedUserQuery(query, user); diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts index 1897059037..6d02ad0ab5 100644 --- a/packages/backend/src/server/api/index.ts +++ b/packages/backend/src/server/api/index.ts @@ -61,10 +61,12 @@ router.use( }), ); -mastoRouter.use(koaBody({ - multipart: true, - urlencoded: true -})); +mastoRouter.use( + koaBody({ + multipart: true, + urlencoded: true, + }), +); apiMastodonCompatible(mastoRouter); diff --git a/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts b/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts index ddd2a23f03..cc60a8db9b 100644 --- a/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts +++ b/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts @@ -1,5 +1,5 @@ import Router from "@koa/router"; -import megalodon, { MegalodonInterface } from "@cutls/megalodon"; +import megalodon, { MegalodonInterface } from "@calckey/megalodon"; import { apiAuthMastodon } from "./endpoints/auth.js"; import { apiAccountMastodon } from "./endpoints/account.js"; import { apiStatusMastodon } from "./endpoints/status.js"; diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts index 0162951d63..ebe6dd2ddb 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/account.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts @@ -1,11 +1,26 @@ -import megalodon, { MegalodonInterface } from "@cutls/megalodon"; import Router from "@koa/router"; -import { koaBody } from "koa-body"; import { getClient } from "../ApiMastodonCompatibleService.js"; import { toLimitToInt } from "./timeline.js"; +const relationshopModel = { + id: "", + following: false, + followed_by: false, + delivery_following: false, + blocking: false, + blocked_by: false, + muting: false, + muting_notifications: false, + requested: false, + domain_blocking: false, + showing_reblogs: false, + endorsed: false, + notifying: false, + note: "", +}; + export function apiAccountMastodon(router: Router): void { - router.get("/v1/accounts/verify_credentials", async (ctx, next) => { + router.get("/v1/accounts/verify_credentials", async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -48,26 +63,39 @@ export function apiAccountMastodon(router: Router): void { ctx.body = e.response.data; } }); - router.get<{ Params: { id: string } }>( - "/v1/accounts/:id", - async (ctx, next) => { - const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; - const accessTokens = ctx.headers.authorization; - const client = getClient(BASE_URL, accessTokens); - try { - const data = await client.getAccount(ctx.params.id); - ctx.body = data.data; - } catch (e: any) { - console.error(e); - console.error(e.response.data); - ctx.status = 401; - ctx.body = e.response.data; - } - }, - ); + router.get("/v1/accounts/lookup", async (ctx) => { + const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; + const accessTokens = ctx.headers.authorization; + const client = getClient(BASE_URL, accessTokens); + try { + const data = await client.getAccount( + `@${(ctx.query.acct || "").toString()}`, + ); + ctx.body = data.data; + } catch (e: any) { + console.error(e); + console.error(e.response.data); + ctx.status = 401; + ctx.body = e.response.data; + } + }); + router.get<{ Params: { id: string } }>("/v1/accounts/:id", async (ctx) => { + const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; + const accessTokens = ctx.headers.authorization; + const client = getClient(BASE_URL, accessTokens); + try { + const data = await client.getAccount(ctx.params.id); + ctx.body = data.data; + } catch (e: any) { + console.error(e); + console.error(e.response.data); + ctx.status = 401; + ctx.body = e.response.data; + } + }); router.get<{ Params: { id: string } }>( "/v1/accounts/:id/statuses", - async (ctx, next) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -87,7 +115,7 @@ export function apiAccountMastodon(router: Router): void { ); router.get<{ Params: { id: string } }>( "/v1/accounts/:id/followers", - async (ctx, next) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -107,7 +135,7 @@ export function apiAccountMastodon(router: Router): void { ); router.get<{ Params: { id: string } }>( "/v1/accounts/:id/following", - async (ctx, next) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -127,7 +155,7 @@ export function apiAccountMastodon(router: Router): void { ); router.get<{ Params: { id: string } }>( "/v1/accounts/:id/lists", - async (ctx, next) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -144,7 +172,7 @@ export function apiAccountMastodon(router: Router): void { ); router.post<{ Params: { id: string } }>( "/v1/accounts/:id/follow", - async (ctx, next) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -163,7 +191,7 @@ export function apiAccountMastodon(router: Router): void { ); router.post<{ Params: { id: string } }>( "/v1/accounts/:id/unfollow", - async (ctx, next) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -182,7 +210,7 @@ export function apiAccountMastodon(router: Router): void { ); router.post<{ Params: { id: string } }>( "/v1/accounts/:id/block", - async (ctx, next) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -199,7 +227,7 @@ export function apiAccountMastodon(router: Router): void { ); router.post<{ Params: { id: string } }>( "/v1/accounts/:id/unblock", - async (ctx, next) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -236,7 +264,7 @@ export function apiAccountMastodon(router: Router): void { ); router.post<{ Params: { id: string } }>( "/v1/accounts/:id/unmute", - async (ctx, next) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -251,13 +279,15 @@ export function apiAccountMastodon(router: Router): void { } }, ); - router.get("/v1/accounts/relationships", async (ctx, next) => { + router.get("/v1/accounts/relationships", async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { const idsRaw = (ctx.query as any)["id[]"]; const ids = typeof idsRaw === "string" ? [idsRaw] : idsRaw; + relationshopModel.id = idsRaw || "1"; + if (!idsRaw) return [relationshopModel]; const data = (await client.getRelationships(ids)) as any; ctx.body = data.data; } catch (e: any) { @@ -267,7 +297,7 @@ export function apiAccountMastodon(router: Router): void { ctx.body = e.response.data; } }); - router.get("/v1/bookmarks", async (ctx, next) => { + router.get("/v1/bookmarks", async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -281,7 +311,7 @@ export function apiAccountMastodon(router: Router): void { ctx.body = e.response.data; } }); - router.get("/v1/favourites", async (ctx, next) => { + router.get("/v1/favourites", async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -295,7 +325,7 @@ export function apiAccountMastodon(router: Router): void { ctx.body = e.response.data; } }); - router.get("/v1/mutes", async (ctx, next) => { + router.get("/v1/mutes", async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -309,7 +339,7 @@ export function apiAccountMastodon(router: Router): void { ctx.body = e.response.data; } }); - router.get("/v1/blocks", async (ctx, next) => { + router.get("/v1/blocks", async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -323,7 +353,7 @@ export function apiAccountMastodon(router: Router): void { ctx.body = e.response.data; } }); - router.get("/v1/follow_ctxs", async (ctx, next) => { + router.get("/v1/follow_ctxs", async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -341,7 +371,7 @@ export function apiAccountMastodon(router: Router): void { }); router.post<{ Params: { id: string } }>( "/v1/follow_ctxs/:id/authorize", - async (ctx, next) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -358,7 +388,7 @@ export function apiAccountMastodon(router: Router): void { ); router.post<{ Params: { id: string } }>( "/v1/follow_ctxs/:id/reject", - async (ctx, next) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); diff --git a/packages/backend/src/server/api/mastodon/endpoints/auth.ts b/packages/backend/src/server/api/mastodon/endpoints/auth.ts index 46b013f3f6..5d10641fb2 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/auth.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/auth.ts @@ -1,4 +1,4 @@ -import megalodon, { MegalodonInterface } from "@cutls/megalodon"; +import megalodon, { MegalodonInterface } from "@calckey/megalodon"; import Router from "@koa/router"; import { koaBody } from "koa-body"; import { getClient } from "../ApiMastodonCompatibleService.js"; @@ -58,10 +58,7 @@ export function apiAuthMastodon(router: Router): void { } const scopeArr = Array.from(pushScope); - let red = body.redirect_uris; - if (red === "urn:ietf:wg:oauth:2.0:oob") { - red = "https://thedesk.top/hello.html"; - } + const red = body.redirect_uris; const appData = await client.registerApp(body.client_name, { scopes: scopeArr, redirect_uris: red, diff --git a/packages/backend/src/server/api/mastodon/endpoints/filter.ts b/packages/backend/src/server/api/mastodon/endpoints/filter.ts index 6098a95435..d21bc1d330 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/filter.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/filter.ts @@ -1,4 +1,4 @@ -import megalodon, { MegalodonInterface } from "@cutls/megalodon"; +import megalodon, { MegalodonInterface } from "@calckey/megalodon"; import Router from "@koa/router"; import { getClient } from "../ApiMastodonCompatibleService.js"; diff --git a/packages/backend/src/server/api/mastodon/endpoints/meta.ts b/packages/backend/src/server/api/mastodon/endpoints/meta.ts index 3496272b9e..a67edc5b2f 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/meta.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/meta.ts @@ -1,4 +1,4 @@ -import { Entity } from "@cutls/megalodon"; +import { Entity } from "@calckey/megalodon"; // TODO: add calckey features export function getInstance(response: Entity.Instance) { return { diff --git a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts index 9c52414ad0..8508f1d486 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts @@ -1,4 +1,4 @@ -import megalodon, { MegalodonInterface } from "@cutls/megalodon"; +import megalodon, { MegalodonInterface } from "@calckey/megalodon"; import Router from "@koa/router"; import { koaBody } from "koa-body"; import { getClient } from "../ApiMastodonCompatibleService.js"; diff --git a/packages/backend/src/server/api/mastodon/endpoints/search.ts b/packages/backend/src/server/api/mastodon/endpoints/search.ts index 48161733ed..c8ca1f0504 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/search.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/search.ts @@ -1,4 +1,4 @@ -import megalodon, { MegalodonInterface } from "@cutls/megalodon"; +import megalodon, { MegalodonInterface } from "@calckey/megalodon"; import Router from "@koa/router"; import { getClient } from "../ApiMastodonCompatibleService.js"; diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts index 3afd7e5769..2be79c73a7 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/status.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts @@ -1,5 +1,5 @@ import Router from "@koa/router"; -import megalodon, { MegalodonInterface } from "@cutls/megalodon"; +import megalodon, { MegalodonInterface } from "@calckey/megalodon"; import { getClient } from "../ApiMastodonCompatibleService.js"; import fs from "fs"; import { pipeline } from "node:stream"; @@ -435,7 +435,7 @@ export function statusModel( id: "9arzuvv0sw", username: "ReactionBot", acct: "ReactionBot", - display_name: "ReactionOfThisPost", + display_name: "ReactionsToThisPost", locked: false, created_at: now, followers_count: 0, diff --git a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts index 9caf431143..bb91a5604d 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts @@ -1,13 +1,14 @@ import Router from "@koa/router"; -import megalodon, { Entity, MegalodonInterface } from "@cutls/megalodon"; +import megalodon, { Entity, MegalodonInterface } from "@calckey/megalodon"; import { getClient } from "../ApiMastodonCompatibleService.js"; import { statusModel } from "./status.js"; import Autolinker from "autolinker"; import { ParsedUrlQuery } from "querystring"; export function toLimitToInt(q: ParsedUrlQuery) { + let object: any = q; if (q.limit) - if (typeof q.limit === "string") q.limit = parseInt(q.limit, 10).toString(); + if (typeof q.limit === "string") object.limit = parseInt(q.limit, 10); return q; } diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts index 167aa614c8..10b9fb6497 100644 --- a/packages/backend/src/server/api/stream/index.ts +++ b/packages/backend/src/server/api/stream/index.ts @@ -24,7 +24,7 @@ import { readNotification } from "../common/read-notification.js"; import channels from "./channels/index.js"; import type Channel from "./channel.js"; import type { StreamEventEmitter, StreamMessages } from "./types.js"; -import { Converter } from "@cutls/megalodon"; +import { Converter } from "@calckey/megalodon"; import { getClient } from "../mastodon/ApiMastodonCompatibleService.js"; import { toTextWithReaction } from "../mastodon/endpoints/timeline.js"; diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index 837f42c871..9becf9f642 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -7,7 +7,6 @@ import type { Note } from "@/models/entities/note.js"; import type { Antenna } from "@/models/entities/antenna.js"; import type { DriveFile } from "@/models/entities/drive-file.js"; import type { DriveFolder } from "@/models/entities/drive-folder.js"; -import { Emoji } from "@/models/entities/emoji.js"; import type { UserList } from "@/models/entities/user-list.js"; import type { MessagingMessage } from "@/models/entities/messaging-message.js"; import type { UserGroup } from "@/models/entities/user-group.js"; @@ -23,7 +22,10 @@ export interface InternalStreamTypes { id: User["id"]; isSuspended: User["isSuspended"]; }; - userChangeSilencedState: { id: User["id"]; isSilenced: User["isSilenced"] }; + userChangeSilencedState: { + id: User["id"]; + isSilenced: User["isSilenced"]; + }; userChangeModeratorState: { id: User["id"]; isModerator: User["isModerator"]; @@ -33,7 +35,12 @@ export interface InternalStreamTypes { oldToken: User["token"]; newToken: User["token"]; }; - remoteUserUpdated: { id: User["id"] }; + localUserUpdated: { + id: User["id"]; + }; + remoteUserUpdated: { + id: User["id"]; + }; webhookCreated: Webhook; webhookDeleted: Webhook; webhookUpdated: Webhook; @@ -135,6 +142,9 @@ export interface NoteStreamTypes { reaction: string; userId: User["id"]; }; + replied: { + id: Note["id"]; + }; } type NoteStreamEventTypes = { [key in keyof NoteStreamTypes]: { diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts index cd495971ef..6ce28bf1a8 100644 --- a/packages/backend/src/server/index.ts +++ b/packages/backend/src/server/index.ts @@ -20,7 +20,7 @@ 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 megalodon, { MegalodonInterface } from "@cutls/megalodon"; +import megalodon, { MegalodonInterface } from "@calckey/megalodon"; import activityPub from "./activitypub.js"; import nodeinfo from "./nodeinfo.js"; import wellKnown from "./well-known.js"; @@ -72,9 +72,11 @@ app.use(mount("/proxy", proxyServer)); const router = new Router(); const mastoRouter = new Router(); -mastoRouter.use(koaBody({ - urlencoded: true -})); +mastoRouter.use( + koaBody({ + urlencoded: true, + }), +); // Routing router.use(activityPub.routes()); @@ -159,9 +161,9 @@ mastoRouter.post("/oauth/token", async (ctx) => { ctx.body = { error: "Invalid code" }; return; } - } + } if (client_id instanceof Array) { - client_id = client_id.toString();; + client_id = client_id.toString(); } else if (!client_id) { client_id = null; } @@ -169,7 +171,7 @@ mastoRouter.post("/oauth/token", async (ctx) => { const atData = await client.fetchAccessToken( client_id, body.client_secret, - m ? m[0] : '', + m ? m[0] : "", ); ctx.body = { access_token: atData.accessToken, diff --git a/packages/backend/src/server/web/style.css b/packages/backend/src/server/web/style.css index 5072e0ad4e..ee42b9deba 100644 --- a/packages/backend/src/server/web/style.css +++ b/packages/backend/src/server/web/style.css @@ -1,4 +1,4 @@ -html { +html, body { background-color: var(--bg); color: var(--fg); } diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index 210ea77710..968aed880f 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -1,6 +1,10 @@ import * as mfm from "mfm-js"; import es from "../../db/elasticsearch.js"; -import { publishMainStream, publishNotesStream } from "@/services/stream.js"; +import { + publishMainStream, + publishNotesStream, + publishNoteStream, +} from "@/services/stream.js"; import DeliverManager from "@/remote/activitypub/deliver-manager.js"; import renderNote from "@/remote/activitypub/renderer/note.js"; import renderCreate from "@/remote/activitypub/renderer/create.js"; @@ -430,6 +434,12 @@ export default async ( } publishNotesStream(note); + if (note.replyId != null) { + // Only provide the reply note id here as the recipient may not be authorized to see the note. + publishNoteStream(note.replyId, "replied", { + id: note.id, + }); + } const webhooks = await getActiveWebhooks().then((webhooks) => webhooks.filter((x) => x.userId === user.id && x.on.includes("note")), diff --git a/packages/backend/src/services/user-cache.ts b/packages/backend/src/services/user-cache.ts index 1a3b2fc657..9492448554 100644 --- a/packages/backend/src/services/user-cache.ts +++ b/packages/backend/src/services/user-cache.ts @@ -21,6 +21,16 @@ subscriber.on("message", async (_, data) => { if (obj.channel === "internal") { const { type, body } = obj.message; switch (type) { + case "localUserUpdated": { + userByIdCache.delete(body.id); + localUserByIdCache.delete(body.id); + localUserByNativeTokenCache.cache.forEach((v, k) => { + if (v.value?.id === body.id) { + localUserByNativeTokenCache.delete(k); + } + }); + break; + } case "userChangeSuspendedState": case "userChangeSilencedState": case "userChangeModeratorState": diff --git a/packages/client/package.json b/packages/client/package.json index 5a184dbc0f..fb63b52a6f 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -33,7 +33,7 @@ "blurhash": "1.1.5", "broadcast-channel": "4.19.1", "browser-image-resizer": "https://github.com/misskey-dev/browser-image-resizer.git", - "calckey-js": "^0.0.20", + "calckey-js": "^0.0.22", "chart.js": "4.1.1", "chartjs-adapter-date-fns": "2.0.1", "chartjs-plugin-gradient": "0.5.1", diff --git a/packages/client/src/components/MkInstanceSelectDialog.vue b/packages/client/src/components/MkInstanceSelectDialog.vue new file mode 100644 index 0000000000..0d0123f0fb --- /dev/null +++ b/packages/client/src/components/MkInstanceSelectDialog.vue @@ -0,0 +1,157 @@ + + + {{ i18n.ts.selectInstance }} + + + + {{ i18n.ts.instance }} + + + + + + + + {{ item.host }} + + + + + {{ i18n.ts.noInstances }} + + + + + + + + + diff --git a/packages/client/src/components/MkNoteDetailed.vue b/packages/client/src/components/MkNoteDetailed.vue index dd6187d7e4..fab06f1ab6 100644 --- a/packages/client/src/components/MkNoteDetailed.vue +++ b/packages/client/src/components/MkNoteDetailed.vue @@ -142,6 +142,8 @@ import { i18n } from '@/i18n'; import { getNoteMenu } from '@/scripts/get-note-menu'; import { useNoteCapture } from '@/scripts/use-note-capture'; import { deepClone } from '@/scripts/clone'; +import { stream } from '@/stream'; +import { NoteUpdatedEvent } from 'calckey-js/built/streaming.types'; const router = useRouter(); @@ -302,6 +304,31 @@ if (appearNote.replyId) { conversation.value = res.reverse(); }); } + +function onNoteReplied(noteData: NoteUpdatedEvent): void { + const { type, id, body } = noteData; + if (type === 'replied' && id === appearNote.id) { + const { id: createdId } = body; + + os.api('notes/show', { + noteId: createdId, + }).then(note => { + if (note.replyId === appearNote.id) { + replies.value.unshift(note); + directReplies.value.unshift(note); + } + }); + } + +} + +onMounted(() => { + stream.on("noteUpdated", onNoteReplied); +}); + +onUnmounted(() => { + stream.off("noteUpdated", onNoteReplied); +});