From cdd8109e812e3448cadcc15475f1efb6f02f06cc Mon Sep 17 00:00:00 2001 From: HidemaruOwO Date: Fri, 23 Aug 2024 02:17:33 +0900 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refer=20new=20firefish=20(?= =?UTF-8?q?.gitlab-ci.yml,=20.gitlab/issue=5Ftemplates/default.md,=20.gitl?= =?UTF-8?q?ab/issue=5Ftemplates/discussion.md,=20.gitlab/issue=5Ftemplates?= =?UTF-8?q?/feature.md,=20.gitlab/issue=5Ftemplates/refactor.md,=20.gitlab?= =?UTF-8?q?/merge=5Frequest=5Ftemplates/default.md,=20.gitlab/merge=5Frequ?= =?UTF-8?q?est=5Ftemplates/release.md,=20Cargo.lock,=20Cargo.toml,=20Docke?= =?UTF-8?q?rfile,=20dev/container/docker-compose.yml,=20dev/docs/local-ins?= =?UTF-8?q?tallation.md,=20docs/changelog.md,=20docs/downgrade.sql,=20docs?= =?UTF-8?q?/install.md,=20docs/notice-for-admins.md,=20locales/ca-ES.yml,?= =?UTF-8?q?=20locales/en-US.yml,=20locales/eo.yml,=20locales/ja-JP.yml,=20?= =?UTF-8?q?locales/ko-KR.yml,=20locales/zh-CN.yml,=20locales/zh-TW.yml,=20?= =?UTF-8?q?package.json,=20packages/backend-rs/Cargo.toml,=20packages/back?= =?UTF-8?q?end-rs/index.d.ts,=20packages/backend-rs/index.js,=20packages/b?= =?UTF-8?q?ackend-rs/package.json,=20packages/backend-rs/src/cache/bare.rs?= =?UTF-8?q?,=20packages/backend-rs/src/cache/mod.rs,=20packages/backend-rs?= =?UTF-8?q?/src/cache/redis.rs,=20packages/backend-rs/src/config/meta.rs,?= =?UTF-8?q?=20packages/backend-rs/src/database/mod.rs,=20packages/backend-?= =?UTF-8?q?rs/src/federation/activitypub/object/accept.rs,=20packages/back?= =?UTF-8?q?end-rs/src/federation/activitypub/object/add.rs,=20packages/bac?= =?UTF-8?q?kend-rs/src/federation/activitypub/object/emoji.rs,=20packages/?= =?UTF-8?q?backend-rs/src/federation/activitypub/object/flag.rs,=20package?= =?UTF-8?q?s/backend-rs/src/federation/activitypub/object/follow.rs,=20pac?= =?UTF-8?q?kages/backend-rs/src/federation/activitypub/object/hashtag.rs,?= =?UTF-8?q?=20packages/backend-rs/src/federation/activitypub/object/like.r?= =?UTF-8?q?s,=20packages/backend-rs/src/federation/activitypub/object/ment?= =?UTF-8?q?ion.rs,=20packages/backend-rs/src/federation/activitypub/object?= =?UTF-8?q?/mod.rs,=20packages/backend-rs/src/federation/activitypub/objec?= =?UTF-8?q?t/read.rs,=20packages/backend-rs/src/federation/activitypub/obj?= =?UTF-8?q?ect/reject.rs,=20packages/backend-rs/src/federation/activitypub?= =?UTF-8?q?/object/remove.rs,=20packages/backend-rs/src/federation/activit?= =?UTF-8?q?ypub/object/tombstone.rs,=20packages/backend-rs/src/federation/?= =?UTF-8?q?internal=5Factor/instance.rs,=20packages/backend-rs/src/federat?= =?UTF-8?q?ion/internal=5Factor/relay.rs,=20packages/backend-rs/src/federa?= =?UTF-8?q?tion/nodeinfo/fetch.rs,=20packages/backend-rs/src/federation/no?= =?UTF-8?q?deinfo/generate.rs,=20packages/backend-rs/src/init/system=5Finf?= =?UTF-8?q?o.rs,=20packages/backend-rs/src/lib.rs,=20packages/backend-rs/s?= =?UTF-8?q?rc/misc/emoji/mod.rs,=20packages/backend-rs/src/misc/emoji/reac?= =?UTF-8?q?tion.rs,=20packages/backend-rs/src/misc/emoji/unicode.rs,=20pac?= =?UTF-8?q?kages/backend-rs/src/misc/get=5Fimage=5Fsize.rs,=20packages/bac?= =?UTF-8?q?kend-rs/src/misc/latest=5Fversion.rs,=20packages/backend-rs/src?= =?UTF-8?q?/misc/mod.rs,=20packages/backend-rs/src/misc/note/mod.rs,=20pac?= =?UTF-8?q?kages/backend-rs/src/misc/random=5Ficon.rs,=20packages/backend-?= =?UTF-8?q?rs/src/misc/should=5Fnyaify.rs,=20packages/backend-rs/src/misc/?= =?UTF-8?q?system=5Finfo.rs,=20packages/backend-rs/src/misc/translate.rs,?= =?UTF-8?q?=20packages/backend-rs/src/misc/user/mod.rs,=20packages/backend?= =?UTF-8?q?-rs/src/model/entity/abuse=5Fuser=5Freport.rs,=20packages/backe?= =?UTF-8?q?nd-rs/src/model/entity/access=5Ftoken.rs,=20packages/backend-rs?= =?UTF-8?q?/src/model/entity/ad.rs,=20packages/backend-rs/src/model/entity?= =?UTF-8?q?/announcement.rs,=20packages/backend-rs/src/model/entity/announ?= =?UTF-8?q?cement=5Fread.rs,=20packages/backend-rs/src/model/entity/antenn?= =?UTF-8?q?a.rs,=20packages/backend-rs/src/model/entity/app.rs,=20packages?= =?UTF-8?q?/backend-rs/src/model/entity/attestation=5Fchallenge.rs,=20pack?= =?UTF-8?q?ages/backend-rs/src/model/entity/auth=5Fsession.rs,=20packages/?= =?UTF-8?q?backend-rs/src/model/entity/blocking.rs,=20packages/backend-rs/?= =?UTF-8?q?src/model/entity/channel.rs,=20packages/backend-rs/src/model/en?= =?UTF-8?q?tity/channel=5Ffollowing.rs,=20packages/backend-rs/src/model/en?= =?UTF-8?q?tity/channel=5Fnote=5Fpining.rs,=20packages/backend-rs/src/mode?= =?UTF-8?q?l/entity/clip.rs,=20packages/backend-rs/src/model/entity/clip?= =?UTF-8?q?=5Fnote.rs,=20packages/backend-rs/src/model/entity/drive=5Ffile?= =?UTF-8?q?.rs,=20packages/backend-rs/src/model/entity/drive=5Ffolder.rs,?= =?UTF-8?q?=20packages/backend-rs/src/model/entity/emoji.rs,=20packages/ba?= =?UTF-8?q?ckend-rs/src/model/entity/follow=5Frequest.rs,=20packages/backe?= =?UTF-8?q?nd-rs/src/model/entity/following.rs,=20packages/backend-rs/src/?= =?UTF-8?q?model/entity/gallery=5Flike.rs,=20packages/backend-rs/src/model?= =?UTF-8?q?/entity/gallery=5Fpost.rs,=20packages/backend-rs/src/model/enti?= =?UTF-8?q?ty/hashtag.rs,=20packages/backend-rs/src/model/entity/instance.?= =?UTF-8?q?rs,=20packages/backend-rs/src/model/entity/messaging=5Fmessage.?= =?UTF-8?q?rs,=20packages/backend-rs/src/model/entity/meta.rs,=20packages/?= =?UTF-8?q?backend-rs/src/model/entity/migrations.rs,=20packages/backend-r?= =?UTF-8?q?s/src/model/entity/mod.rs,=20packages/backend-rs/src/model/enti?= =?UTF-8?q?ty/moderation=5Flog.rs,=20packages/backend-rs/src/model/entity/?= =?UTF-8?q?muted=5Fnote.rs,=20packages/backend-rs/src/model/entity/muting.?= =?UTF-8?q?rs,=20packages/backend-rs/src/model/entity/note.rs,=20packages/?= =?UTF-8?q?backend-rs/src/model/entity/note=5Fedit.rs,=20packages/backend-?= =?UTF-8?q?rs/src/model/entity/note=5Ffavorite.rs,=20packages/backend-rs/s?= =?UTF-8?q?rc/model/entity/note=5Ffile.rs,=20packages/backend-rs/src/model?= =?UTF-8?q?/entity/note=5Freaction.rs,=20packages/backend-rs/src/model/ent?= =?UTF-8?q?ity/note=5Fthread=5Fmuting.rs,=20packages/backend-rs/src/model/?= =?UTF-8?q?entity/note=5Funread.rs,=20packages/backend-rs/src/model/entity?= =?UTF-8?q?/note=5Fwatching.rs,=20packages/backend-rs/src/model/entity/not?= =?UTF-8?q?ification.rs,=20packages/backend-rs/src/model/entity/page.rs,?= =?UTF-8?q?=20packages/backend-rs/src/model/entity/page=5Flike.rs,=20packa?= =?UTF-8?q?ges/backend-rs/src/model/entity/password=5Freset=5Frequest.rs,?= =?UTF-8?q?=20packages/backend-rs/src/model/entity/poll.rs,=20packages/bac?= =?UTF-8?q?kend-rs/src/model/entity/poll=5Fvote.rs,=20packages/backend-rs/?= =?UTF-8?q?src/model/entity/prelude.rs,=20packages/backend-rs/src/model/en?= =?UTF-8?q?tity/promo=5Fnote.rs,=20packages/backend-rs/src/model/entity/pr?= =?UTF-8?q?omo=5Fread.rs,=20packages/backend-rs/src/model/entity/registrat?= =?UTF-8?q?ion=5Fticket.rs,=20packages/backend-rs/src/model/entity/registr?= =?UTF-8?q?y=5Fitem.rs,=20packages/backend-rs/src/model/entity/relay.rs,?= =?UTF-8?q?=20packages/backend-rs/src/model/entity/renote=5Fmuting.rs,=20p?= =?UTF-8?q?ackages/backend-rs/src/model/entity/reply=5Fmuting.rs,=20packag?= =?UTF-8?q?es/backend-rs/src/model/entity/sea=5Form=5Factive=5Fenums.rs,?= =?UTF-8?q?=20packages/backend-rs/src/model/entity/signin.rs,=20packages/b?= =?UTF-8?q?ackend-rs/src/model/entity/sw=5Fsubscription.rs,=20packages/bac?= =?UTF-8?q?kend-rs/src/model/entity/used=5Fusername.rs,=20packages/backend?= =?UTF-8?q?-rs/src/model/entity/user.rs,=20packages/backend-rs/src/model/e?= =?UTF-8?q?ntity/user=5Fgroup.rs,=20packages/backend-rs/src/model/entity/u?= =?UTF-8?q?ser=5Fgroup=5Finvitation.rs,=20packages/backend-rs/src/model/en?= =?UTF-8?q?tity/user=5Fgroup=5Finvite.rs,=20packages/backend-rs/src/model/?= =?UTF-8?q?entity/user=5Fgroup=5Fjoining.rs,=20packages/backend-rs/src/mod?= =?UTF-8?q?el/entity/user=5Fip.rs,=20packages/backend-rs/src/model/entity/?= =?UTF-8?q?user=5Fkeypair.rs,=20packages/backend-rs/src/model/entity/user?= =?UTF-8?q?=5Flist.rs,=20packages/backend-rs/src/model/entity/user=5Flist?= =?UTF-8?q?=5Fjoining.rs,=20packages/backend-rs/src/model/entity/user=5Fno?= =?UTF-8?q?te=5Fpining.rs,=20packages/backend-rs/src/model/entity/user=5Fp?= =?UTF-8?q?ending.rs,=20packages/backend-rs/src/model/entity/user=5Fprofil?= =?UTF-8?q?e.rs,=20packages/backend-rs/src/model/entity/user=5Fpublickey.r?= =?UTF-8?q?s,=20packages/backend-rs/src/model/entity/user=5Fsecurity=5Fkey?= =?UTF-8?q?.rs,=20packages/backend-rs/src/model/entity/webhook.rs,=20packa?= =?UTF-8?q?ges/backend-rs/src/service/antenna/check=5Fhit.rs,=20packages/b?= =?UTF-8?q?ackend-rs/src/service/antenna/mod.rs,=20packages/backend-rs/src?= =?UTF-8?q?/service/antenna/process=5Fnew=5Fnote.rs,=20packages/backend-rs?= =?UTF-8?q?/src/service/antenna/update.rs,=20packages/backend-rs/src/servi?= =?UTF-8?q?ce/push=5Fnotification.rs,=20packages/backend-rs/src/service/st?= =?UTF-8?q?ream.rs,=20packages/backend-rs/src/util/id.rs,=20packages/backe?= =?UTF-8?q?nd/package.json,=20packages/backend/src/boot/master.ts,=20packa?= =?UTF-8?q?ges/backend/src/migration/1644010796173-convert-hard-mutes.ts,?= =?UTF-8?q?=20packages/backend/src/migration/1652859567549-uniform-themeco?= =?UTF-8?q?lor.ts,=20packages/backend/src/migration/1722346019160-set-emoj?= =?UTF-8?q?i-public-url.ts,=20packages/backend/src/models/entities/emoji.t?= =?UTF-8?q?s,=20packages/backend/src/models/entities/note.ts,=20packages/b?= =?UTF-8?q?ackend/src/models/entities/user-profile.ts,=20packages/backend/?= =?UTF-8?q?src/models/entities/user.ts,=20packages/backend/src/models/repo?= =?UTF-8?q?sitories/renote-muting.ts,=20packages/backend/src/models/reposi?= =?UTF-8?q?tories/reply-muting.ts,=20packages/backend/src/models/repositor?= =?UTF-8?q?ies/user.ts,=20packages/backend/src/queue/processors/db/import-?= =?UTF-8?q?firefish-post.ts,=20packages/backend/src/queue/processors/db/im?= =?UTF-8?q?port-masto-post.ts,=20packages/backend/src/remote/activitypub/k?= =?UTF-8?q?ernel/like.ts,=20packages/backend/src/remote/activitypub/misc/c?= =?UTF-8?q?ontexts.ts,=20packages/backend/src/remote/activitypub/models/pe?= =?UTF-8?q?rson.ts,=20packages/backend/src/remote/activitypub/renderer/not?= =?UTF-8?q?e.ts,=20packages/backend/src/remote/activitypub/renderer/person?= =?UTF-8?q?.ts,=20packages/backend/src/remote/activitypub/request.ts,=20pa?= =?UTF-8?q?ckages/backend/src/remote/activitypub/resolver.ts,=20packages/b?= =?UTF-8?q?ackend/src/remote/activitypub/type.ts,=20packages/backend/src/s?= =?UTF-8?q?erver/activitypub.ts,=20packages/backend/src/server/api/common/?= =?UTF-8?q?inject-featured.ts,=20packages/backend/src/server/api/common/re?= =?UTF-8?q?ad-messaging-message.ts,=20packages/backend/src/server/api/endp?= =?UTF-8?q?oints/admin/resolve-abuse-user-report.ts,=20packages/backend/sr?= =?UTF-8?q?c/server/api/endpoints/notes/featured.ts,=20packages/backend/sr?= =?UTF-8?q?c/server/api/mastodon/helpers/misc.ts,=20packages/backend/src/s?= =?UTF-8?q?erver/file/byte-range-readable.ts,=20packages/backend/src/serve?= =?UTF-8?q?r/index.ts,=20packages/backend/src/server/web/bios.css,=20packa?= =?UTF-8?q?ges/backend/src/server/web/cli.css,=20packages/backend/src/serv?= =?UTF-8?q?er/web/style.css,=20packages/backend/src/services/blocking/crea?= =?UTF-8?q?te.ts,=20packages/backend/src/services/fetch-instance-metadata.?= =?UTF-8?q?ts,=20packages/backend/src/services/following/create.ts,=20pack?= =?UTF-8?q?ages/backend/src/services/following/delete.ts,=20packages/backe?= =?UTF-8?q?nd/src/services/following/reject.ts,=20packages/backend/src/ser?= =?UTF-8?q?vices/following/requests/accept.ts,=20packages/backend/src/serv?= =?UTF-8?q?ices/following/requests/cancel.ts,=20packages/backend/src/servi?= =?UTF-8?q?ces/following/requests/create.ts,=20packages/backend/src/servic?= =?UTF-8?q?es/i/pin.ts,=20packages/backend/src/services/logger.ts,=20packa?= =?UTF-8?q?ges/backend/src/services/messages/delete.ts,=20packages/backend?= =?UTF-8?q?/src/services/note/create.ts,=20packages/backend/src/services/n?= =?UTF-8?q?ote/delete.ts,=20packages/backend/src/services/note/reaction/cr?= =?UTF-8?q?eate.ts,=20packages/backend/src/services/note/reaction/delete.t?= =?UTF-8?q?s,=20packages/client/package.json,=20packages/client/src/compon?= =?UTF-8?q?ents/MkAbuseReport.vue,=20packages/client/src/components/MkAbus?= =?UTF-8?q?eReportWindow.vue,=20packages/client/src/components/MkAnnouncem?= =?UTF-8?q?ent.vue,=20packages/client/src/components/MkAutocomplete.vue,?= =?UTF-8?q?=20packages/client/src/components/MkAvatars.vue,=20packages/cli?= =?UTF-8?q?ent/src/components/MkButton.vue,=20packages/client/src/componen?= =?UTF-8?q?ts/MkChannelFollowButton.vue,=20packages/client/src/components/?= =?UTF-8?q?MkChannelPreview.vue,=20packages/client/src/components/MkChart.?= =?UTF-8?q?vue,=20packages/client/src/components/MkChartTooltip.vue,=20pac?= =?UTF-8?q?kages/client/src/components/MkChatPreview.vue,=20packages/clien?= =?UTF-8?q?t/src/components/MkContainer.vue,=20packages/client/src/compone?= =?UTF-8?q?nts/MkCropperDialog.vue,=20packages/client/src/components/MkCwB?= =?UTF-8?q?utton.vue,=20packages/client/src/components/MkDateSeparatedList?= =?UTF-8?q?.vue,=20packages/client/src/components/MkDialog.vue,=20packages?= =?UTF-8?q?/client/src/components/MkDonation.vue,=20packages/client/src/co?= =?UTF-8?q?mponents/MkDrive.file.vue,=20packages/client/src/components/MkD?= =?UTF-8?q?rive.folder.vue,=20packages/client/src/components/MkDrive.navFo?= =?UTF-8?q?lder.vue,=20packages/client/src/components/MkDrive.vue,=20packa?= =?UTF-8?q?ges/client/src/components/MkDriveFileThumbnail.vue,=20packages/?= =?UTF-8?q?client/src/components/MkDriveSelectDialog.vue,=20packages/clien?= =?UTF-8?q?t/src/components/MkEmojiPicker.section.vue,=20packages/client/s?= =?UTF-8?q?rc/components/MkEmojiPicker.vue,=20packages/client/src/componen?= =?UTF-8?q?ts/MkEmojiPickerDialog.vue,=20packages/client/src/components/Mk?= =?UTF-8?q?FileListForAdmin.vue,=20packages/client/src/components/MkFolder?= =?UTF-8?q?.vue,=20packages/client/src/components/MkFollowButton.vue,=20pa?= =?UTF-8?q?ckages/client/src/components/MkForgotPassword.vue,=20packages/c?= =?UTF-8?q?lient/src/components/MkGalleryPostPreview.vue,=20packages/clien?= =?UTF-8?q?t/src/components/MkImageViewer.vue,=20packages/client/src/compo?= =?UTF-8?q?nents/MkImgWithBlurhash.vue,=20packages/client/src/components/M?= =?UTF-8?q?kInfo.vue,=20packages/client/src/components/MkInstanceCardMini.?= =?UTF-8?q?vue,=20packages/client/src/components/MkInstanceSelectDialog.vu?= =?UTF-8?q?e,=20packages/client/src/components/MkInstanceStats.vue,=20pack?= =?UTF-8?q?ages/client/src/components/MkInstanceTicker.vue,=20packages/cli?= =?UTF-8?q?ent/src/components/MkKeyValue.vue,=20packages/client/src/compon?= =?UTF-8?q?ents/MkLaunchPad.vue,=20packages/client/src/components/MkLink.v?= =?UTF-8?q?ue,=20packages/client/src/components/MkManyAnnouncements.vue,?= =?UTF-8?q?=20packages/client/src/components/MkMedia.vue,=20packages/clien?= =?UTF-8?q?t/src/components/MkMediaBanner.vue,=20packages/client/src/compo?= =?UTF-8?q?nents/MkMediaCaption.vue,=20packages/client/src/components/MkMe?= =?UTF-8?q?diaList.vue,=20packages/client/src/components/MkMention.vue,=20?= =?UTF-8?q?packages/client/src/components/MkMenu.child.vue,=20packages/cli?= =?UTF-8?q?ent/src/components/MkMenu.vue,=20packages/client/src/components?= =?UTF-8?q?/MkModPlayer.vue,=20packages/client/src/components/MkModal.vue,?= =?UTF-8?q?=20packages/client/src/components/MkModalPageWindow.vue,=20pack?= =?UTF-8?q?ages/client/src/components/MkModalWindow.vue,=20packages/client?= =?UTF-8?q?/src/components/MkMoved.vue,=20packages/client/src/components/M?= =?UTF-8?q?kNote.vue,=20packages/client/src/components/MkNoteDetailed.vue,?= =?UTF-8?q?=20packages/client/src/components/MkNotePreview.vue,=20packages?= =?UTF-8?q?/client/src/components/MkNoteSimple.vue,=20packages/client/src/?= =?UTF-8?q?components/MkNoteSub.vue,=20packages/client/src/components/MkNo?= =?UTF-8?q?tification.vue,=20packages/client/src/components/MkNotification?= =?UTF-8?q?Folded.vue,=20packages/client/src/components/MkNotificationToas?= =?UTF-8?q?t.vue,=20packages/client/src/components/MkObjectView.value.vue,?= =?UTF-8?q?=20packages/client/src/components/MkPagePreview.vue,=20packages?= =?UTF-8?q?/client/src/components/MkPageWindow.vue,=20packages/client/src/?= =?UTF-8?q?components/MkPagination.vue,=20packages/client/src/components/M?= =?UTF-8?q?kPoll.vue,=20packages/client/src/components/MkPollEditor.vue,?= =?UTF-8?q?=20packages/client/src/components/MkPopupMenu.vue,=20packages/c?= =?UTF-8?q?lient/src/components/MkPostForm.vue,=20packages/client/src/comp?= =?UTF-8?q?onents/MkPostFormAttaches.vue,=20packages/client/src/components?= =?UTF-8?q?/MkPostSearch.vue,=20packages/client/src/components/MkPullToRef?= =?UTF-8?q?resh.vue,=20packages/client/src/components/MkQrCode.vue,=20pack?= =?UTF-8?q?ages/client/src/components/MkQuoteButton.vue,=20packages/client?= =?UTF-8?q?/src/components/MkReactedUsers.vue,=20packages/client/src/compo?= =?UTF-8?q?nents/MkReactionTooltip.vue,=20packages/client/src/components/M?= =?UTF-8?q?kReactionsViewer.details.vue,=20packages/client/src/components/?= =?UTF-8?q?MkReactionsViewer.reaction.vue,=20packages/client/src/component?= =?UTF-8?q?s/MkReactionsViewer.vue,=20packages/client/src/components/MkRem?= =?UTF-8?q?oteCaution.vue,=20packages/client/src/components/MkRipple.vue,?= =?UTF-8?q?=20packages/client/src/components/MkSearchBar.vue,=20packages/c?= =?UTF-8?q?lient/src/components/MkShowMoreButton.vue,=20packages/client/sr?= =?UTF-8?q?c/components/MkSignin.vue,=20packages/client/src/components/MkS?= =?UTF-8?q?ignup.vue,=20packages/client/src/components/MkSimpleTextWindow.?= =?UTF-8?q?vue,=20packages/client/src/components/MkSparkle.vue,=20packages?= =?UTF-8?q?/client/src/components/MkSuperMenu.vue,=20packages/client/src/c?= =?UTF-8?q?omponents/MkTab.vue,=20packages/client/src/components/MkTagClou?= =?UTF-8?q?d.vue,=20packages/client/src/components/MkTimeline.vue,=20packa?= =?UTF-8?q?ges/client/src/components/MkToast.vue,=20packages/client/src/co?= =?UTF-8?q?mponents/MkTokenGenerateWindow.vue,=20packages/client/src/compo?= =?UTF-8?q?nents/MkTooltip.vue,=20packages/client/src/components/MkTutoria?= =?UTF-8?q?lDialog.vue,=20packages/client/src/components/MkUpdated.vue,=20?= =?UTF-8?q?packages/client/src/components/MkUrlPreview.vue,=20packages/cli?= =?UTF-8?q?ent/src/components/MkUrlPreviewPopup.vue,=20packages/client/src?= =?UTF-8?q?/components/MkUserCardMini.vue,=20packages/client/src/component?= =?UTF-8?q?s/MkUserInfo.vue,=20packages/client/src/components/MkUserPrevie?= =?UTF-8?q?w.vue,=20packages/client/src/components/MkUserSelectDialog.vue,?= =?UTF-8?q?=20packages/client/src/components/MkUserSelectLocalDialog.vue,?= =?UTF-8?q?=20packages/client/src/components/MkUsersTooltip.vue,=20package?= =?UTF-8?q?s/client/src/components/MkVisibility.vue,=20packages/client/src?= =?UTF-8?q?/components/MkVisibilityPicker.vue,=20packages/client/src/compo?= =?UTF-8?q?nents/MkWaitingDialog.vue,=20packages/client/src/components/MkW?= =?UTF-8?q?idgets.vue,=20packages/client/src/components/MkWindow.vue,=20pa?= =?UTF-8?q?ckages/client/src/components/form/checkbox.vue,=20packages/clie?= =?UTF-8?q?nt/src/components/form/folder.vue,=20packages/client/src/compon?= =?UTF-8?q?ents/form/input.vue,=20packages/client/src/components/form/link?= =?UTF-8?q?.vue,=20packages/client/src/components/form/radio.vue,=20packag?= =?UTF-8?q?es/client/src/components/form/radios.vue,=20packages/client/src?= =?UTF-8?q?/components/form/range.vue,=20packages/client/src/components/fo?= =?UTF-8?q?rm/section.vue,=20packages/client/src/components/form/select.vu?= =?UTF-8?q?e,=20packages/client/src/components/form/slot.vue,=20packages/c?= =?UTF-8?q?lient/src/components/form/suspense.vue,=20packages/client/src/c?= =?UTF-8?q?omponents/form/switch.vue,=20packages/client/src/components/for?= =?UTF-8?q?m/textarea.vue,=20packages/client/src/components/global/MkAd.vu?= =?UTF-8?q?e,=20packages/client/src/components/global/MkAvatar.vue,=20pack?= =?UTF-8?q?ages/client/src/components/global/MkEmoji.vue,=20packages/clien?= =?UTF-8?q?t/src/components/global/MkError.vue,=20packages/client/src/comp?= =?UTF-8?q?onents/global/MkLoading.vue,=20packages/client/src/components/g?= =?UTF-8?q?lobal/MkMisskeyFlavoredMarkdown.vue,=20packages/client/src/comp?= =?UTF-8?q?onents/global/MkPageHeader.vue,=20packages/client/src/component?= =?UTF-8?q?s/global/MkSpacer.vue,=20packages/client/src/components/global/?= =?UTF-8?q?MkStickyContainer.vue,=20packages/client/src/components/global/?= =?UTF-8?q?MkUrl.vue,=20packages/client/src/components/note/MkNoteContent.?= =?UTF-8?q?vue,=20packages/client/src/components/note/MkNoteFooter.vue,=20?= =?UTF-8?q?packages/client/src/components/note/MkNoteFooterInfo.vue,=20pac?= =?UTF-8?q?kages/client/src/components/note/MkNoteHeader.vue,=20packages/c?= =?UTF-8?q?lient/src/components/note/MkNoteHeaderInfo.vue,=20packages/clie?= =?UTF-8?q?nt/src/components/note/MkNoteMedia.vue,=20packages/client/src/c?= =?UTF-8?q?omponents/note/MkNoteTranslation.vue,=20packages/client/src/com?= =?UTF-8?q?ponents/note/MkRenoteBar.vue,=20packages/client/src/components/?= =?UTF-8?q?page/page.button.vue,=20packages/client/src/components/page/pag?= =?UTF-8?q?e.canvas.vue,=20packages/client/src/components/page/page.counte?= =?UTF-8?q?r.vue,=20packages/client/src/components/page/page.note.vue,=20p?= =?UTF-8?q?ackages/client/src/components/page/page.number-input.vue,=20pac?= =?UTF-8?q?kages/client/src/components/page/page.post.vue,=20packages/clie?= =?UTF-8?q?nt/src/components/page/page.section.vue,=20packages/client/src/?= =?UTF-8?q?components/page/page.switch.vue,=20packages/client/src/componen?= =?UTF-8?q?ts/page/page.text-input.vue,=20packages/client/src/components/p?= =?UTF-8?q?age/page.text.vue,=20packages/client/src/init.ts,=20packages/cl?= =?UTF-8?q?ient/src/navbar.ts,=20packages/client/src/pages/=5Ferror=5F.vue?= =?UTF-8?q?,=20packages/client/src/pages/about-firefish.vue,=20packages/cl?= =?UTF-8?q?ient/src/pages/about.emojis.vue,=20packages/client/src/pages/ab?= =?UTF-8?q?out.federation.vue,=20packages/client/src/pages/about.vue,=20pa?= =?UTF-8?q?ckages/client/src/pages/admin-file.vue,=20packages/client/src/p?= =?UTF-8?q?ages/admin/=5Fheader=5F.vue,=20packages/client/src/pages/admin/?= =?UTF-8?q?abuses.vue,=20packages/client/src/pages/admin/emoji-edit-dialog?= =?UTF-8?q?.vue,=20packages/client/src/pages/admin/emojis.vue,=20packages/?= =?UTF-8?q?client/src/pages/admin/files.vue,=20packages/client/src/pages/a?= =?UTF-8?q?dmin/index.vue,=20packages/client/src/pages/admin/overview.fede?= =?UTF-8?q?ration.vue,=20packages/client/src/pages/admin/overview.metrics.?= =?UTF-8?q?vue,=20packages/client/src/pages/admin/overview.moderators.vue,?= =?UTF-8?q?=20packages/client/src/pages/admin/overview.queue.vue,=20packag?= =?UTF-8?q?es/client/src/pages/admin/overview.stats.vue,=20packages/client?= =?UTF-8?q?/src/pages/admin/overview.user.vue,=20packages/client/src/pages?= =?UTF-8?q?/admin/promotions.vue,=20packages/client/src/pages/admin/queue.?= =?UTF-8?q?chart.vue,=20packages/client/src/pages/admin/relays.vue,=20pack?= =?UTF-8?q?ages/client/src/pages/admin/settings.vue,=20packages/client/src?= =?UTF-8?q?/pages/admin/users.vue,=20packages/client/src/pages/announcemen?= =?UTF-8?q?ts.vue,=20packages/client/src/pages/channel-editor.vue,=20packa?= =?UTF-8?q?ges/client/src/pages/channel.vue,=20packages/client/src/pages/c?= =?UTF-8?q?hannels.vue,=20packages/client/src/pages/clip.vue,=20packages/c?= =?UTF-8?q?lient/src/pages/emojis.emoji.vue,=20packages/client/src/pages/e?= =?UTF-8?q?xplore.featured.vue,=20packages/client/src/pages/explore.users.?= =?UTF-8?q?vue,=20packages/client/src/pages/explore.vue,=20packages/client?= =?UTF-8?q?/src/pages/follow-requests-sent.vue,=20packages/client/src/page?= =?UTF-8?q?s/follow-requests.vue,=20packages/client/src/pages/gallery/edit?= =?UTF-8?q?.vue,=20packages/client/src/pages/gallery/index.vue,=20packages?= =?UTF-8?q?/client/src/pages/gallery/post.vue,=20packages/client/src/pages?= =?UTF-8?q?/instance-info.vue,=20packages/client/src/pages/messaging/index?= =?UTF-8?q?.vue,=20packages/client/src/pages/messaging/messaging-room.form?= =?UTF-8?q?.vue,=20packages/client/src/pages/messaging/messaging-room.mess?= =?UTF-8?q?age.vue,=20packages/client/src/pages/messaging/messaging-room.v?= =?UTF-8?q?ue,=20packages/client/src/pages/mfm-cheat-sheet.vue,=20packages?= =?UTF-8?q?/client/src/pages/miauth.vue,=20packages/client/src/pages/my-an?= =?UTF-8?q?tennas/editor.vue,=20packages/client/src/pages/my-antennas/inde?= =?UTF-8?q?x.vue,=20packages/client/src/pages/my-clips/index.vue,=20packag?= =?UTF-8?q?es/client/src/pages/my-groups/group.vue,=20packages/client/src/?= =?UTF-8?q?pages/my-groups/index.vue,=20packages/client/src/pages/my-lists?= =?UTF-8?q?/index.vue,=20packages/client/src/pages/my-lists/list.vue,=20pa?= =?UTF-8?q?ckages/client/src/pages/no-graze.vue,=20packages/client/src/pag?= =?UTF-8?q?es/note.vue,=20packages/client/src/pages/notifications.vue,=20p?= =?UTF-8?q?ackages/client/src/pages/oauth.vue,=20packages/client/src/pages?= =?UTF-8?q?/page-editor/els/page-editor.el.button.vue,=20packages/client/s?= =?UTF-8?q?rc/pages/page-editor/els/page-editor.el.if.vue,=20packages/clie?= =?UTF-8?q?nt/src/pages/page-editor/els/page-editor.el.image.vue,=20packag?= =?UTF-8?q?es/client/src/pages/page-editor/els/page-editor.el.note.vue,=20?= =?UTF-8?q?packages/client/src/pages/page-editor/els/page-editor.el.switch?= =?UTF-8?q?.vue,=20packages/client/src/pages/page-editor/els/page-editor.e?= =?UTF-8?q?l.text.vue,=20packages/client/src/pages/page-editor/els/page-ed?= =?UTF-8?q?itor.el.textarea.vue,=20packages/client/src/pages/page-editor/p?= =?UTF-8?q?age-editor.container.vue,=20packages/client/src/pages/page-edit?= =?UTF-8?q?or/page-editor.script-block.vue,=20packages/client/src/pages/pa?= =?UTF-8?q?ge-editor/page-editor.vue,=20packages/client/src/pages/page.vue?= =?UTF-8?q?,=20packages/client/src/pages/pages.vue,=20packages/client/src/?= =?UTF-8?q?pages/scratchpad.vue,=20packages/client/src/pages/search.vue,?= =?UTF-8?q?=20packages/client/src/pages/settings/2fa.qrdialog.vue,=20packa?= =?UTF-8?q?ges/client/src/pages/settings/2fa.vue,=20packages/client/src/pa?= =?UTF-8?q?ges/settings/accounts.vue,=20packages/client/src/pages/settings?= =?UTF-8?q?/apps.vue,=20packages/client/src/pages/settings/deck.vue,=20pac?= =?UTF-8?q?kages/client/src/pages/settings/drive.vue,=20packages/client/sr?= =?UTF-8?q?c/pages/settings/general.vue,=20packages/client/src/pages/setti?= =?UTF-8?q?ngs/import-export.vue,=20packages/client/src/pages/settings/ind?= =?UTF-8?q?ex.vue,=20packages/client/src/pages/settings/instance-mute.vue,?= =?UTF-8?q?=20packages/client/src/pages/settings/migration.vue,=20packages?= =?UTF-8?q?/client/src/pages/settings/mute-block.vue,=20packages/client/sr?= =?UTF-8?q?c/pages/settings/navbar.vue,=20packages/client/src/pages/settin?= =?UTF-8?q?gs/other.vue,=20packages/client/src/pages/settings/plugin.vue,?= =?UTF-8?q?=20packages/client/src/pages/settings/profile.vue,=20packages/c?= =?UTF-8?q?lient/src/pages/settings/reaction.vue,=20packages/client/src/pa?= =?UTF-8?q?ges/settings/security.vue,=20packages/client/src/pages/settings?= =?UTF-8?q?/sounds.vue,=20packages/client/src/pages/settings/statusbar.vue?= =?UTF-8?q?,=20packages/client/src/pages/settings/theme.vue,=20packages/cl?= =?UTF-8?q?ient/src/pages/settings/word-mute.vue,=20packages/client/src/pa?= =?UTF-8?q?ges/share.vue,=20packages/client/src/pages/tag.vue,=20packages/?= =?UTF-8?q?client/src/pages/theme-editor.vue,=20packages/client/src/pages/?= =?UTF-8?q?timeline.vue,=20packages/client/src/pages/user-info.vue,=20pack?= =?UTF-8?q?ages/client/src/pages/user-list-timeline.vue,=20packages/client?= =?UTF-8?q?/src/pages/user/clips.vue,=20packages/client/src/pages/user/hom?= =?UTF-8?q?e.vue,=20packages/client/src/pages/user/index.photos.vue,=20pac?= =?UTF-8?q?kages/client/src/pages/user/index.timeline.vue,=20packages/clie?= =?UTF-8?q?nt/src/pages/user/index.vue,=20packages/client/src/pages/user/m?= =?UTF-8?q?edia-list.vue,=20packages/client/src/pages/user/reactions.vue,?= =?UTF-8?q?=20packages/client/src/pages/welcome.entrance.a.vue,=20packages?= =?UTF-8?q?/client/src/pages/welcome.entrance.b.vue,=20packages/client/src?= =?UTF-8?q?/pages/welcome.entrance.c.vue,=20packages/client/src/pages/welc?= =?UTF-8?q?ome.setup.vue,=20packages/client/src/pages/welcome.timeline.vue?= =?UTF-8?q?,=20packages/client/src/scripts/get-note-menu.ts,=20packages/cl?= =?UTF-8?q?ient/src/scripts/get-user-menu.ts,=20packages/client/src/store.?= =?UTF-8?q?ts,=20packages/client/src/style.scss,=20packages/client/src/ui/?= =?UTF-8?q?=5Fcommon=5F/common.vue,=20packages/client/src/ui/=5Fcommon=5F/?= =?UTF-8?q?navbar-for-mobile.vue,=20packages/client/src/ui/=5Fcommon=5F/na?= =?UTF-8?q?vbar.vue,=20packages/client/src/ui/=5Fcommon=5F/statusbar-feder?= =?UTF-8?q?ation.vue,=20packages/client/src/ui/=5Fcommon=5F/statusbar-rss.?= =?UTF-8?q?vue,=20packages/client/src/ui/=5Fcommon=5F/statusbar-user-list.?= =?UTF-8?q?vue,=20packages/client/src/ui/=5Fcommon=5F/statusbars.vue,=20pa?= =?UTF-8?q?ckages/client/src/ui/=5Fcommon=5F/stream-indicator.vue,=20packa?= =?UTF-8?q?ges/client/src/ui/=5Fcommon=5F/upload.vue,=20packages/client/sr?= =?UTF-8?q?c/ui/deck.vue,=20packages/client/src/ui/deck/antenna-column.vue?= =?UTF-8?q?,=20packages/client/src/ui/deck/channel-column.vue,=20packages/?= =?UTF-8?q?client/src/ui/deck/column.vue,=20packages/client/src/ui/deck/di?= =?UTF-8?q?rect-column.vue,=20packages/client/src/ui/deck/list-column.vue,?= =?UTF-8?q?=20packages/client/src/ui/deck/mentions-column.vue,=20packages/?= =?UTF-8?q?client/src/ui/deck/notifications-column.vue,=20packages/client/?= =?UTF-8?q?src/ui/deck/tl-column.vue,=20packages/client/src/ui/deck/widget?= =?UTF-8?q?s-column.vue,=20packages/client/src/ui/universal.vue,=20package?= =?UTF-8?q?s/client/src/ui/universal.widgets.vue,=20packages/client/src/ui?= =?UTF-8?q?/visitor/a.vue,=20packages/client/src/ui/visitor/b.vue,=20packa?= =?UTF-8?q?ges/client/src/ui/visitor/header.vue,=20packages/client/src/ui/?= =?UTF-8?q?visitor/kanban.vue,=20packages/client/src/ui/zen.vue,=20package?= =?UTF-8?q?s/client/src/widgets/aiscript.vue,=20packages/client/src/widget?= =?UTF-8?q?s/calendar.vue,=20packages/client/src/widgets/clock.vue,=20pack?= =?UTF-8?q?ages/client/src/widgets/digital-clock.vue,=20packages/client/sr?= =?UTF-8?q?c/widgets/federation.vue,=20packages/client/src/widgets/instanc?= =?UTF-8?q?e-cloud.vue,=20packages/client/src/widgets/job-queue.vue,=20pac?= =?UTF-8?q?kages/client/src/widgets/memo.vue,=20packages/client/src/widget?= =?UTF-8?q?s/notifications.vue,=20packages/client/src/widgets/online-users?= =?UTF-8?q?.vue,=20packages/client/src/widgets/photos.vue,=20packages/clie?= =?UTF-8?q?nt/src/widgets/rss-ticker.vue,=20packages/client/src/widgets/rs?= =?UTF-8?q?s.vue,=20packages/client/src/widgets/server-info.vue,=20package?= =?UTF-8?q?s/client/src/widgets/server-metric/cpu-mem.vue,=20packages/clie?= =?UTF-8?q?nt/src/widgets/server-metric/cpu.vue,=20packages/client/src/wid?= =?UTF-8?q?gets/server-metric/disk.vue,=20packages/client/src/widgets/serv?= =?UTF-8?q?er-metric/mem.vue,=20packages/client/src/widgets/server-metric/?= =?UTF-8?q?pie.vue,=20packages/client/src/widgets/slideshow.vue,=20package?= =?UTF-8?q?s/client/src/widgets/timeline.vue,=20packages/client/src/widget?= =?UTF-8?q?s/trends.vue,=20packages/client/src/widgets/unix-clock.vue,=20p?= =?UTF-8?q?ackages/client/vite.config.ts,=20packages/firefish-js/package.j?= =?UTF-8?q?son,=20packages/firefish-js/src/index.ts,=20packages/firefish-j?= =?UTF-8?q?s/src/misc/langmap.ts,=20packages/macro-rs/macros-impl/src/lib.?= =?UTF-8?q?rs,=20packages/macro-rs/macros/src/lib.rs,=20packages/sw/packag?= =?UTF-8?q?e.json,=20packages/sw/src/scripts/notification-read.ts,=20packa?= =?UTF-8?q?ges/sw/vite.config.ts,=20pnpm-lock.yaml)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 28 +- .gitlab/issue_templates/default.md | 99 ++ .gitlab/issue_templates/discussion.md | 82 ++ .gitlab/issue_templates/feature.md | 67 + .gitlab/issue_templates/refactor.md | 67 + .gitlab/merge_request_templates/default.md | 18 + .gitlab/merge_request_templates/release.md | 18 + Cargo.lock | 378 ++++-- Cargo.toml | 21 +- Dockerfile | 1 - dev/container/docker-compose.yml | 2 +- dev/docs/local-installation.md | 2 +- docs/changelog.md | 13 + docs/downgrade.sql | 4 + docs/install.md | 2 +- docs/notice-for-admins.md | 8 +- locales/ca-ES.yml | 23 +- locales/en-US.yml | 73 +- locales/eo.yml | 29 + locales/ja-JP.yml | 19 +- locales/ko-KR.yml | 144 ++- locales/zh-CN.yml | 30 +- locales/zh-TW.yml | 9 +- package.json | 14 +- packages/backend-rs/Cargo.toml | 5 +- packages/backend-rs/index.d.ts | 146 ++- packages/backend-rs/index.js | 14 +- packages/backend-rs/package.json | 3 +- packages/backend-rs/src/cache/bare.rs | 199 +++ packages/backend-rs/src/cache/mod.rs | 7 + packages/backend-rs/src/cache/redis.rs | 332 +++++ packages/backend-rs/src/config/meta.rs | 23 +- packages/backend-rs/src/database/mod.rs | 1 - .../federation/activitypub/object/accept.rs | 29 + .../src/federation/activitypub/object/add.rs | 34 + .../federation/activitypub/object/emoji.rs | 48 + .../src/federation/activitypub/object/flag.rs | 36 + .../federation/activitypub/object/follow.rs | 70 ++ .../federation/activitypub/object/hashtag.rs | 27 + .../src/federation/activitypub/object/like.rs | 74 ++ .../federation/activitypub/object/mention.rs | 40 + .../src/federation/activitypub/object/mod.rs | 45 +- .../src/federation/activitypub/object/read.rs | 27 + .../federation/activitypub/object/reject.rs | 29 + .../federation/activitypub/object/remove.rs | 34 + .../activitypub/object/tombstone.rs | 25 + .../src/federation/internal_actor/instance.rs | 15 +- .../src/federation/internal_actor/relay.rs | 6 +- .../src/federation/nodeinfo/fetch.rs | 2 +- .../src/federation/nodeinfo/generate.rs | 38 +- packages/backend-rs/src/init/system_info.rs | 26 +- packages/backend-rs/src/lib.rs | 1 + packages/backend-rs/src/misc/emoji/mod.rs | 9 + .../backend-rs/src/misc/emoji/reaction.rs | 198 +++ packages/backend-rs/src/misc/emoji/unicode.rs | 6 + .../backend-rs/src/misc/get_image_size.rs | 17 +- .../backend-rs/src/misc/latest_version.rs | 63 +- packages/backend-rs/src/misc/mod.rs | 2 +- packages/backend-rs/src/misc/note/mod.rs | 7 + packages/backend-rs/src/misc/random_icon.rs | 43 + packages/backend-rs/src/misc/should_nyaify.rs | 19 +- packages/backend-rs/src/misc/system_info.rs | 55 +- packages/backend-rs/src/misc/translate.rs | 2 +- packages/backend-rs/src/misc/user/mod.rs | 29 + .../src/model/entity/abuse_user_report.rs | 2 +- .../src/model/entity/access_token.rs | 2 +- packages/backend-rs/src/model/entity/ad.rs | 2 +- .../src/model/entity/announcement.rs | 2 +- .../src/model/entity/announcement_read.rs | 2 +- .../backend-rs/src/model/entity/antenna.rs | 2 +- packages/backend-rs/src/model/entity/app.rs | 2 +- .../src/model/entity/attestation_challenge.rs | 2 +- .../src/model/entity/auth_session.rs | 2 +- .../backend-rs/src/model/entity/blocking.rs | 2 +- .../backend-rs/src/model/entity/channel.rs | 2 +- .../src/model/entity/channel_following.rs | 2 +- .../src/model/entity/channel_note_pining.rs | 2 +- packages/backend-rs/src/model/entity/clip.rs | 2 +- .../backend-rs/src/model/entity/clip_note.rs | 2 +- .../backend-rs/src/model/entity/drive_file.rs | 8 +- .../src/model/entity/drive_folder.rs | 2 +- packages/backend-rs/src/model/entity/emoji.rs | 2 +- .../src/model/entity/follow_request.rs | 2 +- .../backend-rs/src/model/entity/following.rs | 2 +- .../src/model/entity/gallery_like.rs | 2 +- .../src/model/entity/gallery_post.rs | 2 +- .../backend-rs/src/model/entity/hashtag.rs | 3 +- .../backend-rs/src/model/entity/instance.rs | 3 +- .../src/model/entity/messaging_message.rs | 2 +- packages/backend-rs/src/model/entity/meta.rs | 2 +- .../backend-rs/src/model/entity/migrations.rs | 2 +- packages/backend-rs/src/model/entity/mod.rs | 2 +- .../src/model/entity/moderation_log.rs | 2 +- .../backend-rs/src/model/entity/muted_note.rs | 2 +- .../backend-rs/src/model/entity/muting.rs | 2 +- packages/backend-rs/src/model/entity/note.rs | 3 +- .../backend-rs/src/model/entity/note_edit.rs | 2 +- .../src/model/entity/note_favorite.rs | 2 +- .../backend-rs/src/model/entity/note_file.rs | 2 +- .../src/model/entity/note_reaction.rs | 2 +- .../src/model/entity/note_thread_muting.rs | 2 +- .../src/model/entity/note_unread.rs | 2 +- .../src/model/entity/note_watching.rs | 2 +- .../src/model/entity/notification.rs | 2 +- packages/backend-rs/src/model/entity/page.rs | 2 +- .../backend-rs/src/model/entity/page_like.rs | 2 +- .../model/entity/password_reset_request.rs | 3 +- packages/backend-rs/src/model/entity/poll.rs | 2 +- .../backend-rs/src/model/entity/poll_vote.rs | 2 +- .../backend-rs/src/model/entity/prelude.rs | 2 +- .../backend-rs/src/model/entity/promo_note.rs | 2 +- .../backend-rs/src/model/entity/promo_read.rs | 2 +- .../src/model/entity/registration_ticket.rs | 3 +- .../src/model/entity/registry_item.rs | 2 +- packages/backend-rs/src/model/entity/relay.rs | 3 +- .../src/model/entity/renote_muting.rs | 2 +- .../src/model/entity/reply_muting.rs | 2 +- .../src/model/entity/sea_orm_active_enums.rs | 2 +- .../backend-rs/src/model/entity/signin.rs | 2 +- .../src/model/entity/sw_subscription.rs | 2 +- .../src/model/entity/used_username.rs | 2 +- packages/backend-rs/src/model/entity/user.rs | 2 +- .../backend-rs/src/model/entity/user_group.rs | 2 +- .../src/model/entity/user_group_invitation.rs | 2 +- .../src/model/entity/user_group_invite.rs | 2 +- .../src/model/entity/user_group_joining.rs | 2 +- .../backend-rs/src/model/entity/user_ip.rs | 2 +- .../src/model/entity/user_keypair.rs | 2 +- .../backend-rs/src/model/entity/user_list.rs | 2 +- .../src/model/entity/user_list_joining.rs | 2 +- .../src/model/entity/user_note_pining.rs | 2 +- .../src/model/entity/user_pending.rs | 3 +- .../src/model/entity/user_profile.rs | 2 +- .../src/model/entity/user_publickey.rs | 4 +- .../src/model/entity/user_security_key.rs | 2 +- .../backend-rs/src/model/entity/webhook.rs | 2 +- .../src/service/antenna/check_hit.rs | 18 +- .../backend-rs/src/service/antenna/mod.rs | 22 +- .../src/service/antenna/process_new_note.rs | 23 +- .../backend-rs/src/service/antenna/update.rs | 2 +- .../src/service/push_notification.rs | 6 +- packages/backend-rs/src/service/stream.rs | 6 +- packages/backend-rs/src/util/id.rs | 7 +- packages/backend/package.json | 46 +- packages/backend/src/boot/master.ts | 9 +- .../1644010796173-convert-hard-mutes.ts | 8 +- .../1652859567549-uniform-themecolor.ts | 2 +- .../1722346019160-set-emoji-public-url.ts | 18 + packages/backend/src/models/entities/emoji.ts | 1 - packages/backend/src/models/entities/note.ts | 1 - .../src/models/entities/user-profile.ts | 1 - packages/backend/src/models/entities/user.ts | 1 - .../src/models/repositories/renote-muting.ts | 4 +- .../src/models/repositories/reply-muting.ts | 4 +- .../backend/src/models/repositories/user.ts | 1 + .../processors/db/import-firefish-post.ts | 2 +- .../queue/processors/db/import-masto-post.ts | 2 +- .../src/remote/activitypub/kernel/like.ts | 6 +- .../src/remote/activitypub/misc/contexts.ts | 1 - .../src/remote/activitypub/models/person.ts | 8 +- .../src/remote/activitypub/renderer/note.ts | 4 +- .../src/remote/activitypub/renderer/person.ts | 5 +- .../backend/src/remote/activitypub/request.ts | 2 +- .../src/remote/activitypub/resolver.ts | 6 +- .../backend/src/remote/activitypub/type.ts | 2 +- packages/backend/src/server/activitypub.ts | 14 +- .../src/server/api/common/inject-featured.ts | 1 - .../api/common/read-messaging-message.ts | 7 +- .../admin/resolve-abuse-user-report.ts | 7 +- .../server/api/endpoints/notes/featured.ts | 1 - .../src/server/api/mastodon/helpers/misc.ts | 1 - .../src/server/file/byte-range-readable.ts | 2 +- packages/backend/src/server/index.ts | 5 +- packages/backend/src/server/web/bios.css | 55 +- packages/backend/src/server/web/cli.css | 20 +- packages/backend/src/server/web/style.css | 45 +- .../backend/src/services/blocking/create.ts | 6 +- .../src/services/fetch-instance-metadata.ts | 2 +- .../backend/src/services/following/create.ts | 10 +- .../backend/src/services/following/delete.ts | 8 +- .../backend/src/services/following/reject.ts | 8 +- .../src/services/following/requests/accept.ts | 11 +- .../src/services/following/requests/cancel.ts | 3 +- .../src/services/following/requests/create.ts | 3 +- packages/backend/src/services/i/pin.ts | 11 +- packages/backend/src/services/logger.ts | 2 +- .../backend/src/services/messages/delete.ts | 13 +- packages/backend/src/services/note/create.ts | 9 +- packages/backend/src/services/note/delete.ts | 10 +- .../src/services/note/reaction/create.ts | 4 +- .../src/services/note/reaction/delete.ts | 10 +- packages/client/package.json | 25 +- .../client/src/components/MkAbuseReport.vue | 15 +- .../src/components/MkAbuseReportWindow.vue | 2 +- .../client/src/components/MkAnnouncement.vue | 13 +- .../client/src/components/MkAutocomplete.vue | 50 +- packages/client/src/components/MkAvatars.vue | 4 +- packages/client/src/components/MkButton.vue | 45 +- .../src/components/MkChannelFollowButton.vue | 16 +- .../src/components/MkChannelPreview.vue | 47 +- packages/client/src/components/MkChart.vue | 8 +- .../client/src/components/MkChartTooltip.vue | 8 +- .../client/src/components/MkChatPreview.vue | 40 +- .../client/src/components/MkContainer.vue | 51 +- .../client/src/components/MkCropperDialog.vue | 20 +- packages/client/src/components/MkCwButton.vue | 16 +- .../src/components/MkDateSeparatedList.vue | 19 +- packages/client/src/components/MkDialog.vue | 25 +- packages/client/src/components/MkDonation.vue | 38 +- .../client/src/components/MkDrive.file.vue | 46 +- .../client/src/components/MkDrive.folder.vue | 26 +- .../src/components/MkDrive.navFolder.vue | 2 +- packages/client/src/components/MkDrive.vue | 39 +- .../src/components/MkDriveFileThumbnail.vue | 8 +- .../src/components/MkDriveSelectDialog.vue | 2 +- .../src/components/MkEmojiPicker.section.vue | 2 +- .../client/src/components/MkEmojiPicker.vue | 83 +- .../src/components/MkEmojiPickerDialog.vue | 4 +- .../src/components/MkFileListForAdmin.vue | 30 +- packages/client/src/components/MkFolder.vue | 32 +- .../client/src/components/MkFollowButton.vue | 31 +- .../src/components/MkForgotPassword.vue | 2 +- .../src/components/MkGalleryPostPreview.vue | 36 +- .../client/src/components/MkImageViewer.vue | 19 +- .../src/components/MkImgWithBlurhash.vue | 21 +- packages/client/src/components/MkInfo.vue | 10 +- .../src/components/MkInstanceCardMini.vue | 12 +- .../src/components/MkInstanceSelectDialog.vue | 27 +- .../client/src/components/MkInstanceStats.vue | 18 +- .../src/components/MkInstanceTicker.vue | 15 +- packages/client/src/components/MkKeyValue.vue | 16 +- .../client/src/components/MkLaunchPad.vue | 34 +- packages/client/src/components/MkLink.vue | 2 +- .../src/components/MkManyAnnouncements.vue | 9 +- packages/client/src/components/MkMedia.vue | 40 +- .../client/src/components/MkMediaBanner.vue | 11 +- .../client/src/components/MkMediaCaption.vue | 70 +- .../client/src/components/MkMediaList.vue | 48 +- packages/client/src/components/MkMention.vue | 16 +- .../client/src/components/MkMenu.child.vue | 52 +- packages/client/src/components/MkMenu.vue | 68 +- .../client/src/components/MkModPlayer.vue | 47 +- packages/client/src/components/MkModal.vue | 186 ++- .../src/components/MkModalPageWindow.vue | 28 +- .../client/src/components/MkModalWindow.vue | 24 +- packages/client/src/components/MkMoved.vue | 4 +- packages/client/src/components/MkNote.vue | 113 +- .../client/src/components/MkNoteDetailed.vue | 76 +- .../client/src/components/MkNotePreview.vue | 33 +- .../client/src/components/MkNoteSimple.vue | 31 +- packages/client/src/components/MkNoteSub.vue | 174 +-- .../client/src/components/MkNotification.vue | 40 +- .../src/components/MkNotificationFolded.vue | 24 +- .../src/components/MkNotificationToast.vue | 27 +- .../src/components/MkObjectView.value.vue | 8 +- .../client/src/components/MkPagePreview.vue | 46 +- .../client/src/components/MkPageWindow.vue | 6 +- .../client/src/components/MkPagination.vue | 8 +- packages/client/src/components/MkPoll.vue | 20 +- .../client/src/components/MkPollEditor.vue | 31 +- .../client/src/components/MkPopupMenu.vue | 4 +- packages/client/src/components/MkPostForm.vue | 366 +++--- .../src/components/MkPostFormAttaches.vue | 25 +- .../client/src/components/MkPostSearch.vue | 13 +- .../client/src/components/MkPullToRefresh.vue | 61 +- packages/client/src/components/MkQrCode.vue | 13 +- .../client/src/components/MkQuoteButton.vue | 7 +- .../client/src/components/MkReactedUsers.vue | 9 +- .../src/components/MkReactionTooltip.vue | 5 +- .../components/MkReactionsViewer.details.vue | 27 +- .../components/MkReactionsViewer.reaction.vue | 12 +- .../src/components/MkReactionsViewer.vue | 17 +- .../client/src/components/MkRemoteCaution.vue | 2 +- packages/client/src/components/MkRipple.vue | 4 +- .../client/src/components/MkSearchBar.vue | 12 +- .../src/components/MkShowMoreButton.vue | 18 +- packages/client/src/components/MkSignin.vue | 13 +- packages/client/src/components/MkSignup.vue | 5 +- .../src/components/MkSimpleTextWindow.vue | 14 +- packages/client/src/components/MkSparkle.vue | 4 +- .../client/src/components/MkSuperMenu.vue | 54 +- packages/client/src/components/MkTab.vue | 48 +- packages/client/src/components/MkTagCloud.vue | 4 +- packages/client/src/components/MkTimeline.vue | 29 +- packages/client/src/components/MkToast.vue | 20 +- .../src/components/MkTokenGenerateWindow.vue | 10 +- packages/client/src/components/MkTooltip.vue | 24 +- .../src/components/MkTutorialDialog.vue | 13 +- packages/client/src/components/MkUpdated.vue | 12 +- .../client/src/components/MkUrlPreview.vue | 46 +- .../src/components/MkUrlPreviewPopup.vue | 4 +- .../client/src/components/MkUserCardMini.vue | 16 +- packages/client/src/components/MkUserInfo.vue | 55 +- .../client/src/components/MkUserPreview.vue | 2 +- .../src/components/MkUserSelectDialog.vue | 44 +- .../components/MkUserSelectLocalDialog.vue | 20 +- .../client/src/components/MkUsersTooltip.vue | 10 +- .../client/src/components/MkVisibility.vue | 2 +- .../src/components/MkVisibilityPicker.vue | 45 +- .../client/src/components/MkWaitingDialog.vue | 8 +- packages/client/src/components/MkWidgets.vue | 21 +- packages/client/src/components/MkWindow.vue | 84 +- .../client/src/components/form/checkbox.vue | 17 +- .../client/src/components/form/folder.vue | 21 +- packages/client/src/components/form/input.vue | 45 +- packages/client/src/components/form/link.vue | 17 +- packages/client/src/components/form/radio.vue | 22 +- .../client/src/components/form/radios.vue | 10 +- packages/client/src/components/form/range.vue | 28 +- .../client/src/components/form/section.vue | 18 +- .../client/src/components/form/select.vue | 47 +- packages/client/src/components/form/slot.vue | 10 +- .../client/src/components/form/suspense.vue | 2 +- .../client/src/components/form/switch.vue | 27 +- .../client/src/components/form/textarea.vue | 25 +- .../client/src/components/global/MkAd.vue | 26 +- .../client/src/components/global/MkAvatar.vue | 25 +- .../client/src/components/global/MkEmoji.vue | 8 +- .../client/src/components/global/MkError.vue | 14 +- .../src/components/global/MkLoading.vue | 15 +- .../global/MkMisskeyFlavoredMarkdown.vue | 10 +- .../src/components/global/MkPageHeader.vue | 130 +- .../client/src/components/global/MkSpacer.vue | 8 +- .../components/global/MkStickyContainer.vue | 7 +- .../client/src/components/global/MkUrl.vue | 2 +- .../src/components/note/MkNoteContent.vue | 58 +- .../src/components/note/MkNoteFooter.vue | 23 +- .../src/components/note/MkNoteFooterInfo.vue | 2 +- .../src/components/note/MkNoteHeader.vue | 40 +- .../src/components/note/MkNoteHeaderInfo.vue | 16 +- .../src/components/note/MkNoteMedia.vue | 40 +- .../src/components/note/MkNoteTranslation.vue | 2 +- .../src/components/note/MkRenoteBar.vue | 14 +- .../src/components/page/page.button.vue | 7 +- .../src/components/page/page.canvas.vue | 2 +- .../src/components/page/page.counter.vue | 7 +- .../client/src/components/page/page.note.vue | 3 +- .../src/components/page/page.number-input.vue | 7 +- .../client/src/components/page/page.post.vue | 6 +- .../src/components/page/page.section.vue | 18 +- .../src/components/page/page.switch.vue | 5 +- .../src/components/page/page.text-input.vue | 7 +- .../client/src/components/page/page.text.vue | 7 +- packages/client/src/init.ts | 50 +- packages/client/src/navbar.ts | 6 +- packages/client/src/pages/_error_.vue | 12 +- packages/client/src/pages/about-firefish.vue | 23 +- packages/client/src/pages/about.emojis.vue | 16 +- .../client/src/pages/about.federation.vue | 4 +- packages/client/src/pages/about.vue | 12 +- packages/client/src/pages/admin-file.vue | 9 +- packages/client/src/pages/admin/_header_.vue | 79 +- packages/client/src/pages/admin/abuses.vue | 12 +- .../src/pages/admin/emoji-edit-dialog.vue | 5 +- packages/client/src/pages/admin/emojis.vue | 28 +- packages/client/src/pages/admin/files.vue | 2 +- packages/client/src/pages/admin/index.vue | 20 +- .../src/pages/admin/overview.federation.vue | 19 +- .../src/pages/admin/overview.metrics.vue | 8 +- .../src/pages/admin/overview.moderators.vue | 8 +- .../client/src/pages/admin/overview.queue.vue | 7 +- .../client/src/pages/admin/overview.stats.vue | 17 +- .../client/src/pages/admin/overview.user.vue | 14 +- .../client/src/pages/admin/promotions.vue | 4 +- .../client/src/pages/admin/queue.chart.vue | 10 +- packages/client/src/pages/admin/relays.vue | 7 +- packages/client/src/pages/admin/settings.vue | 6 +- packages/client/src/pages/admin/users.vue | 8 +- packages/client/src/pages/announcements.vue | 8 +- packages/client/src/pages/channel-editor.vue | 2 +- packages/client/src/pages/channel.vue | 55 +- packages/client/src/pages/channels.vue | 5 + packages/client/src/pages/clip.vue | 8 +- packages/client/src/pages/emojis.emoji.vue | 11 +- .../client/src/pages/explore.featured.vue | 2 +- packages/client/src/pages/explore.users.vue | 22 +- packages/client/src/pages/explore.vue | 5 + .../client/src/pages/follow-requests-sent.vue | 29 +- packages/client/src/pages/follow-requests.vue | 38 +- packages/client/src/pages/gallery/edit.vue | 10 +- packages/client/src/pages/gallery/index.vue | 8 +- packages/client/src/pages/gallery/post.vue | 49 +- packages/client/src/pages/instance-info.vue | 27 +- packages/client/src/pages/messaging/index.vue | 14 +- .../pages/messaging/messaging-room.form.vue | 32 +- .../messaging/messaging-room.message.vue | 92 +- .../src/pages/messaging/messaging-room.vue | 51 +- packages/client/src/pages/mfm-cheat-sheet.vue | 6 +- packages/client/src/pages/miauth.vue | 9 +- .../client/src/pages/my-antennas/editor.vue | 5 +- .../client/src/pages/my-antennas/index.vue | 12 +- packages/client/src/pages/my-clips/index.vue | 6 +- packages/client/src/pages/my-groups/group.vue | 7 +- packages/client/src/pages/my-groups/index.vue | 2 +- packages/client/src/pages/my-lists/index.vue | 4 +- packages/client/src/pages/my-lists/list.vue | 10 +- packages/client/src/pages/no-graze.vue | 9 +- packages/client/src/pages/note.vue | 13 +- packages/client/src/pages/notifications.vue | 5 + packages/client/src/pages/oauth.vue | 18 +- .../page-editor/els/page-editor.el.button.vue | 5 +- .../page-editor/els/page-editor.el.if.vue | 5 +- .../page-editor/els/page-editor.el.image.vue | 2 +- .../page-editor/els/page-editor.el.note.vue | 4 +- .../page-editor/els/page-editor.el.switch.vue | 5 +- .../page-editor/els/page-editor.el.text.vue | 6 +- .../els/page-editor.el.textarea.vue | 6 +- .../page-editor/page-editor.container.vue | 24 +- .../page-editor/page-editor.script-block.vue | 10 +- .../src/pages/page-editor/page-editor.vue | 40 +- packages/client/src/pages/page.vue | 58 +- packages/client/src/pages/pages.vue | 17 +- packages/client/src/pages/scratchpad.vue | 4 +- packages/client/src/pages/search.vue | 5 + .../src/pages/settings/2fa.qrdialog.vue | 10 +- packages/client/src/pages/settings/2fa.vue | 8 +- .../client/src/pages/settings/accounts.vue | 13 +- packages/client/src/pages/settings/apps.vue | 11 +- packages/client/src/pages/settings/deck.vue | 2 +- packages/client/src/pages/settings/drive.vue | 4 +- .../client/src/pages/settings/general.vue | 27 + .../src/pages/settings/import-export.vue | 2 +- packages/client/src/pages/settings/index.vue | 28 +- .../src/pages/settings/instance-mute.vue | 2 +- .../client/src/pages/settings/migration.vue | 2 +- .../client/src/pages/settings/mute-block.vue | 2 +- packages/client/src/pages/settings/navbar.vue | 21 +- packages/client/src/pages/settings/other.vue | 2 +- packages/client/src/pages/settings/plugin.vue | 2 +- .../client/src/pages/settings/profile.vue | 17 +- .../client/src/pages/settings/reaction.vue | 18 +- .../client/src/pages/settings/security.vue | 20 +- packages/client/src/pages/settings/sounds.vue | 2 +- .../client/src/pages/settings/statusbar.vue | 2 +- packages/client/src/pages/settings/theme.vue | 119 +- .../client/src/pages/settings/word-mute.vue | 2 +- packages/client/src/pages/share.vue | 3 +- packages/client/src/pages/tag.vue | 5 + packages/client/src/pages/theme-editor.vue | 13 +- packages/client/src/pages/timeline.vue | 19 +- packages/client/src/pages/user-info.vue | 23 +- .../client/src/pages/user-list-timeline.vue | 2 +- packages/client/src/pages/user/clips.vue | 6 +- packages/client/src/pages/user/home.vue | 156 +-- .../client/src/pages/user/index.photos.vue | 6 +- .../client/src/pages/user/index.timeline.vue | 6 +- packages/client/src/pages/user/index.vue | 2 +- packages/client/src/pages/user/media-list.vue | 2 +- packages/client/src/pages/user/reactions.vue | 19 +- .../client/src/pages/welcome.entrance.a.vue | 127 +- .../client/src/pages/welcome.entrance.b.vue | 66 +- .../client/src/pages/welcome.entrance.c.vue | 86 +- packages/client/src/pages/welcome.setup.vue | 10 +- .../client/src/pages/welcome.timeline.vue | 20 +- packages/client/src/scripts/get-note-menu.ts | 4 +- packages/client/src/scripts/get-user-menu.ts | 2 +- packages/client/src/store.ts | 9 + packages/client/src/style.scss | 238 ++-- packages/client/src/ui/_common_/common.vue | 19 +- .../src/ui/_common_/navbar-for-mobile.vue | 97 +- packages/client/src/ui/_common_/navbar.vue | 178 +-- .../src/ui/_common_/statusbar-federation.vue | 10 +- .../client/src/ui/_common_/statusbar-rss.vue | 9 +- .../src/ui/_common_/statusbar-user-list.vue | 13 +- .../client/src/ui/_common_/statusbars.vue | 9 +- .../src/ui/_common_/stream-indicator.vue | 9 +- packages/client/src/ui/_common_/upload.vue | 42 +- packages/client/src/ui/deck.vue | 86 +- .../client/src/ui/deck/antenna-column.vue | 2 +- .../client/src/ui/deck/channel-column.vue | 2 +- packages/client/src/ui/deck/column.vue | 56 +- packages/client/src/ui/deck/direct-column.vue | 2 +- packages/client/src/ui/deck/list-column.vue | 4 +- .../client/src/ui/deck/mentions-column.vue | 2 +- .../src/ui/deck/notifications-column.vue | 2 +- packages/client/src/ui/deck/tl-column.vue | 2 +- .../client/src/ui/deck/widgets-column.vue | 5 +- packages/client/src/ui/universal.vue | 154 ++- packages/client/src/ui/universal.widgets.vue | 16 +- packages/client/src/ui/visitor/a.vue | 41 +- packages/client/src/ui/visitor/b.vue | 44 +- packages/client/src/ui/visitor/header.vue | 64 +- packages/client/src/ui/visitor/kanban.vue | 62 +- packages/client/src/ui/zen.vue | 5 +- packages/client/src/widgets/aiscript.vue | 19 +- packages/client/src/widgets/calendar.vue | 43 +- packages/client/src/widgets/clock.vue | 22 +- packages/client/src/widgets/digital-clock.vue | 3 +- packages/client/src/widgets/federation.vue | 19 +- .../client/src/widgets/instance-cloud.vue | 2 +- packages/client/src/widgets/job-queue.vue | 4 +- packages/client/src/widgets/memo.vue | 19 +- packages/client/src/widgets/notifications.vue | 2 +- packages/client/src/widgets/online-users.vue | 3 +- packages/client/src/widgets/photos.vue | 4 +- packages/client/src/widgets/rss-ticker.vue | 11 +- packages/client/src/widgets/rss.vue | 5 +- packages/client/src/widgets/server-info.vue | 11 +- .../src/widgets/server-metric/cpu-mem.vue | 6 +- .../client/src/widgets/server-metric/cpu.vue | 8 +- .../client/src/widgets/server-metric/disk.vue | 8 +- .../client/src/widgets/server-metric/mem.vue | 8 +- .../client/src/widgets/server-metric/pie.vue | 2 +- packages/client/src/widgets/slideshow.vue | 14 +- packages/client/src/widgets/timeline.vue | 14 +- packages/client/src/widgets/trends.vue | 11 +- packages/client/src/widgets/unix-clock.vue | 3 +- packages/client/vite.config.ts | 2 +- packages/firefish-js/package.json | 4 +- packages/firefish-js/src/index.ts | 3 +- packages/firefish-js/src/misc/langmap.ts | 4 + packages/macro-rs/macros-impl/src/lib.rs | 1 - packages/macro-rs/macros/src/lib.rs | 18 +- packages/sw/package.json | 6 +- packages/sw/src/scripts/notification-read.ts | 6 +- packages/sw/vite.config.ts | 2 +- pnpm-lock.yaml | 1116 ++++++++--------- 517 files changed, 7966 insertions(+), 4444 deletions(-) create mode 100644 .gitlab/issue_templates/default.md create mode 100644 .gitlab/issue_templates/discussion.md create mode 100644 .gitlab/issue_templates/feature.md create mode 100644 .gitlab/issue_templates/refactor.md create mode 100644 .gitlab/merge_request_templates/default.md create mode 100644 .gitlab/merge_request_templates/release.md create mode 100644 packages/backend-rs/src/cache/bare.rs create mode 100644 packages/backend-rs/src/cache/mod.rs create mode 100644 packages/backend-rs/src/cache/redis.rs create mode 100644 packages/backend-rs/src/federation/activitypub/object/accept.rs create mode 100644 packages/backend-rs/src/federation/activitypub/object/add.rs create mode 100644 packages/backend-rs/src/federation/activitypub/object/emoji.rs create mode 100644 packages/backend-rs/src/federation/activitypub/object/flag.rs create mode 100644 packages/backend-rs/src/federation/activitypub/object/follow.rs create mode 100644 packages/backend-rs/src/federation/activitypub/object/hashtag.rs create mode 100644 packages/backend-rs/src/federation/activitypub/object/like.rs create mode 100644 packages/backend-rs/src/federation/activitypub/object/mention.rs create mode 100644 packages/backend-rs/src/federation/activitypub/object/read.rs create mode 100644 packages/backend-rs/src/federation/activitypub/object/reject.rs create mode 100644 packages/backend-rs/src/federation/activitypub/object/remove.rs create mode 100644 packages/backend-rs/src/federation/activitypub/object/tombstone.rs create mode 100644 packages/backend-rs/src/misc/emoji/mod.rs create mode 100644 packages/backend-rs/src/misc/emoji/reaction.rs create mode 100644 packages/backend-rs/src/misc/emoji/unicode.rs create mode 100644 packages/backend-rs/src/misc/random_icon.rs create mode 100644 packages/backend/src/migration/1722346019160-set-emoji-public-url.ts diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d8275a7..3821fc0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: docker.io/rust:slim-bookworm +image: docker.io/rust:bookworm services: - name: docker.io/groonga/pgroonga:latest-alpine-12-slim @@ -47,8 +47,7 @@ variables: default: before_script: - - apt-get update && apt-get -y upgrade - - apt-get -y --no-install-recommends install curl + - apt-get update && apt-get -y --no-install-recommends install curl - curl -fsSL 'https://deb.nodesource.com/setup_18.x' | bash - - apt-get install -y --no-install-recommends build-essential clang mold python3 perl nodejs postgresql-client - corepack enable @@ -111,8 +110,7 @@ test:build:backend_ts: - packages/firefish-js/**/* when: always before_script: - - apt-get update && apt-get -y upgrade - - apt-get -y --no-install-recommends install curl + - apt-get update && apt-get -y --no-install-recommends install curl - curl -fsSL 'https://deb.nodesource.com/setup_18.x' | bash - - apt-get install -y --no-install-recommends build-essential clang mold python3 perl nodejs postgresql-client - corepack enable @@ -159,8 +157,7 @@ test:build:client: when: always services: [] before_script: - - apt-get update && apt-get -y upgrade - - apt-get -y --no-install-recommends install curl + - apt-get update && apt-get -y --no-install-recommends install curl - curl -fsSL 'https://deb.nodesource.com/setup_18.x' | bash - - apt-get install -y --no-install-recommends build-essential python3 perl nodejs - corepack enable @@ -179,10 +176,9 @@ build:container: variables: STORAGE_DRIVER: overlay before_script: - - apt-get update && apt-get -y upgrade + - apt-get update && apt-get install -y --no-install-recommends ca-certificates fuse-overlayfs buildah - |- sed -i -r 's/"version": "([-0-9]+)",/"version": "\1-dev",/' package.json - - apt-get install -y --no-install-recommends ca-certificates fuse-overlayfs buildah - echo "${CI_REGISTRY_PASSWORD}" | buildah login --username "${CI_REGISTRY_USER}" --password-stdin "${CI_REGISTRY}" - export IMAGE_TAG_1="${CI_REGISTRY}/${CI_PROJECT_PATH}/develop:not-for-production" - export IMAGE_TAG_2="${CI_REGISTRY}/${CI_PROJECT_PATH}/develop:not-for-production-$(date +%Y%m%d)" @@ -205,7 +201,7 @@ build:container: cargo:check:msrv: stage: test - image: docker.io/rust:1.74-slim-bookworm + image: docker.io/rust:1.74-bookworm rules: - if: $TEST == 'true' when: always @@ -221,8 +217,7 @@ cargo:check:msrv: when: always services: [] before_script: - - apt-get update && apt-get -y upgrade - - apt-get install -y --no-install-recommends build-essential clang mold python3 perl nodejs postgresql-client + - apt-get update && apt-get install -y --no-install-recommends build-essential clang mold python3 perl nodejs postgresql-client - cp ci/cargo/config.toml /usr/local/cargo/config.toml - export CARGO_TARGET_DIR='ci/target-msrv' script: @@ -264,8 +259,7 @@ cargo:clippy: when: always services: [] before_script: - - apt-get update && apt-get -y upgrade - - apt-get install -y --no-install-recommends build-essential clang mold perl + - apt-get update && apt-get install -y --no-install-recommends build-essential clang mold perl - cp ci/cargo/config.toml /usr/local/cargo/config.toml - rustup component add clippy script: @@ -289,8 +283,7 @@ cargo:doc: when: always services: [] before_script: - - apt-get update - - apt-get install -y --no-install-recommends build-essential clang mold nodejs npm + - apt-get update && apt-get install -y --no-install-recommends build-essential clang mold nodejs npm - cp ci/cargo/config.toml /usr/local/cargo/config.toml script: - cargo doc --document-private-items @@ -331,8 +324,7 @@ clean: - if: $CLEAN && $CI_PIPELINE_SOURCE == 'schedule' services: [] before_script: - - apt-get update && apt-get -y upgrade - - apt-get -y --no-install-recommends install curl + - apt-get update && apt-get -y --no-install-recommends install curl - curl -fsSL 'https://deb.nodesource.com/setup_18.x' | bash - - apt-get install -y --no-install-recommends nodejs - corepack enable diff --git a/.gitlab/issue_templates/default.md b/.gitlab/issue_templates/default.md new file mode 100644 index 0000000..b606d65 --- /dev/null +++ b/.gitlab/issue_templates/default.md @@ -0,0 +1,99 @@ + + + + +## What type of issue is this? + + + + + + + + + + + + + + + +* label: Bug + +## What happened? + + + +## What did you expect to happen? + + + +## Steps to reproduce the issue + + + +## Reproduces how often + + + +## What did you try to solve the issue / Do you have any insights + + + +## Version + + + +
+ +### Instance + + + +### What browser are you using? (client-side issues only) + + +### What operating system are you using? (client-side issues only) + + +### How do you deploy Firefish on your server? (server-side issues only) + + +### What operating system are you using? (Server-side issues only) + + +### Relevant log output + + + +
+ +## Contribution Guidelines +By submitting this issue, you agree to follow our [Contribution Guidelines](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md) +- [ ] I agree to follow this project's Contribution Guidelines +- [ ] I have searched the issue tracker for similar issues, and this is not a duplicate. + +## Are you willing to fix this bug? (optional) +- [ ] Yes, I will open a merge request that closes this ticket. + + + diff --git a/.gitlab/issue_templates/discussion.md b/.gitlab/issue_templates/discussion.md new file mode 100644 index 0000000..bc930f3 --- /dev/null +++ b/.gitlab/issue_templates/discussion.md @@ -0,0 +1,82 @@ + + + + +## What type of issue is this? + + + + + + + + + + + + + + + +* label: Discussion + +## What do you think needs to be discussed? + + + +## Relevant information (optional) + + +## Version + + + +
+ +### Instance + + + +### What browser are you using? (client-side issues only) + + +### What operating system are you using? (client-side issues only) + + +### How do you deploy Firefish on your server? (server-side issues only) + + +### What operating system are you using? (Server-side issues only) + + +
+ +## Contribution Guidelines +By submitting this issue, you agree to follow our [Contribution Guidelines](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md) +- [ ] I agree to follow this project's Contribution Guidelines +- [ ] I have searched the issue tracker for similar issues, and this is not a duplicate. + +## Are you willing to open a merge request? (optional) +- [ ] Yes, I will open a merge request that closes this ticket. + + + diff --git a/.gitlab/issue_templates/feature.md b/.gitlab/issue_templates/feature.md new file mode 100644 index 0000000..1eb2a71 --- /dev/null +++ b/.gitlab/issue_templates/feature.md @@ -0,0 +1,67 @@ + + + + +## What type of feature is this? + + + + + + + + + + + + + + + +* label: Feature + +## What feature would you like implemented? + + + +## Why should we add this feature? + + + +## Version + + + +## Instance + + + +## Contribution Guidelines +By submitting this issue, you agree to follow our [Contribution Guidelines](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md) +- [ ] I agree to follow this project's Contribution Guidelines +- [ ] I have searched the issue tracker for similar requests, and this is not a duplicate. + +## Are you willing to implement this feature? (optional) +- [ ] Yes, I will open a merge request that closes this ticket. + + + diff --git a/.gitlab/issue_templates/refactor.md b/.gitlab/issue_templates/refactor.md new file mode 100644 index 0000000..ab9a544 --- /dev/null +++ b/.gitlab/issue_templates/refactor.md @@ -0,0 +1,67 @@ + + + + +## What type of refactoring is this? + + + + + + + + + + + + + + + +* label: Refactor + +## What parts of the code do you think should be refactored? + + + +## Why should the code be refactored that way? + + + +## Version + + + +## Instance + + + +## Contribution Guidelines +By submitting this issue, you agree to follow our [Contribution Guidelines](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md) +- [ ] I agree to follow this project's Contribution Guidelines +- [ ] I have searched the issue tracker for similar requests, and this is not a duplicate. + +## Are you willing to refactor the code? (optional) +- [ ] Yes, I will open a merge request that closes this ticket. + + + diff --git a/.gitlab/merge_request_templates/default.md b/.gitlab/merge_request_templates/default.md new file mode 100644 index 0000000..7a37db2 --- /dev/null +++ b/.gitlab/merge_request_templates/default.md @@ -0,0 +1,18 @@ + + +## What does this merge request do? + + + +## Contribution Guidelines +By submitting this merge request, you agree to follow our [Contribution Guidelines](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md) +- [ ] This closes #0000 (please substitute the issue number or open a new one unless this is a minor fix/refactor) +- [ ] I agree to follow this project's Contribution Guidelines +- [ ] I have made sure to test this merge request +- [ ] I have made sure to run `pnpm run format` before submitting this merge request + +If this merge request makes changes to API, please update `docs/api-change.md` +- [ ] I updated the document / This merge request doesn't include API changes + + + diff --git a/.gitlab/merge_request_templates/release.md b/.gitlab/merge_request_templates/release.md new file mode 100644 index 0000000..6b83d5f --- /dev/null +++ b/.gitlab/merge_request_templates/release.md @@ -0,0 +1,18 @@ + +/label Release + +## Checklist + +- [ ] There are no pending changes on Weblate + +I have updated... + +- [ ] `package.json` +- [ ] `docs/changelog.md` +- [ ] `docs/notice-for-admins.md` +- [ ] `docs/api-change.md` +- [ ] `packages/backend-rs/index.js` +- [ ] OCI container image + +## Remarks + diff --git a/Cargo.lock b/Cargo.lock index 974715a..57b3e7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,7 +98,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -149,7 +149,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -160,7 +160,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -213,7 +213,9 @@ dependencies = [ "chrono", "cuid2", "emojis", + "error-doc", "futures-util", + "identicon-rs", "idna 1.0.2", "image", "isahc", @@ -241,6 +243,7 @@ dependencies = [ "tracing-subscriber", "url", "urlencoding", + "uuid", "web-push", "zhconv", ] @@ -325,6 +328,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e0d60973d9320722cb1206f412740e162a33b8547ea8d6be75d7cff237c7a85" +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitflags" version = "1.3.2" @@ -388,9 +397,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.16.1" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" +checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83" [[package]] name = "byteorder" @@ -406,9 +415,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.6.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "castaway" @@ -418,9 +427,9 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.1.6" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" +checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" dependencies = [ "jobserver", "libc", @@ -611,6 +620,12 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -646,7 +661,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" dependencies = [ "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -684,9 +699,9 @@ dependencies = [ [[package]] name = "curl-sys" -version = "0.4.73+curl-8.8.0" +version = "0.4.74+curl-8.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "450ab250ecf17227c39afb9a2dd9261dc0035cb80f2612472fc0c4aac2dcb84d" +checksum = "8af10b986114528fcdc4b63b6f5f021b7057618411046a4de2ba0f0149a097bf" dependencies = [ "cc", "libc", @@ -758,17 +773,6 @@ dependencies = [ "serde", ] -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "diff" version = "0.1.13" @@ -795,7 +799,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -846,6 +850,18 @@ dependencies = [ "getrandom", ] +[[package]] +name = "educe" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4bd92664bf78c4d3dba9b7cdafce6fa15b13ed3ed16175218196942e99168a8" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.75", +] + [[package]] name = "either" version = "1.13.0" @@ -894,6 +910,26 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-ordinalize" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.75", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -910,6 +946,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "error-doc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ffaad84523e0144697672bce3a0d8e300fd43404a630d420f238e2ef2e85b84" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.75", +] + [[package]] name = "etcetera" version = "0.8.0" @@ -927,6 +974,22 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "exr" +version = "1.72.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -963,9 +1026,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" dependencies = [ "crc32fast", "miniz_oxide", @@ -1165,6 +1228,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -1290,7 +1363,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.52.0", ] [[package]] @@ -1417,7 +1490,18 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", +] + +[[package]] +name = "identicon-rs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c4fdcf9f82fef33187cc52ad515de24786ec837837e69f641d1cb8157f4f02" +dependencies = [ + "image", + "sha3", + "thiserror", ] [[package]] @@ -1451,11 +1535,14 @@ dependencies = [ "bytemuck", "byteorder-lite", "color_quant", + "exr", "gif", "image-webp", "num-traits", "png", + "qoi", "ravif", + "rayon", "rgb", "tiff", "zune-core", @@ -1480,9 +1567,9 @@ checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" [[package]] name = "indexmap" -version = "2.2.6" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" dependencies = [ "equivalent", "hashbrown", @@ -1496,7 +1583,7 @@ checksum = "0122b7114117e64a63ac49f752a5ca4624d534c7b1c7de796ac196381cd2d947" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -1525,7 +1612,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -1663,6 +1750,12 @@ dependencies = [ "spin", ] +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "libc" version = "0.2.155" @@ -1775,7 +1868,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.72", + "syn 2.0.75", "thiserror", ] @@ -1786,7 +1879,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -1883,7 +1976,7 @@ dependencies = [ "napi-derive-backend", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -1898,7 +1991,7 @@ dependencies = [ "quote", "regex", "semver", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -2028,7 +2121,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -2119,7 +2212,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -2180,7 +2273,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -2338,7 +2431,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -2439,9 +2532,12 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "pretty_assertions" @@ -2511,7 +2607,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" dependencies = [ "quote", - "syn 2.0.72", + "syn 2.0.75", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", ] [[package]] @@ -2631,9 +2736,9 @@ dependencies = [ [[package]] name = "redis" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cc5b667390cb038bc65fc4b18c06e2550469f7e06a02d886f1a018a11f63563" +checksum = "e902a69d09078829137b4a5d9d082e0490393537badd7c91a3d69d14639e115f" dependencies = [ "arc-swap", "async-trait", @@ -2670,9 +2775,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.5" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", @@ -2709,9 +2814,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.45" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade4539f42266ded9e755c605bdddf546242b2c961b03b06a7375260788a0523" +checksum = "e12bc8d2f72df26a5d3178022df33720fbede0d31d82c7291662eff89836994d" dependencies = [ "bytemuck", ] @@ -2901,14 +3006,14 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] name = "sea-orm" -version = "0.12.15" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8814e37dc25de54398ee62228323657520b7f29713b8e238649385dbe473ee0" +checksum = "f9d4ec1cdd8bdd3553d3c946079f58efa33fedc477f32603652652abcef96fe6" dependencies = [ "async-stream", "async-trait", @@ -2922,7 +3027,7 @@ dependencies = [ "serde", "serde_json", "sqlx", - "strum 0.25.0", + "strum 0.26.3", "thiserror", "time", "tracing", @@ -2932,26 +3037,26 @@ dependencies = [ [[package]] name = "sea-orm-macros" -version = "0.12.15" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e115c6b078e013aa963cc2d38c196c2c40b05f03d0ac872fe06b6e0d5265603" +checksum = "f363ead48b625a6f8f905322a820464f728fa4fe4f1c222bed5234ccf8fb8555" dependencies = [ "heck 0.4.1", "proc-macro2", "quote", "sea-bae", - "syn 2.0.72", + "syn 2.0.75", "unicode-ident", ] [[package]] name = "sea-query" -version = "0.30.7" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4166a1e072292d46dc91f31617c2a1cdaf55a8be4b5c9f4bf2ba248e3ac4999b" +checksum = "7e5073b2cfed767511a57d18115f3b3d8bcb5690bf8c89518caec6cb22c0cd74" dependencies = [ "chrono", - "derivative", + "educe", "inherent", "ordered-float", "serde_json", @@ -2959,9 +3064,9 @@ dependencies = [ [[package]] name = "sea-query-binder" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36bbb68df92e820e4d5aeb17b4acd5cc8b5d18b2c36a4dd6f4626aabfa7ab1b9" +checksum = "754965d4aee6145bec25d0898e5c931e6c22859789ce62fd85a42a15ed5a8ce3" dependencies = [ "chrono", "sea-query", @@ -3002,31 +3107,32 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.204" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.204" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] name = "serde_json" -version = "1.0.120" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -3454,9 +3560,9 @@ dependencies = [ [[package]] name = "strum" -version = "0.25.0" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" [[package]] name = "strum_macros" @@ -3490,9 +3596,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.72" +version = "2.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" dependencies = [ "proc-macro2", "quote", @@ -3519,20 +3625,19 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] name = "sysinfo" -version = "0.30.13" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3" +checksum = "d4115055da5f572fff541dd0c4e61b0262977f453cc9fe04be83aba25a89bdab" dependencies = [ - "cfg-if", "core-foundation-sys", "libc", + "memchr", "ntapi", - "once_cell", "windows", ] @@ -3551,18 +3656,19 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.15" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4873307b7c257eddcb50c9bedf158eb669578359fb28428bef438fec8e6ba7c2" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.10.1" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "b8fcd239983515c23a32fb82099f97d0b11b8c72f654ed659363a95c3dad7a53" dependencies = [ "cfg-if", "fastrand 2.1.0", + "once_cell", "rustix", "windows-sys 0.52.0", ] @@ -3593,7 +3699,7 @@ checksum = "e4c60d69f36615a077cc7663b9cb8e42275722d23e58a7fa3d2c7f2915d09d04" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -3604,7 +3710,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -3688,9 +3794,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.2" +version = "1.39.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" +checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" dependencies = [ "backtrace", "bytes", @@ -3711,7 +3817,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -3753,9 +3859,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.16" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", @@ -3765,18 +3871,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.17" +version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ "indexmap", "serde", @@ -3805,7 +3911,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -3947,6 +4053,8 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ + "getrandom", + "rand", "serde", ] @@ -4039,7 +4147,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", "wasm-bindgen-shared", ] @@ -4061,7 +4169,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4074,8 +4182,9 @@ checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-push" -version = "0.10.1" -source = "git+https://github.com/pimeys/rust-web-push.git?rev=40febe4085e3cef9cdfd539c315e3e945aba0656#40febe4085e3cef9cdfd539c315e3e945aba0656" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49214685777c429ac44a92780983f4fadeecddd8c08c463d8196521e17e974bd" dependencies = [ "async-trait", "base64 0.13.1", @@ -4139,11 +4248,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.52.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" dependencies = [ - "windows-core", + "windows-core 0.57.0", "windows-targets 0.52.6", ] @@ -4156,6 +4265,49 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.75", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.75", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -4297,9 +4449,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.16" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b480ae9340fc261e6be3e95a1ba86d54ae3f9171132a73ce8d4bbaf68339507c" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] @@ -4342,7 +4494,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", "synstructure 0.13.1", ] @@ -4352,6 +4504,7 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] @@ -4363,7 +4516,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -4383,7 +4536,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", "synstructure 0.13.1", ] @@ -4412,7 +4565,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.75", ] [[package]] @@ -4457,9 +4610,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.12+zstd.1.5.6" +version = "2.0.13+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" dependencies = [ "cc", "pkg-config", @@ -4471,6 +4624,15 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + [[package]] name = "zune-jpeg" version = "0.4.13" diff --git a/Cargo.toml b/Cargo.toml index 421ae8b..29d4985 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,9 @@ chrono = { version = "0.4.38", default-features = false } convert_case = { version = "0.6.0", default-features = false } cuid2 = { version = "0.1.2", default-features = false } emojis = { version = "0.6.3", default-features = false } +error-doc = { version = "0.1.0" } futures-util = { version = "0.3.30", default-features = false } +identicon-rs = "5.0.1" idna = { version = "1.0.2", default-features = false } image = { version = "0.25.2", default-features = false } isahc = { version = "1.7.2", default-features = false } @@ -33,23 +35,24 @@ pretty_assertions = { version = "1.4.0", default-features = false } proc-macro2 = { version = "1.0.86", default-features = false } quote = { version = "1.0.36", default-features = false } rand = { version = "0.8.5", default-features = false } -redis = { version = "0.26.0", default-features = false } -regex = { version = "1.10.5", default-features = false } +redis = { version = "0.26.1", default-features = false } +regex = { version = "1.10.6", default-features = false } rmp-serde = { version = "1.3.0", default-features = false } -sea-orm = { version = "0.12.15", default-features = false } -serde = { version = "1.0.204", default-features = false } -serde_json = { version = "1.0.120", default-features = false } +sea-orm = { version = "1.0.0", default-features = false } +serde = { version = "1.0.208", default-features = false } +serde_json = { version = "1.0.125", default-features = false } serde_yaml = { version = "0.9.34", default-features = false } -syn = { version = "2.0.72", default-features = false } -sysinfo = { version = "0.30.13", default-features = false } +syn = { version = "2.0.75", default-features = false } +sysinfo = { version = "0.31.2", default-features = false } thiserror = { version = "1.0.63", default-features = false } -tokio = { version = "1.39.2", default-features = false } +tokio = { version = "1.39.3", default-features = false } tokio-test = { version = "0.4.4", default-features = false } tracing = { version = "0.1.40", default-features = false } tracing-subscriber = { version = "0.3.18", default-features = false } url = { version = "2.5.2", default-features = false } urlencoding = { version = "2.1.3", default-features = false } -web-push = { git = "https://github.com/pimeys/rust-web-push.git", rev = "40febe4085e3cef9cdfd539c315e3e945aba0656", default-features = false } +uuid = "1.10.0" +web-push = { version = "0.10.2", default-features = false } zhconv = "0.3.1" # subdependencies diff --git a/Dockerfile b/Dockerfile index f9087b9..9ef65e8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,6 @@ RUN corepack enable && corepack prepare pnpm@latest --activate # Build COPY . ./ RUN pnpm install --frozen-lockfile -RUN cargo fetch --locked --manifest-path Cargo.toml RUN NODE_ENV='production' NODE_OPTIONS='--max_old_space_size=3072' pnpm run build # Trim down the dependencies to only those for production diff --git a/dev/container/docker-compose.yml b/dev/container/docker-compose.yml index 8768a09..56f0ff3 100644 --- a/dev/container/docker-compose.yml +++ b/dev/container/docker-compose.yml @@ -2,7 +2,7 @@ version: "3" services: web: - image: docker.io/node:18.19.0-bookworm + image: docker.io/node:18.20.0-bookworm container_name: firefish_web restart: unless-stopped depends_on: diff --git a/dev/docs/local-installation.md b/dev/docs/local-installation.md index 70f1d4c..63197f0 100644 --- a/dev/docs/local-installation.md +++ b/dev/docs/local-installation.md @@ -15,7 +15,7 @@ sudo apt install build-essential python3 curl wget git lsb-release ### Node.js -Firefish requires Node.js v18.19.0 or later. While you can choose any versions between v18.19.0 and the latest version (v22.2.0 as of writing), we recommend that you install v18.x so as not to use new features inadvertently and introduce incompatibility issues. +Firefish requires Node.js v18.20.0 or later. While you can choose any versions between v18.20.0 and the latest version (v22.2.0 as of writing), we recommend that you install v18.x so as not to use new features inadvertently and introduce incompatibility issues. Instructions can be found at [this repository](https://github.com/nodesource/distributions). diff --git a/docs/changelog.md b/docs/changelog.md index 1f4729f..3f99482 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -7,6 +7,19 @@ This changelog is not an exhaustive list. Code refactorings, minor bug fixes, do - Server administrators must check [notice-for-admins.md](https://firefish.dev/firefish/firefish/-/blob/main/docs/notice-for-admins.md) as well. - Third-party client/bot developers may want to check [api-change.md](https://firefish.dev/firefish/firefish/-/blob/main/docs/api-change.md) as well. +## [v20240818](https://firefish.dev/firefish/firefish/-/merge_requests/11293/commits) + +- Fix bugs + +## [v20240809](https://firefish.dev/firefish/firefish/-/merge_requests/11262/commits) + +- Add writing mode (right-to-left, vertical) support (!11222) +- Fix bugs + +### Breaking change + +The random icon generator has been changed, so your icon will be changed if you haven't set your icon image and random icon generation is enabled on your server. + ## [v20240729](https://firefish.dev/firefish/firefish/-/merge_requests/11214/commits) - Fix bugs (including a medium severity security issue) diff --git a/docs/downgrade.sql b/docs/downgrade.sql index 24f0f4b..d52227a 100644 --- a/docs/downgrade.sql +++ b/docs/downgrade.sql @@ -1,6 +1,7 @@ BEGIN; DELETE FROM "migrations" WHERE name IN ( + 'SetEmojiPublicUrl1722346019160', 'SetAccessTokenName1722134626110', 'CreateSystemActors1720618854585', 'AddMastodonSubscriptionType1715181461692', @@ -41,6 +42,9 @@ DELETE FROM "migrations" WHERE name IN ( 'RemoveNativeUtilsMigration1705877093218' ); +-- set-emoji-public-url +ALTER TABLE "emoji" ALTER COLUMN "publicUrl" SET DEFAULT ''; + -- addMastodonSubscriptionType ALTER TABLE "sw_subscription" DROP COLUMN "subscriptionTypes"; DROP TYPE "push_subscription_type"; diff --git a/docs/install.md b/docs/install.md index 4ed2268..96197ee 100644 --- a/docs/install.md +++ b/docs/install.md @@ -8,7 +8,7 @@ Firefish depends on the following software. ### Runtime dependencies -- At least [NodeJS](https://nodejs.org/en/) v18.19.0 (v20/v22 recommended) +- At least [NodeJS](https://nodejs.org/en/) v18.20.0 (v20/v22 recommended) - At least [PostgreSQL](https://www.postgresql.org/) v12 (v16 recommended) with [PGroonga](https://pgroonga.github.io/) extension - At least [Redis](https://redis.io/) v7 or [Valkey](https://valkey.io/) v7 - Web Proxy (one of the following) diff --git a/docs/notice-for-admins.md b/docs/notice-for-admins.md index 366b026..4e7b66f 100644 --- a/docs/notice-for-admins.md +++ b/docs/notice-for-admins.md @@ -2,9 +2,13 @@ You can skip intermediate versions when upgrading from an old version, but please read the notices and follow the instructions for each intermediate version before [upgrading](https://firefish.dev/firefish/firefish/-/blob/main/docs/upgrade.md). -## Upcoming breaking change (unreleased) +## v20240809 -Please take a look at #10947. +### For systemd/pm2 users + +Required Node.js version has been bumped from v18.19.0 to v18.20.0. + +As written in the [v20240710 note](https://firefish.dev/firefish/firefish/-/blob/7660050d9938a5a92293bb8acc361a0ef0715912/docs/notice-for-admins.md#v20240710), it is highly recommended that you use an even newer version since v18.20.0 has known vulnerabilities. ## v20240725 diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index 89aede8..bb39683 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -735,7 +735,7 @@ _notification: pollEnded: Es resultat de la enquesta ja està disponible emptyPushNotificationMessage: Les notificacions push s'han actualitzat youWereInvitedToGroup: "{userName} t'ha convidat a un grup" - reacted: Ha reaccionat a la teva publicació + reacted: han reaccionat a la teva publicació renoted: Ha impulsat la teva publicació voted: Ha votat a la teva enquesta andCountUsers: I {count} usuaris més {acted} @@ -978,7 +978,7 @@ recipient: Destinatari(s) annotation: Comentaris blockedInstances: Servidors bloquejats blockedInstancesDescription: Llista les adreces dels servidors que vols bloquejar. - Els servidors de la llista no podrán comunicarse amb aquests servidors. + Els servidors de la llista no podrán comunicarse amb aquest servidor. hiddenTags: Etiquetes amagades hiddenTagsDescription: 'Enumereu les etiquetes (sense el #) que voleu ocultar de tendències i explorar. Les etiquetes ocultes encara es poden descobrir per altres mitjans.' @@ -1250,8 +1250,8 @@ disablePlayer: Tancar el reproductor de vídeo fileIdOrUrl: ID o adreça URL del fitxer behavior: Comportament regenerateLoginTokenDescription: Regenera la clau que es fa servir de manera interna - durant l'inici de sessió. Normalment això no és necessari. Si la clau és torna a - generar, es tancarà la sessió a tots els dispositius. + durant l'inici de sessió. Usualment aquesta acció no és necessària. Si la clau és + torna a generar, es tancarà la sessió a tots els dispositius. setMultipleBySeparatingWithSpace: Separa diferents entrades amb espais. reportAbuseOf: Informa d'un abús de {name} sample: Exemple @@ -1934,6 +1934,12 @@ _permissions: "read:reactions": Consulta les teves reaccions "read:pages": Consulta la teva pàgina "read:page-likes": Veure les pàgines que t'agraden + push: Enviar notificacions emergents + follow: Seguir i deixar de seguir comptes + read: Llegir (llegir línies de temps, notificacions, reaccions, silenciats, informació + dels comptes, etc.) + write: Escriure (escriure publicacions, reaccionar a publicacions, silenciar usuaris, + editar informació dels comptes, etc.) _poll: noOnlyOneChoice: Calen almenys dues opcions canMultipleVote: Permet seleccionar diverses opcions @@ -2228,7 +2234,7 @@ useCdnDescription: Carrega alguns dels recursos estàtics com ara Twemoji des de releaseToReload: Deixa anar per actualitzar reloading: Actualitzant enableTimelineStreaming: Actualitza les línies de temps automàticament -enablePullToRefresh: Activa "Baixa per actualitzar" +enablePullToRefresh: Activa "Baixa per recarregar" pullDownToReload: Baixa per actualitzar pullToRefreshThreshold: Distancia de baixada per actualitzar searchWords: Paraules / ID o adreça URL que vols cercar @@ -2327,3 +2333,10 @@ addAlt4MeTag: "Afegeix automàticament l'etiqueta #Alt4Me a les teves publicacio strongPassword: Bona contrasenya turnOffCatLanguage: Desactiva la conversió al llenguatge de gat announcement: Anunci +_writingMode: + horizontalTB: Horitzontal, de d'alt a baix + verticalRL: Vertical, de dreta a esquerra, lateralment + verticalLRUpright: Vertical, esquerra andreta, cap amunt + verticalRLUpright: Vertical, de dreta i esquerra, cap amunt + verticalLR: Vertical, d'esquerra a dreta, lateralment +writingMode: Mode d'escriptura diff --git a/locales/en-US.yml b/locales/en-US.yml index e3cf890..6b15d2e 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -128,7 +128,7 @@ pinnedNote: "Pinned post" pinned: "Pin to profile" you: "You" clickToShow: "Click to show" -sensitive: "NSFW" +sensitive: "Sensitive" add: "Add" reaction: "Reaction" reactions: "Reactions" @@ -139,8 +139,8 @@ reactionSetting: "Reactions to show in the reaction picker" reactionSettingDescription2: "Drag to reorder, click to delete, press \"+\" to add." rememberNoteVisibility: "Remember post visibility settings" attachCancel: "Remove attachment" -markAsSensitive: "Mark as NSFW" -unmarkAsSensitive: "Unmark as NSFW" +markAsSensitive: "Mark as sensitive" +unmarkAsSensitive: "Unmark as sensitive" clickToShowPatterns: "Click to show module patterns" enterFileName: "Enter filename" mute: "Mute" @@ -176,7 +176,7 @@ cacheRemoteFilesDescription: "When this setting is disabled, remote files are lo increase traffic, as thumbnails will not be generated." markLocalFilesNsfwByDefault: "Mark all new local file as sensitive by default" markLocalFilesNsfwByDefaultDescription: "Regardless of this setting, users can remove - the NSFW flag themselves. Existing files are unaffected." + the sensitive flag themselves. Existing files are unaffected." flagAsBot: "Mark this account as automated" flagAsBotDescription: "Enable this option if this account is controlled by a program. If enabled, it will act as a flag for other developers to prevent endless interaction @@ -243,7 +243,7 @@ clearCachedFiles: "Clear cache" clearCachedFilesConfirm: "Are you sure that you want to delete all cached remote files?" blockedInstances: "Blocked Servers" blockedInstancesDescription: "List the hostnames of the servers that you want to block. - Listed servers will no longer be able to communicate with this servers." + Listed servers will no longer be able to communicate with this server." silencedInstances: "Silenced Servers" silencedInstancesDescription: "List the hostnames of the servers that you want to silence. Accounts in the listed servers are treated as \"Silenced\", can only make @@ -362,7 +362,7 @@ copyUrl: "Copy URL" rename: "Rename" avatar: "Avatar" banner: "Banner" -nsfw: "NSFW" +nsfw: "Sensitive" whenServerDisconnected: "When losing connection to the server" disconnectedFromServer: "Connection to server has been lost" reload: "Refresh" @@ -727,7 +727,7 @@ useGlobalSettingDesc: "If turned on, your account's notification settings will b other: "Other" regenerateLoginToken: "Regenerate sign in token" regenerateLoginTokenDescription: "Regenerates the token used internally during sign - in. Normally this action is not necessary. If regenerated, all devices will be logged + in. Usually this action is not necessary. If regenerated, all devices will be logged out." setMultipleBySeparatingWithSpace: "Separate multiple entries with spaces." fileIdOrUrl: "File ID or URL" @@ -792,7 +792,7 @@ noCrawle: "Reject crawler indexing" noCrawleDescription: "Ask external search engines to not index your content." lockedAccountInfo: "Unless you set your post visiblity to \"Followers only\", your posts will be visible to anyone, even if you require followers to be manually approved." -alwaysMarkSensitive: "Mark as NSFW by default" +alwaysMarkSensitive: "Mark as sensitive by default" loadRawImages: "Load original images instead of showing thumbnails" disableShowingAnimatedImages: "Don't play animated images" verificationEmailSent: "A verification email has been sent. Please follow the included @@ -1035,20 +1035,16 @@ type: "Type" speed: "Speed" slow: "Slow" fast: "Fast" -sensitiveMediaDetection: "Detection of NSFW media" localOnly: "Local only" remoteOnly: "Remote only" failedToUpload: "Upload failed" cannotUploadBecauseInappropriate: "This file could not be uploaded because parts of - it have been detected as potentially NSFW." + it have been detected as potentially sensitive." cannotUploadBecauseNoFreeSpace: "Upload failed due to lack of Drive capacity." cannotUploadBecauseExceedsFileSizeLimit: "This file could not be uploaded because it exceeds the maximum allowed size." beta: "Beta" -enableAutoSensitive: "Automatic NSFW-Marking" -enableAutoSensitiveDescription: "Allows automatic detection and marking of NSFW media - through Machine Learning where possible. Even if this option is disabled, it may - be enabled server-wide." +enableAutoSensitive: "Mark as sensitive automatically" activeEmailValidationDescription: "Enables stricter validation of email addresses, which includes checking for disposable addresses and by whether it can actually be communicated with. When unchecked, only the format of the email is validated." @@ -1200,7 +1196,7 @@ privateDescription: "Make visible for you only" makePrivate: "Make private" makePrivateConfirm: "This operation will send a deletion request to remote servers and change the visibility to private. Proceed?" -enablePullToRefresh: "Enable \"Pull down to refresh\"" +enablePullToRefresh: "Enable “Pull down to reload”" pullToRefreshThreshold: "Pull distance for reloading" pullDownToReload: "Pull down to reload" releaseToReload: "Release to reload" @@ -1211,13 +1207,13 @@ searchWordsDescription: "Enter the search term here to search for posts. Separat words with a space for an AND search, or 'OR' (without quotes) between words for an OR search.\nFor example, 'morning night' will find posts that contain both 'morning' and 'night', and 'morning OR night' will find posts that contain either 'morning' - or 'night' (or both).\nYou can also filter out certain word(s) from the search results, like - 'sleepy -morning -breakfast'. Moreover, you can combine these AND/OR/exclude conditions like - '(morning OR night) sleepy -breakfast'.\nIf you want to search for a sequence of words (e.g., a sentence), - you must put it in double quotes, not to make it an AND search: \"Today I learned\"\ - \n\nIf you want to go to a specific user page or post page, enter the ID or URL - in this field and click the 'Lookup' button. Clicking 'Search' will search for posts - that literally contain the ID/URL." + or 'night' (or both).\nYou can also filter out certain word(s) from the search results, + like 'sleepy -morning -breakfast'. Moreover, you can combine these AND/OR/exclude + conditions like '(morning OR night) sleepy -breakfast'.\nIf you want to search for + a sequence of words (e.g., a sentence), you must put it in double quotes, not to + make it an AND search: \"Today I learned\"\n\nIf you want to go to a specific user + page or post page, enter the ID or URL in this field and click the 'Lookup' button. + Clicking 'Search' will search for posts that literally contain the ID/URL." searchUsers: "Posted by (optional)" searchUsersDescription: "To search for posts by a specific user/server, enter the ID (@user@example.com, or @user for a local user) or domain name (example.com).\n @@ -1240,7 +1236,8 @@ noAltTextWarning: "Some attached file(s) have no description. Did you forget to showNoAltTextWarning: "Show a warning if you attempt to post files without a description" showAddFileDescriptionAtFirstPost: "Automatically open a form to write a description when you attempt to post files without a description" -addAlt4MeTag: "Automatically append #Alt4Me hashtag to your post if attached file has no description" +addAlt4MeTag: "Automatically append #Alt4Me hashtag to your post if attached file + has no description" turnOffCatLanguage: "Turn off cat language conversion" _emojiModPerm: @@ -1248,19 +1245,6 @@ _emojiModPerm: add: "Add" mod: "Add and Edit" full: "Allow All" -_sensitiveMediaDetection: - description: "Reduces the effort of server moderation through automatically recognizing - NSFW media via Machine Learning. This will slightly increase the load on the server." - sensitivity: "Detection sensitivity" - sensitivityDescription: "Reducing the sensitivity will lead to fewer misdetections - (false positives) whereas increasing it will lead to fewer missed detections (false - negatives)." - setSensitiveFlagAutomatically: "Mark as NSFW" - setSensitiveFlagAutomaticallyDescription: "The results of the internal detection - will be retained even if this option is turned off." - analyzeVideos: "Enable analysis of videos" - analyzeVideosDescription: "Analyzes videos in addition to images. This will slightly - increase the load on the server." _emailUnavailable: used: "This email address is already being used" format: "The format of this email address is invalid" @@ -1353,9 +1337,16 @@ _aboutFirefish: to help support its operation costs." donateHost: "Donate to {host}" _nsfw: - respect: "Hide NSFW media" - ignore: "Don't hide NSFW media" + respect: "Hide sensitive media" + ignore: "Don't hide sensitive media" force: "Hide all media" +writingMode: "Writing mode" +_writingMode: + horizontalTB: "Horizontal, top to bottom" + verticalLR: "Vertical, left to right, sideways" + verticalRL: "Vertical, right to left, sideways" + verticalLRUpright: "Vertical, left to right, upright" + verticalRLUpright: "Vertical, right to left, upright" _mfm: play: "Play MFM" stop: "Stop MFM" @@ -1668,8 +1659,10 @@ _2fa: removeKeyConfirm: "Really delete the {name} key?" token: "2FA Token" _permissions: - read: "Read (read timelines, notifications, reactions, mutes, account information, etc.)" - write: "Write (make posts, react to posts, mute users, edit account information, etc.)" + read: "Read (read timelines, notifications, reactions, mutes, account information, + etc.)" + write: "Write (make posts, react to posts, mute users, edit account information, + etc.)" push: "Send push notifications" follow: "Follow and unfollow accounts" "read:account": "View your account information" diff --git a/locales/eo.yml b/locales/eo.yml index 6e9bb18..100abaa 100644 --- a/locales/eo.yml +++ b/locales/eo.yml @@ -9,3 +9,32 @@ notifications: Sciigoj username: Uzantnomo password: Pasvorto forgotPassword: Forgesa pasvorto +fetchingAsApObject: Kaptante de Federujo +ok: O kej +noNotes: Ne afiŝoj +noNotifications: Ne sciigiloj +openInWindow: Malfermu en fenestro +profile: Profilo +gotIt: Jam estas! +cancel: Mezfini +noThankYou: Ne dankon +renotedBy: Potenci de {user} +noAccountDescription: Tiu ĉi uzanto eĉ ne skribis ilia biografio. +enterUsername: Tajpu uzantnomon +instance: Servilo +settings: Agordoj +basicSettings: Bazaj Agordoj +otherSettings: Aliaj Agordoj +login: Ensaluti +loggingIn: Ensalutante +logout: Elsaluti +timeline: Templinio +signup: Registru +favorites: Paĝosignoj +unfavorite: Forigi el paĝosignoj +uploading: Alŝutante... +save: Konservi +users: Uzantoj +addUser: Aldoni uzanton +addInstance: Aldoni servilon +favorite: Aldoni al paĝosigno diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 4e8c4ed..078e4a6 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -939,7 +939,7 @@ cannotUploadBecauseInappropriate: "不適切な内容を含む可能性がある cannotUploadBecauseNoFreeSpace: "ドライブの空き容量が無いためアップロードできません。" cannotUploadBecauseExceedsFileSizeLimit: "ファイルサイズの制限を超えているためアップロードできません。" beta: "ベータ" -enableAutoSensitive: "自動NSFW判定" +enableAutoSensitive: "自動で閲覧注意にする" enableAutoSensitiveDescription: "利用可能な場合は、機械学習を利用して自動でメディアにNSFWフラグを設定します。この機能をオフにしても、サーバーによっては自動で設定されることがあります。" activeEmailValidationDescription: "ユーザーのメールアドレスのバリデーションを、捨てアドかどうかや実際に通信可能かどうかなどを判定しより積極的に行います。オフにすると単に文字列として正しいかどうかのみチェックされます。" showAds: "コミュニティバナーを表示する" @@ -1017,7 +1017,7 @@ searchWordsDescription: "投稿を検索するには、ここに検索語句を (@user@example.com) や投稿のURLを入力し「照会」を押してください。「検索」を押すとそのIDやURLが文字通り含まれる投稿を検索します。" searchUsers: "投稿元(省略可)" searchUsersDescription: "投稿検索で投稿者を絞りたい場合、@user@example.com(ローカルユーザーなら @user)の形式で投稿者のIDを入力してください。ユーザーIDではなくドメイン名 - (example.com) を指定すると、そのサーバーの投稿を検索します。\n\nme とだけ入力すると、自分の投稿を検索します。この検索結果には未収載・フォロワー限定・ダイレクト・秘密を含む全ての投稿が含まれます。\n + (example.com) を指定すると、そのサーバーの投稿を検索します。\n\nme とだけ入力すると、自分の投稿を検索します。この検索結果には控えめな公開・フォロワー限定・ダイレクト・秘密を含む全ての投稿が含まれます。\n \nlocal とだけ入力すると、ローカルサーバーの投稿を検索します。" searchRange: "投稿期間(省略可)" searchRangeDescription: "投稿検索で投稿期間を絞りたい場合、20220615-20231031 のような形式で投稿期間を入力してください。今年の日付を指定する場合には年の指定を省略できます(0105-0106 @@ -1121,6 +1121,13 @@ _nsfw: respect: "閲覧注意のメディアは隠す" ignore: "閲覧注意のメディアを隠さない" force: "常にメディアを隠す" +writingMode: "組み方向" +_writingMode: + horizontalTB: "横組み、上から下" + verticalLR: "縦組み、左から右、英数字90度回転" + verticalRL: "縦組み、右から左、英数字90度回転" + verticalLRUpright: "縦組み、左から右、英数字1字ずつ配置" + verticalRLUpright: "縦組み、右から左、英数字1字ずつ配置" _mfm: cheatSheet: "MFMチートシート" intro: "MFMは、MisskeyやFirefish、Akkomaなどの様々な場所で使用できるマークアップ言語です。ここでは、MFMで使用可能な構文一覧が確認できます。" @@ -1425,6 +1432,10 @@ _permissions: "write:gallery": "ギャラリーを操作する" "read:gallery-likes": "ギャラリーのいいねを見る" "write:gallery-likes": "ギャラリーのいいねを操作する" + read: 読み取り(タイムライン・通知・ミュート・アカウント情報などの読み込み) + write: 書き込み(投稿・リアクション・ミュート・アカウント情報など) + push: プッシュ通知の送信 + follow: アカウントのフォローとフォロー解除 _auth: shareAccess: "「{name}」がアカウントにアクセスすることを許可しますか?" shareAccessAsk: "アカウントへのアクセスを許可しますか?" @@ -1505,7 +1516,7 @@ _poll: _visibility: public: "公開" publicDescription: "全ての公開タイムラインに配信されます" - home: "未収載" + home: "控えめな公開" homeDescription: "ホームタイムラインのみに公開" followers: "フォロワー" followersDescription: "フォロワーと会話相手のみに公開" @@ -2059,7 +2070,7 @@ driveCapacityOverride: ドライブ容量の変更 autocorrectNoteLanguage: 設定した投稿言語が自動検出されたものと異なる場合に警告する incorrectLanguageWarning: "この投稿は{detected}で書かれていると判定されました。\n投稿言語を{current}ではなく{detected}にしますか?" markLocalFilesNsfwByDefault: このサーバーの全てのファイルをデフォルトでNSFWに設定する -markLocalFilesNsfwByDefaultDescription: この設定が有効でも、ユーザーは自分でNSFWのフラグを外すことができます。また、この設定は既存のファイルには影響しません。 +markLocalFilesNsfwByDefaultDescription: この設定が有効でも、ユーザーは自分で閲覧注意のフラグを外すことができます。また、この設定は既存のファイルには影響しません。 noteEditHistory: 編集履歴 showAddFileDescriptionAtFirstPost: 説明の無い添付ファイルを投稿しようとした際に説明を書く画面を自動で開く antennaLimit: 各ユーザーが作れるアンテナの最大数 diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 2f2c54c..c4f0ee2 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -139,9 +139,9 @@ settingGuide: "추천 설정" cacheRemoteFiles: "리모트 파일을 캐시" cacheRemoteFilesDescription: "이 설정을 해지하면 리모트 파일을 캐시하지 않고 해당 파일을 직접 링크하게 됩니다. 그에 따라 서버의 저장 공간을 절약할 수 있지만, 썸네일이 생성되지 않기 때문에 통신량이 증가합니다." -flagAsBot: "나는 봇입니다" -flagAsBotDescription: "이 계정을 자동화된 수단으로 운용할 경우에 활성화해 주세요. 이 플래그를 활성화하면, 다른 봇이 이를 참고하여 - 봇 끼리의 무한 연쇄 반응을 회피하거나, 이 계정의 시스템 상에서의 취급이 Bot 운영에 최적화되는 등의 변화가 생깁니다." +flagAsBot: "이 계정은 자동화된 계정입니다" +flagAsBotDescription: "이 계정을 자동화된 수단으로 운용할 경우에 활성화해 주세요. 이 플래그를 활성화하면, 다른 자동화 계정이 + 이를 참고하여 자동화 계정 간 무한 연쇄 반응을 회피하거나, 이 계정의 시스템 상에서의 취급이 자동화 계정 운영에 최적화되는 등의 변화가 생깁니다." flagAsCat: "나는 고양이다냥" flagAsCatDescription: "고양이귀를 쓰고 냥냥거려요!" flagShowTimelineReplies: "타임라인에 게시물의 답글을 표시하기" @@ -261,7 +261,7 @@ agreeTo: "{0}에 동의" tos: "이용 약관" start: "시작하기" home: "홈" -remoteUserCaution: "리모트 유저이기 때문에, 정보가 정확하지 않을 수 있습니다." +remoteUserCaution: "리모트 유저는 모든 정보가 표시되지 않습니다." activity: "활동" images: "이미지" birthday: "생일" @@ -441,8 +441,8 @@ usernameInvalidFormat: "a~z, A~Z, 0-9, _를 사용할 수 있습니다." tooShort: "너무 짧습니다" tooLong: "너무 깁니다" weakPassword: "약한 비밀번호" -normalPassword: "좋은 비밀번호" -veryStrongPassword: "강한 비밀번호" +normalPassword: "보통 비밀번호" +veryStrongPassword: "매우 강한 비밀번호" passwordMatched: "일치합니다" passwordNotMatched: "일치하지 않습니다" signinWith: "{x}로 로그인" @@ -605,7 +605,7 @@ emptyToDisableSmtpAuth: "SMTP 인증을 사용하지 않으려면 공란으로 smtpSecure: "SMTP 연결에 Implicit SSL/TTS 사용" smtpSecureInfo: "STARTTLS 사용 시에는 해제합니다" testEmail: "이메일 전송 테스트" -wordMute: "단어 뮤트" +wordMute: "단어 및 언어 뮤트" regexpError: "정규 표현식 오류" regexpErrorDescription: "{tab}단어 뮤트 {line}행의 정규 표현식에 오류가 발생했습니다:" instanceMute: "서버 뮤트" @@ -798,7 +798,7 @@ customCss: "CSS 사용자화" customCssWarn: "이 설정은 기능을 알고 있는 경우에만 사용해야 합니다. 잘못된 값을 입력하면 클라이언트가 정상적으로 작동하지 않을 수 있습니다." global: "글로벌" -squareAvatars: "프로필 아이콘을 사각형으로 표시" +squareAvatars: "고양이가 아닌 프로필 아이콘을 사각형으로 표시" sent: "전송" received: "수신" searchResult: "검색 결과" @@ -942,7 +942,8 @@ _accountDelete: inProgress: "삭제 진행 중" _ad: back: "뒤로" - reduceFrequencyOfThisAd: "이 광고의 표시 빈도 낮추기" + reduceFrequencyOfThisAd: "이 배너의 표시 빈도 낮추기" + adsBy: '{by}에 의한 커뮤니티 배너' _forgotPassword: enterEmail: "여기에 계정에 등록한 메일 주소를 입력해 주세요. 입력한 메일 주소로 비밀번호 재설정 링크를 발송합니다." ifNoEmail: "메일 주소를 등록하지 않은 경우, 서버 관리자에게 문의해 주십시오." @@ -1122,6 +1123,12 @@ _wordMute: soft: "보통" hard: "보다 높은 수준" mutedNotes: "뮤트된 게시물" + mutePatterns: 뮤트할 패턴 + langDescription: 설정한 언어와 일치하는 게시물을 타임라인에서 숨깁니다. + lang: 언어 + muteLangs: 뮤트할 언어 + muteLangsDescription: OR의 경우 공백 또는 줄바꿈으로 구분하십시오. + muteLangsDescription2: en, fr, ja, zh 와 같은 언어 코드를 입력하세요. _instanceMute: instanceMuteDescription: "뮤트한 서버에서 오는 답글을 포함한 모든 게시물과 부스트를 뮤트합니다." instanceMuteDescription2: "한 줄에 하나씩 입력해 주세요" @@ -1277,6 +1284,10 @@ _permissions: "write:gallery": "갤러리를 추가하거나 삭제합니다" "read:gallery-likes": "갤러리의 좋아요를 확인합니다" "write:gallery-likes": "갤러리에 좋아요를 추가하거나 취소합니다" + push: 푸시 알림 보내기 + read: 읽기 (타임라인, 알림, 리액션, 뮤트, 계정 정보 등) + write: 쓰기 (게시물 작성, 리액션 보내기, 뮤트하기, 계정 정보 수정 등) + follow: 계정 팔로우 및 팔로우 해제 _auth: shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?" shareAccessAsk: "이 애플리케이션이 계정에 접근하는 것을 허용하시겠습니까?" @@ -1760,6 +1771,7 @@ _notification: reacted: 님의 리액션 renoted: 님이 부스트 voted: 님이 투표함 + andCountUsers: 와(과) {count}명이 {acted} _deck: alwaysShowMainColumn: "메인 칼럼 항상 표시" columnAlign: "칼럼 정렬" @@ -1803,7 +1815,7 @@ privateMode: 비공개 모드 audio: 오디오 customKaTeXMacro: 커스텀 KaTeX 매크로 replayTutorial: 튜토리얼 다시 보기 -renoteMute: 부스트 뮤트 +renoteMute: 타임라인에서 부스트 숨기기 antennaInstancesDescription: 서버 호스트를 한 줄에 하나씩 입력하세요 userSaysSomethingReason: '{name} 님이 {reason}에 대해 말했습니다' userSaysSomethingReasonQuote: '{name} 님이 {reason} 을 포함하는 게시물을 인용했습니다' @@ -1874,11 +1886,11 @@ selectChannel: 채널 선택 allowedInstancesDescription: 연합을 허가하려는 서버를 한 줄에 하나씩 입력합니다. 비공개 모드에서만 유효합니다. splash: 스플래시 화면 preventAiLearningDescription: 업로드한 게시물이나 미디어를 AI 모델이 학습하지 말기를 요구합니다. -isBot: 이 계정은 봇입니다 +isBot: 이 계정은 자동화된 계정입니다 isAdmin: 관리자 newer: 새로운 게시물 older: 이전 게시물 -renoteUnmute: 부스트 뮤트 해제 +renoteUnmute: 타임라인에서 부스트 보이기 accountMoved: '이 유저는 다른 계정으로 이사했습니다:' silencedInstances: 사일런스한 서버 accessibility: 접근성 @@ -1917,8 +1929,7 @@ license: 라이선스 migrationConfirm: "정말로 이 계정을 {account}로 이사하시겠습니까? 한 번 이사하면, 현재 이 계정은 두 번 다시 사용할 수 없게 됩니다.\n또한, 이사 갈 계정에 현재 사용 중인 계정의 별칭을 올바르게 작성하였는지 다시 한 번 확인하십시오." noteId: 게시물 ID -signupsDisabled: 현재 이 서버에서는 신규 등록을 받고 있지 않습니다. 초대 코드를 가지고 계신 경우 아래 칸에 입력해 주십시오. 초대 - 코드를 가지고 있지 않더라도, 신규 등록이 열려 있는 다른 서버에 등록하실 수 있습니다! +signupsDisabled: 현재 이 서버에서는 신규 등록을 받고 있지 않습니다. 초대 코드를 가지고 계신 경우 아래 칸에 입력해 주십시오. apps: 앱 preventAiLearning: AI에 의한 학습을 막기 isLocked: 이 계정은 팔로우를 수동으로 승인합니다 @@ -2002,3 +2013,108 @@ renotes: 부스트 quotes: 인용 sentFollowRequests: 팔로우 요청 보냄 reactions: 리액션 +searchUsers: 작성한 사람 (옵션) +replaceChatButtonWithAccountButton: 채팅 버튼을 계정 전환으로 변경 +searchEngine: 검색 MFM에서 사용할 검색 엔진 +turnOffCatLanguage: 냥체 변환을 끄기 +noSentFollowRequests: 승인 대기중인 팔로우 요청이 없습니다 +squareCatAvatars: 고양이 계정의 아이콘을 사각형으로 표시 +useThisAccountConfirm: 이 계정으로 계속 진행하시겠습니까? +inputAccountId: '당신의 계정을 입력해 주세요 (예시: @firefish@info.firefish.dev)' +pullToRefreshThreshold: 새로고침하기 위해 아래로 당길 길이 +pullDownToReload: 아래로 당겨 새로고침 +searchRange: 게시시각 범위 (옵션) +showAddFileDescriptionAtFirstPost: 설명이 없는 첨부파일을 게시하려고 할 때에 자동으로 설명 작성창을 열기 +searchCwAndAlt: CW 주석과 미디어 설명에서도 검색하기 +publishTimelines: 비로그인 유저에게 타임라인을 공개 +addAlt4MeTag: '설명이 없는 파일을 게시할 때에 자동으로 #Alt4Me 해시태그를 포함하여 게시' +_emojiModPerm: + unauthorized: 없음 + full: 모두 허용 + add: 추가 + mod: 추가 및 편집 +openServerInfo: 게시물의 서버 이름을 클릭하여 서버 정보 열기 +iconSet: 아이콘 스타일 +showBigPostButton: 게시물 작성 폼에서 게시 버튼을 매우 크게 하기 +emojiModPerm: 커스텀 이모지 관리 권한 +releaseToReload: 놓아서 새로고침 +searchWords: 검색할 단어 / 조회할 ID 및 URL +_later: + secondsAgo: '{n}초 후' + minutesAgo: '{n}분 후' + hoursAgo: '{n}시간 후' + daysAgo: '{n}일 후' + weeksAgo: '{n}주 후' + monthsAgo: '{n}개월 후' + future: 미래 + justNow: 잠시 후 + yearsAgo: '{n}년 후' +makePrivateConfirm: 리모트 서버에 삭제 요청을 보내고, 게시물의 공개 범위를 '비공개'로 전환합니다. 계속하시겠습니까? +markLocalFilesNsfwByDefault: 모든 로컬 파일을 기본으로 NSFW로 처리 +markLocalFilesNsfwByDefaultDescription: 이 설정이 켜져 있더라도 유저는 직접 NSFW 설정을 해제할 수 있으며, 이 + 설정은 기존 파일에는 적용되지 않습니다. +showAttachedNotes: 이 파일이 첨부된 게시물 보기 +noLanguage: 언어 없음 +toReply: 답글 +toQuote: 인용 +replyMute: 타임라인에서 답글 숨기기 +searchPostsWithFiles: 첨부 파일이 있는 게시물만 검색 +antennaLimit: 각 유저가 생성 가능한 최대 안테나 수 +forMobile: 모바일 +reloading: 새로고침하는 중 +noAltTextWarning: 미디어 설명이 없는 파일이 있습니다. 깜빡하고 안 쓰셨나요? +publishTimelinesDescription: 활성화하면 로그인하지 않아도 {url} 에서 로컬 타임라인과 글로벌 타임라인을 확인할 수 있게 + 됩니다. +cannotEditVisibility: 공개 범위를 변경할 수 없습니다 +private: 비공개 +preventMisclick: 클릭 실수 방지 +strongPassword: 강한 비밀번호 +_iconSets: + light: 얇음 + regular: 보통 + bold: 굵음 +i18nServerInfo: 새 단말부터 {language}가 기본값으로 적용됩니다. +privateDescription: 나를 제외한 모두에게 비공개 +enableTimelineStreaming: 타임라인을 자동으로 갱신 +toPost: 게시 +replyUnmute: 타임라인에서 답글 보이기 +attachedToNotes: 이 파일이 첨부된 게시물 +ipFirstAcknowledged: IP 주소를 처음 발견한 날짜 +driveCapacityOverride: 드라이브 용량 변경 +remoteFollow: 리모트 팔로우 +emojiModPermDescription: "추가: 커스텀 이모지의 신규 추가와 태그/카테고리/라이선스를 신규 지정할 수 있습니다.\n추가 및 편집: + '추가'에 더해, 기존 이모지의 이름/카테고리/태그/라이선스를 변경할 수 있습니다.\n모두 허용: '추가 및 편집'에 더해, 기존 커스텀 이모지의 + 삭제를 허가합니다.\n관리자 및 모더레이터는 이 설정을 무시하고 '모두 허용' 권한이 주어집니다." +searchWordsDescription: "게시물을 검색하시려면, 여기에 검색어를 입력하여 주십시오. 공백으로 구분하여 AND, 'OR'으로 구분하여 + OR 검색이 가능합니다.\n예를 들어, '아침 저녁'이라고 검색할 경우 '아침'과 '저녁'을 동시에 포함하는 게시물을 검색하며, '아침 OR 저녁'이라고 + 검색할 경우 '아침' 또는 '저녁' 둘 중 하나가 포함된 게시물을 검색합니다.\n-(대시) 부호를 붙여 특정 단어를 제외할 수 있으며, AND/OR/제외는 + 괄호를 사용하여 동시에 사용할 수 있습니다. '(아침 OR 저녁) 졸려 -식곤증' 과 같이 사용합니다.\n띄어쓰기가 포함된 문자열을 검색하려는 + 경우 \"오늘 뭐하지\"와 같이 큰따옴표를 사용하십시오.\n\n특정 유저나 게시물 페이지로 이동하시려면, 이 칸에 ID 또는 URL을 입력하고 + '조회'를 눌러 주십시오. '검색'을 누르면 해당 ID 또는 URL이 포함된 게시물을 검색합니다." +searchUsersDescription: "특정 게시자가 올린 게시물을 검색할 경우, @user@example.com 혹은 (로컬 유저의 경우) + @user와 같이 게시자의 ID를 입력하십시오. 도메인을 입력하여 해당 서버의 모든 유저가 작성한 게시물을 검색할 수 있습니다.\n\nme 를 + 입력하면 자신의 게시물을 검색합니다. 자신의 검색 결과에는 미등재, 팔로워, 다이렉트, 비공개 게시물이 모두 포함됩니다.\n\nlocal 을 입력하면 + 이 서버의 게시물을 검색합니다." +searchRangeDescription: "게시 시각을 지정하실 경우, 20220615-20231031 과 같이 입력하십시오.\n\n올해 안에서 + 검색하려면 연도를 생략할 수 있습니다. (0105-0106 또는 20231105-0110 과 같이)\n\n시작 날짜와 종료 날짜를 생략할 수 있습니다. + 예를 들어 -0102 의 경우 올해 1월 2일까지의 게시물을, 20231026- 의 경우 2023년 10월 26일 이후의 게시물을 검색합니다." +showNoAltTextWarning: 설명이 없는 첨부 파일을 게시할 때에 경고 +vibrate: 진동 활성화 +clickToShowPatterns: 클릭하여 트랙을 표시 +announcement: 공지사항 +toEdit: 편집 +media: 미디어 +i18nServerChange: '{language}를 대신해서 사용합니다.' +i18nServerSet: 새 기기의 표시 언어를 {language} 로 설정합니다. +getQrCode: QR 코드 표시 +copyRemoteFollowUrl: 리모트 팔로우 URL 복사 +useCdn: 일부 구성요소를 CDN에서 가져오기 +useCdnDescription: Twemoji와 같은 일부 정적 구성요소를 JSDelivr CDN에서 가져옵니다. 비활성화할 경우 이 서버에서 가져옵니다. +suggested: 추천 +showPreviewByDefault: 게시물 작성 화면에서 항상 미리보기 켜기 +hideFollowButtons: 잘못 누르기 쉬운 위치에 있는 팔로우 버튼을 숨기기 +replaceWidgetsButtonWithReloadButton: 위젯 버튼을 새로고침으로 변경 +postSearch: 이 서버의 게시물 검색 +makePrivate: 비공개로 전환 +enablePullToRefresh: "'아래로 당겨 새로고침'을 활성화" +moderationNote: 모더레이션 노트 diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 3d80c0f..256aea2 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -478,7 +478,7 @@ existingAccount: "现有的账号" regenerate: "重新生成" fontSize: "字体大小" noFollowRequests: "没有待批准的关注申请" -noSentFollowRequests: "没有待回应的关注请求" +noSentFollowRequests: "尚未发送任何关注请求" openImageInNewTab: "在新标签页中打开图片" dashboard: "管理面板" local: "本地" @@ -1012,6 +1012,13 @@ _nsfw: respect: "隐藏敏感内容" ignore: "不隐藏敏感内容" force: "总是隐藏内容" +writingMode: "书写方向" +_writingMode: + horizontalTB: "横排,由上向下" + verticalLR: "直排,由左向右,字母旋转90°" + verticalRL: "直排,由右向左,字母旋转90°" + verticalLRUpright: "直排,由左向右,字母逐个排列" + verticalRLUpright: "直排,由右向左,字母逐个排列" _mfm: cheatSheet: "MFM 代码速查表" intro: "MFM 是一种在 Misskey、Firefish、Akkoma 中使用的标记语言,可以在很多地方使用。您可以在此处查看所有可用的 MFM 语法的列表。" @@ -1322,6 +1329,10 @@ _permissions: "write:gallery": "编辑图库" "read:gallery-likes": "读取喜欢的图片" "write:gallery-likes": "编辑喜欢的图片" + read: 读取(时间线、通知、回应、静音、账户信息等) + write: 修改(发表帖子、回应帖子、静音用户、编辑用户信息等) + push: 发送推送通知 + follow: 关注和取消关注账号 _auth: shareAccess: "您要授权允许 \"{name}\" 访问您的账号吗?" shareAccessAsk: "您确定要授权此应用访问您的账号吗?" @@ -1990,7 +2001,7 @@ origin: 起源 confirm: 确认 importZip: 导入 ZIP exportZip: 导出 ZIP -getQrCode: "获取二维码" +getQrCode: "显示二维码" remoteFollow: "远程关注" copyRemoteFollowUrl: "复制远程关注 URL" objectStorageS3ForcePathStyleDesc: 打开此选项可构建格式为 "s3.amazonaws.com//" 而非 ".s3.amazonaws.com" @@ -2027,7 +2038,7 @@ _emojiModPerm: unauthorized: 未授权 full: 全部允许 moreUrls: 置顶页面 -enablePullToRefresh: 启用 “下拉刷新” +enablePullToRefresh: 启用“下拉刷新” pullToRefreshThreshold: 触发下拉刷新所需的距离 pullDownToReload: 下拉刷新 releaseToReload: 释放刷新 @@ -2064,10 +2075,10 @@ searchCwAndAlt: 包括内容警告和文件描述 publishTimelines: 为访客发布时间线 publishTimelinesDescription: 如果启用,在用户登出时本地和全局时间线也会显示在 {url} 上。 searchWordsDescription: "在此处输入搜索词以搜索帖子。交集搜索关键词之间使用空格进行区分,并集搜索关键词之间使用 OR 进行区分。\n例如 - '早上 晚上' 将查找包含 '早上' 和 '晚上' 的帖子,而 '早上 OR 晚上' 将查找包含 '早上' 或 '晚上' (以及同时包含两者)的帖子。\n您还可以组合交集/并集条件,例如 - '(早上 OR 晚上) 困了' 。\n如果您想搜索单词序列(例如一个英语句子),您必须将其放在双引号中,例如 \"Today I learned\" 以区分于交集搜索。\n - \n如果您想转到特定的用户页面或帖子页面,请在此字段中输入用户 ID 或 URL,然后单击 “查询” 按钮。 单击 “搜索” 将搜索字面包含用户 ID/URL - 的帖子。" + '早上 晚上' 将查找包含 '早上' 和 '晚上' 的帖子,而 '早上 OR 晚上' 将查找包含 '早上' 或 '晚上' (以及同时包含两者)的帖子。\n您也可以从搜索结果中过滤部分关键词,例如 + '困了 -早上 -早餐'。此外,您还可以组合交集/并集条件,例如 '(早上 OR 晚上) 困了 -早餐' 。\n如果您想搜索单词序列(例如一个英语句子),您必须将其放在双引号中,例如 + \"Today I learned\" 以区分于交集搜索。\n \n如果您想转到特定的用户页面或帖子页面,请在此字段中输入用户 ID 或 URL,然后单击 “查询” + 按钮。 单击 “搜索” 将搜索字面包含用户 ID/URL 的帖子。" searchRangeDescription: "如果您要过滤时间段,请按以下格式输入:20220615-20231031\n\n如果您省略年份(例如 0105-0106 或 20231105-0110),它将被解释为当前年份。\n\n您还可以省略开始日期或结束日期。 例如 -0102 将过滤搜索结果以仅显示今年 1 月 2 日之前发布的帖子,而 20231026- 将过滤结果以仅显示 2023 年 10 月 26 日之后发布的帖子。" @@ -2076,7 +2087,7 @@ noAltTextWarning: 有些附件没有描述。您是否忘记写描述了? showNoAltTextWarning: 当您尝试发布没有描述的帖子附件时显示警告 showAddFileDescriptionAtFirstPost: 当您首次尝试发布没有描述的帖子附件时自动弹出添加描述页面 autocorrectNoteLanguage: 当帖子语言不符合自动检测的结果的时候显示警告 -incorrectLanguageWarning: "看上去您帖子使用的语言是{detected},但您选择的语言是{current}。\n要改为以{detected}发帖吗?" +incorrectLanguageWarning: "检测到帖子语言可能是{detected}。\n要将发帖语言从{current}更改为{detected}吗?" noteEditHistory: "帖子编辑历史" media: 媒体 slashQuote: "斜杠引用" @@ -2087,3 +2098,6 @@ scheduledPostAt: "帖子将于 {time} 发送" scheduledDate: "发送日期" mergeThreadInTimeline: "将时间线内的连续回复合并成一串" mergeRenotesInTimeline: "合并同一个帖子的转发" +turnOffCatLanguage: 关闭猫语转换 +strongPassword: 密码强度:良好 +addAlt4MeTag: '当附件没有描述时,自动在帖子中添加 #Alt4Me 标签' diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 6c8f1c4..c1bcb98 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -1004,6 +1004,13 @@ _nsfw: respect: "隱藏敏感內容" ignore: "不隱藏敏感內容" force: "隱藏所有內容" +writingMode: "書寫方向" +_writingMode: + horizontalTB: "橫排,由上而下" + verticalLR: "直排,由左而右,字母旋轉90°" + verticalRL: "直排,由右而左,字母旋轉90°" + verticalLRUpright: "直排,由左而右,字母逐個排列" + verticalRLUpright: "直排,由右而左,字母逐個排列" _mfm: cheatSheet: "MFM代碼小抄" intro: "MFM是Misskey、Firefish、Akkoma等專用的標記語言,可以在各個位置使用。 您可以這裏看到MFM可用語法列表。" @@ -1385,7 +1392,7 @@ _poll: _visibility: public: "公開" publicDescription: "發佈至公開時間軸" - home: "不在主頁顯示" + home: "悄悄公開" homeDescription: "僅發送至首頁的時間軸" followers: "追隨者" followersDescription: "僅發佈至關注者" diff --git a/package.json b/package.json index e6cffd1..bcddd2e 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { - "name": "firefish", - "version": "20240729", + "name": "puyoskey", + "version": "v0.1.0-20240818", "repository": { "type": "git", - "url": "https://firefish.dev/firefish/firefish.git" + "url": "https://git.v-sli.me/HidemaruOwO/puyoskey.git" }, - "packageManager": "pnpm@9.6.0", + "packageManager": "pnpm@9.7.1", "private": true, "scripts": { "rebuild": "pnpm run clean && pnpm run build", @@ -47,8 +47,8 @@ "@biomejs/cli-darwin-x64": "1.8.3", "@biomejs/cli-linux-arm64": "1.8.3", "@biomejs/cli-linux-x64": "1.8.3", - "@types/node": "20.14.13", - "execa": "9.3.0", - "pnpm": "9.6.0" + "@types/node": "20.15.0", + "execa": "9.3.1", + "pnpm": "9.7.1" } } diff --git a/packages/backend-rs/Cargo.toml b/packages/backend-rs/Cargo.toml index 54efdde..07e587e 100644 --- a/packages/backend-rs/Cargo.toml +++ b/packages/backend-rs/Cargo.toml @@ -27,7 +27,9 @@ bcrypt = { workspace = true, features = ["std"] } chrono = { workspace = true } cuid2 = { workspace = true } emojis = { workspace = true } +error-doc = { workspace = true } futures-util = { workspace = true, features = ["io"] } +identicon-rs = { workspace = true } idna = { workspace = true, features = ["std", "compiled_data"] } image = { workspace = true, features = ["avif", "bmp", "gif", "ico", "jpeg", "png", "tiff", "webp"] } isahc = { workspace = true, features = ["http2", "text-decoding", "json"] } @@ -42,13 +44,14 @@ sea-orm = { workspace = true, features = ["macros", "runtime-tokio-rustls", "sql serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } serde_yaml = { workspace = true } -sysinfo = { workspace = true } +sysinfo = { workspace = true, features = ["system", "disk"] } thiserror = { workspace = true } tokio = { workspace = true, features = ["fs", "io-std", "io-util", "macros", "process", "rt-multi-thread", "signal", "sync", "time"] } tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["ansi"] } url = { workspace = true } urlencoding = { workspace = true } +uuid = { workspace = true, features = ["v4", "fast-rng"] } web-push = { workspace = true, features = ["isahc-client"] } zhconv = { workspace = true } diff --git a/packages/backend-rs/index.d.ts b/packages/backend-rs/index.d.ts index d939266..c7295b5 100644 --- a/packages/backend-rs/index.d.ts +++ b/packages/backend-rs/index.d.ts @@ -48,7 +48,19 @@ export interface Acct { export declare function acctToString(acct: Acct): string -export type Activity = 'Follow'; +export type Activity = 'Accept'| +'Add'| +'Emoji'| +'Flag'| +'Follow'| +'Hashtag'| +'Like'| +'Mention'| +'Image'| +'Read'| +'Reject'| +'Remove'| +'Tombstone'; export interface Ad { id: string @@ -106,6 +118,63 @@ export type AntennaSrc = 'all'| 'list'| 'users'; +export interface ApAccept { + id: string + type: Activity + actor: string + object: ApFollow +} + +export interface ApAdd { + type: Activity + actor: string + target: string + object: string +} + +export interface ApEmoji { + id: string + type: Activity + name: string + updated: string + icon: Icon +} + +export interface ApFlag { + type: Activity + actor: string + content: string + object: string +} + +export interface ApFollow { + id: string + type: Activity + actor: string + object: string +} + +export interface ApHashtag { + id: string + type: Activity + name: string +} + +export interface ApLike { + id: string + type: Activity + actor: string + object: string + content: string + tag?: Array +} + +export interface ApMention { + type: Activity + href: string + name: string +} + export interface App { id: string createdAt: DateTimeWithTimeZone @@ -117,6 +186,31 @@ export interface App { callbackUrl: string | null } +export interface ApRead { + type: Activity + actor: string + object: string +} + +export interface ApReject { + id: string + type: Activity + actor: string + object: ApFollow +} + +export interface ApRemove { + type: Activity + actor: string + target: string + object: string +} + +export interface ApTombstone { + id: string + type: Activity +} + export interface AttestationChallenge { id: string userId: string @@ -425,13 +519,6 @@ export interface Following { followeeSharedInbox: string | null } -export interface FollowRelay { - id: string - type: Activity - actor: string - object: string -} - export interface FollowRequest { id: string createdAt: DateTimeWithTimeZone @@ -487,6 +574,8 @@ export declare function genId(): string /** Generate an ID using a specific datetime */ export declare function genIdAt(date: Date): string +export declare function genIdenticon(id: string): Promise + export declare function getFullApAccount(username: string, host?: string | undefined | null): string export declare function getImageSizeFromUrl(url: string): Promise @@ -522,6 +611,12 @@ export interface Hashtag { attachedRemoteUsersCount: number } +export interface Icon { + type: Activity + mediaType: string + url: string +} + export interface IdConfig { length?: number fingerprint?: string @@ -1265,7 +1360,31 @@ export type RelayStatus = 'accepted'| /** Delete all entries in the [attestation_challenge] table created at more than 5 minutes ago */ export declare function removeOldAttestationChallenges(): Promise -export declare function renderFollowRelay(relayId: string): Promise +export declare function renderAccept(userId: string, followObject: ApFollow): ApAccept + +export declare function renderAdd(userId: string, noteId: string): ApAdd + +export declare function renderEmoji(emoji: Emoji): ApEmoji + +export declare function renderFlag(targetUserUri: string, comment: string): Promise + +export declare function renderFollow(follower: UserLike, followee: UserLike, requestId?: string | undefined | null): ApFollow + +export declare function renderFollowRelay(relayId: string): Promise + +export declare function renderHashtag(tagName: string): ApHashtag + +export declare function renderLike(reaction: Model): Promise + +export declare function renderMention(user: UserLike): ApMention + +export declare function renderRead(userId: string, messageUri: string): ApRead + +export declare function renderReject(userId: string, followObject: ApFollow): ApReject + +export declare function renderRemove(userId: string, noteId: string): ApRemove + +export declare function renderTombstone(noteId: string): ApTombstone export interface RenoteMuting { id: string @@ -1423,8 +1542,6 @@ export declare function updateAntennasOnNewNote(note: Note, noteAuthor: Acct, no export declare function updateMetaCache(): Promise -export declare function updateNodeinfoCache(): Promise - /** Usage statistics for this server. */ export interface Usage { users: Users @@ -1537,6 +1654,13 @@ export interface UserKeypair { privateKey: string } +export interface UserLike { + id: string + username: string + host: string | null + uri: string | null +} + export interface UserList { id: string createdAt: DateTimeWithTimeZone diff --git a/packages/backend-rs/index.js b/packages/backend-rs/index.js index 9ca4963..4d63d6d 100644 --- a/packages/backend-rs/index.js +++ b/packages/backend-rs/index.js @@ -384,6 +384,7 @@ module.exports.generateSecureRandomString = nativeBinding.generateSecureRandomSt module.exports.generateUserToken = nativeBinding.generateUserToken module.exports.genId = nativeBinding.genId module.exports.genIdAt = nativeBinding.genIdAt +module.exports.genIdenticon = nativeBinding.genIdenticon module.exports.getFullApAccount = nativeBinding.getFullApAccount module.exports.getImageSizeFromUrl = nativeBinding.getImageSizeFromUrl module.exports.getInstanceActor = nativeBinding.getInstanceActor @@ -437,7 +438,19 @@ module.exports.PushNotificationKind = nativeBinding.PushNotificationKind module.exports.PushSubscriptionType = nativeBinding.PushSubscriptionType module.exports.RelayStatus = nativeBinding.RelayStatus module.exports.removeOldAttestationChallenges = nativeBinding.removeOldAttestationChallenges +module.exports.renderAccept = nativeBinding.renderAccept +module.exports.renderAdd = nativeBinding.renderAdd +module.exports.renderEmoji = nativeBinding.renderEmoji +module.exports.renderFlag = nativeBinding.renderFlag +module.exports.renderFollow = nativeBinding.renderFollow module.exports.renderFollowRelay = nativeBinding.renderFollowRelay +module.exports.renderHashtag = nativeBinding.renderHashtag +module.exports.renderLike = nativeBinding.renderLike +module.exports.renderMention = nativeBinding.renderMention +module.exports.renderRead = nativeBinding.renderRead +module.exports.renderReject = nativeBinding.renderReject +module.exports.renderRemove = nativeBinding.renderRemove +module.exports.renderTombstone = nativeBinding.renderTombstone module.exports.safeForSql = nativeBinding.safeForSql module.exports.sendPushNotification = nativeBinding.sendPushNotification module.exports.shouldNyaify = nativeBinding.shouldNyaify @@ -453,7 +466,6 @@ module.exports.unwatchNote = nativeBinding.unwatchNote module.exports.updateAntennaCache = nativeBinding.updateAntennaCache module.exports.updateAntennasOnNewNote = nativeBinding.updateAntennasOnNewNote module.exports.updateMetaCache = nativeBinding.updateMetaCache -module.exports.updateNodeinfoCache = nativeBinding.updateNodeinfoCache module.exports.UserEmojiModPerm = nativeBinding.UserEmojiModPerm module.exports.UserEvent = nativeBinding.UserEvent module.exports.UserProfileFfvisibility = nativeBinding.UserProfileFfvisibility diff --git a/packages/backend-rs/package.json b/packages/backend-rs/package.json index a33effc..dbc6aa9 100644 --- a/packages/backend-rs/package.json +++ b/packages/backend-rs/package.json @@ -11,7 +11,8 @@ "@napi-rs/cli": "3.0.0-alpha.62" }, "scripts": { - "build": "napi build --features napi --no-const-enum --platform --release --output-dir ./built/", + "fetch": "cargo fetch --locked --manifest-path ../../Cargo.toml", + "build": "pnpm run fetch && napi build --features napi --no-const-enum --platform --release --output-dir ./built/ -- --frozen", "build:debug": "napi build --features napi --no-const-enum --platform --output-dir ./built/ --dts-header '/* auto-generated by NAPI-RS */\n/* Do NOT edit this file manually */\n\ntype DateTimeWithTimeZone = Date;\n\ntype Json = any;\n\n'" } } diff --git a/packages/backend-rs/src/cache/bare.rs b/packages/backend-rs/src/cache/bare.rs new file mode 100644 index 0000000..a930d01 --- /dev/null +++ b/packages/backend-rs/src/cache/bare.rs @@ -0,0 +1,199 @@ +//! In-memory cache handler + +use chrono::{DateTime, Duration, Utc}; +use std::sync::Mutex; + +/// Cache stored directly in memory +pub struct Cache { + cache: Mutex>, + ttl: Option, +} + +struct TimedData { + value: Option, + last_updated: DateTime, +} + +impl Default for Cache { + fn default() -> Self { + Self::new() + } +} + +impl Cache { + /// Creates a new cache object with no auto invalidation. + pub const fn new() -> Self { + Self { + cache: Mutex::new(TimedData { + value: None, + last_updated: DateTime::UNIX_EPOCH, + }), + ttl: None, + } + } + + /// Creates a new cache object whose content is invalidated + /// in the specified duration. + /// + /// # Example + /// ``` + /// # use backend_rs::cache::Cache; + /// use chrono::Duration; + /// static CACHE: Cache = Cache::new_with_ttl(Duration::seconds(1)); + /// + /// fn use_cache() { + /// let data = 998244353; + /// + /// // Set cache + /// CACHE.set(data); + /// + /// // wait for the cache to expire + /// std::thread::sleep(std::time::Duration::from_millis(1100)); + /// + /// // Get cache + /// let cache = CACHE.get(); + /// + /// assert!(cache.is_none()); + /// } + /// ``` + pub const fn new_with_ttl(ttl: Duration) -> Self { + Self { + cache: Mutex::new(TimedData { + value: None, + last_updated: DateTime::UNIX_EPOCH, + }), + ttl: Some(ttl), + } + } + + /// Sets a cache. This function overwrites the existing data. + /// + /// # Example + /// ``` + /// # use backend_rs::cache::Cache; + /// static CACHE: Cache = Cache::new(); + /// + /// fn use_cache() { + /// let data = 998244353; + /// + /// // Set cache + /// CACHE.set(data); + /// + /// // Get cache + /// let cache = CACHE.get(); + /// + /// if let Some(cached_data) = cache { + /// println!("found a cached value"); + /// assert_eq!(data, cached_data) + /// } else { + /// println!("cache not found"); + /// } + /// } + /// ``` + pub fn set(&self, value: T) { + if self.ttl.is_none() { + let mut cache = match self.cache.lock() { + Ok(cache) => cache, + Err(err) => err.into_inner(), + }; + cache.value = Some(value); + } else { + let mut cache = match self.cache.lock() { + Ok(cache) => cache, + Err(err) => err.into_inner(), + }; + + *cache = TimedData { + value: Some(value), + last_updated: Utc::now(), + }; + } + } + + /// Gets a cache. Returns [`None`] is the cache is not set or expired. + pub fn get(&self) -> Option { + let data = self.cache.lock().ok()?; + + if let Some(ttl) = self.ttl { + if data.last_updated + ttl < Utc::now() { + return None; + } + } + data.value.to_owned() + } +} + +#[cfg(test)] +mod unit_test { + use super::Cache; + use chrono::Duration; + use pretty_assertions::assert_eq; + + #[derive(Clone, Debug, PartialEq)] + struct Data { + id: u64, + name: String, + } + + #[test] + fn set_and_get() { + static CACHE: Cache = Cache::new(); + static CACHE_WITH_TTL: Cache = Cache::new_with_ttl(Duration::seconds(1)); + + let data = Data { + id: 16, + name: "Firefish".to_owned(), + }; + + assert!(CACHE.get().is_none()); + assert!(CACHE_WITH_TTL.get().is_none()); + + CACHE.set(data.clone()); + assert_eq!(data, CACHE.get().unwrap()); + + CACHE_WITH_TTL.set(data.clone()); + assert_eq!(data, CACHE_WITH_TTL.get().unwrap()); + } + + #[test] + fn expire() { + static CACHE: Cache = Cache::new_with_ttl(Duration::seconds(1)); + + let data = Data { + id: 16, + name: "Firefish".to_owned(), + }; + + CACHE.set(data); + + std::thread::sleep(std::time::Duration::from_millis(1100)); + + assert!(CACHE.get().is_none()); + } + + static GLOBAL_CACHE_1: Cache = Cache::new(); + static GLOBAL_CACHE_2: Cache = Cache::new_with_ttl(Duration::milliseconds(2)); + + #[tokio::test] + async fn use_cache_in_parallel() { + let mut tasks = Vec::new(); + + async fn f() -> Data { + Data { + id: rand::random(), + name: cuid2::create_id(), + } + } + + for _ in 0..20 { + tasks.push(tokio::spawn(async { + GLOBAL_CACHE_1.set(f().await); + GLOBAL_CACHE_2.set(f().await); + (GLOBAL_CACHE_1.get(), GLOBAL_CACHE_2.get()) + })) + } + for task in tasks { + task.await.unwrap(); + } + } +} diff --git a/packages/backend-rs/src/cache/mod.rs b/packages/backend-rs/src/cache/mod.rs new file mode 100644 index 0000000..c68f689 --- /dev/null +++ b/packages/backend-rs/src/cache/mod.rs @@ -0,0 +1,7 @@ +//! Cache handlers + +pub mod bare; +pub mod redis; + +pub use bare::Cache; +pub use redis::{delete, delete_all, delete_one, get, get_one, set, set_one, Category}; diff --git a/packages/backend-rs/src/cache/redis.rs b/packages/backend-rs/src/cache/redis.rs new file mode 100644 index 0000000..dfa5f3f --- /dev/null +++ b/packages/backend-rs/src/cache/redis.rs @@ -0,0 +1,332 @@ +//! Utilities for using Redis cache + +use crate::database::{redis_conn, redis_key, RedisConnError}; +use chrono::Duration; +use redis::{AsyncCommands, RedisError}; +use serde::{Deserialize, Serialize}; + +#[cfg_attr(test, derive(Debug))] +pub enum Category { + FetchUrl, + Block, + Follow, + CatLang, + RandomIcon, + #[cfg(test)] + Test, +} + +#[macros::errors] +pub enum Error { + #[error("failed to execute Redis command")] + Redis(#[from] RedisError), + #[error("bad Redis connection")] + RedisConn(#[from] RedisConnError), + #[error("failed to encode data for Redis")] + Encode(#[from] rmp_serde::encode::Error), + #[error("invalid cache TTL")] + TTL, +} + +#[inline] +fn prefix_key(key: &str) -> String { + redis_key(format!("cache:{}", key)) +} + +fn categorize(category: Category, key: &str) -> String { + let prefix = match category { + Category::FetchUrl => "fetchUrl", + Category::Block => "blocking", + Category::Follow => "following", + Category::CatLang => "catlang", + Category::RandomIcon => "randomIcon", + #[cfg(test)] + Category::Test => "usedOnlyForTesting", + }; + format!("{}:{}", prefix, key) +} + +#[inline] +fn wildcard(category: Category) -> String { + prefix_key(&categorize(category, "*")) +} + +/// Sets a Redis cache. +/// +/// This overwrites the exsisting cache with the same key. +/// +/// # Arguments +/// +/// * `key` : key (prefixed automatically) +/// * `value` : (de)serializable value +/// * `ttl` : cache lifetime +/// +/// # Example +/// +/// ``` +/// # use backend_rs::cache; +/// use chrono::Duration; +/// # async fn f() -> Result<(), Box> { +/// let key = "apple"; +/// let data = "I want to cache this string".to_owned(); +/// +/// // caches the data for 10 seconds +/// cache::set(key, &data, Duration::seconds(10)).await?; +/// +/// // get the cache +/// let cached_data = cache::get::(key).await?; +/// +/// assert!(cached_data.is_some()); +/// assert_eq!(data, cached_data.unwrap()); +/// # Ok(()) +/// # } +/// ``` +pub async fn set Deserialize<'a> + Serialize>( + key: &str, + value: &V, + ttl: Duration, +) -> Result<(), Error> { + Ok(redis_conn() + .await? + .set_ex( + prefix_key(key), + rmp_serde::encode::to_vec(&value)?, + ttl.num_seconds().try_into().map_err(|_| Error::TTL)?, + ) + .await?) +} + +/// Gets a Redis cache. +/// +/// If the Redis connection is fine, this returns `Ok(data)` where `data` +/// is the cached value. Returns `Ok(None)` if there is no value corresponding to `key`. +/// +/// # Argument +/// +/// * `key` : key (will be prefixed automatically) +/// +/// # Example +/// +/// ``` +/// # use backend_rs::cache; +/// use chrono::Duration; +/// # async fn f() -> Result<(), Box> { +/// let key = "banana"; +/// let data = "I want to cache this string".to_owned(); +/// +/// // set cache +/// cache::set(key, &data, Duration::seconds(10)).await?; +/// +/// // get cache +/// let cached_data = cache::get::(key).await?; +/// assert!(cached_data.is_some()); +/// assert_eq!(data, cached_data.unwrap()); +/// +/// // get nonexistent (or expired) cache +/// let no_cache = cache::get::("nonexistent").await?; +/// assert!(no_cache.is_none()); +/// # Ok(()) +/// # } +/// ``` +pub async fn get Deserialize<'a> + Serialize>(key: &str) -> Result, Error> { + let serialized_value: Option> = redis_conn().await?.get(prefix_key(key)).await?; + Ok(match serialized_value { + Some(v) => rmp_serde::from_slice::(v.as_ref()).ok(), + None => None, + }) +} + +/// Deletes a Redis cache. +/// +/// If the Redis connection is fine, this returns `Ok(())` +/// regardless of whether the cache exists. +/// +/// # Argument +/// +/// * `key` : key (prefixed automatically) +/// +/// # Example +/// +/// ``` +/// # use backend_rs::cache; +/// use chrono::Duration; +/// # async fn f() -> Result<(), Box> { +/// let key = "chocolate"; +/// let value = "I want to cache this string".to_owned(); +/// +/// // set cache +/// cache::set(key, &value, Duration::seconds(10)).await?; +/// +/// // delete the cache +/// cache::delete("foo").await?; +/// cache::delete("nonexistent").await?; // this is okay +/// +/// // the cache is gone +/// let cached_value = cache::get::("foo").await?; +/// assert!(cached_value.is_none()); +/// # Ok(()) +/// # } +/// ``` +pub async fn delete(key: &str) -> Result<(), Error> { + Ok(redis_conn().await?.del(prefix_key(key)).await?) +} + +/// Sets a Redis cache under a `category`. +/// +/// The usage is the same as [set], except that you need to +/// use [get_one] and [delete_one] to get/delete the cache. +/// +/// # Arguments +/// +/// * `category` : one of [Category] +/// * `key` : key (prefixed automatically) +/// * `value` : (de)serializable value +/// * `ttl` : cache lifetime +pub async fn set_one Deserialize<'a> + Serialize>( + category: Category, + key: &str, + value: &V, + ttl: Duration, +) -> Result<(), Error> { + set(&categorize(category, key), value, ttl).await +} + +/// Gets a Redis cache under a `category`. +/// +/// The usage is basically the same as [get]. +/// +/// # Arguments +/// +/// * `category` : one of [Category] +/// * `key` : key (prefixed automatically) +pub async fn get_one Deserialize<'a> + Serialize>( + category: Category, + key: &str, +) -> Result, Error> { + get(&categorize(category, key)).await +} + +/// Deletes a Redis cache under a `category`. +/// +/// The usage is basically the same as [delete]. +/// +/// # Arguments +/// +/// - `category` : one of [Category] +/// - `key` : key (prefixed automatically) +pub async fn delete_one(category: Category, key: &str) -> Result<(), Error> { + delete(&categorize(category, key)).await +} + +/// Deletes all Redis caches under a `category`. +/// +/// # Argument +/// +/// * `category` : one of [Category] +pub async fn delete_all(category: Category) -> Result<(), Error> { + let mut redis = redis_conn().await?; + let keys: Vec> = redis.keys(wildcard(category)).await?; + + if !keys.is_empty() { + redis.del(keys).await? + } + + Ok(()) +} + +#[cfg(test)] +mod unit_test { + use super::{delete_all, get, get_one, set, set_one, Category::Test}; + use crate::cache::delete_one; + use chrono::Duration; + use pretty_assertions::assert_eq; + + #[tokio::test] + #[cfg_attr(miri, ignore)] // can't call foreign function `getaddrinfo` on OS `linux` + async fn set_get_expire() { + #[derive(serde::Deserialize, serde::Serialize, PartialEq, Debug)] + struct Data { + id: u32, + kind: String, + } + + let key_1 = "CARGO_TEST_CACHE_KEY_1"; + let value_1: Vec = vec![1, 2, 3, 4, 5]; + + let key_2 = "CARGO_TEST_CACHE_KEY_2"; + let value_2 = "Hello fedizens".to_owned(); + + let key_3 = "CARGO_TEST_CACHE_KEY_3"; + let value_3 = Data { + id: 1000000007, + kind: "prime number".to_owned(), + }; + + set(key_1, &value_1, Duration::seconds(1)).await.unwrap(); + set(key_2, &value_2, Duration::seconds(1)).await.unwrap(); + set(key_3, &value_3, Duration::seconds(1)).await.unwrap(); + + let cached_value_1: Vec = get(key_1).await.unwrap().unwrap(); + let cached_value_2: String = get(key_2).await.unwrap().unwrap(); + let cached_value_3: Data = get(key_3).await.unwrap().unwrap(); + + assert_eq!(value_1, cached_value_1); + assert_eq!(value_2, cached_value_2); + assert_eq!(value_3, cached_value_3); + + // wait for the cache to expire + std::thread::sleep(std::time::Duration::from_millis(1100)); + + let expired_value_1: Option> = get(key_1).await.unwrap(); + let expired_value_2: Option> = get(key_2).await.unwrap(); + let expired_value_3: Option> = get(key_3).await.unwrap(); + + assert!(expired_value_1.is_none()); + assert!(expired_value_2.is_none()); + assert!(expired_value_3.is_none()); + } + + #[tokio::test] + #[cfg_attr(miri, ignore)] // can't call foreign function `getaddrinfo` on OS `linux` + async fn use_category() { + let key_1 = "fire"; + let key_2 = "fish"; + let key_3 = "awawa"; + + let value_1 = "hello".to_owned(); + let value_2 = 998244353u32; + let value_3 = 'あ'; + + set_one(Test, key_1, &value_1, Duration::minutes(5)) + .await + .unwrap(); + set_one(Test, key_2, &value_2, Duration::minutes(5)) + .await + .unwrap(); + set_one(Test, key_3, &value_3, Duration::minutes(5)) + .await + .unwrap(); + + assert_eq!( + get_one::(Test, key_1).await.unwrap().unwrap(), + value_1 + ); + assert_eq!(get_one::(Test, key_2).await.unwrap().unwrap(), value_2); + assert_eq!( + get_one::(Test, key_3).await.unwrap().unwrap(), + value_3 + ); + + delete_one(Test, key_1).await.unwrap(); + + assert!(get_one::(Test, key_1).await.unwrap().is_none()); + assert!(get_one::(Test, key_2).await.unwrap().is_some()); + assert!(get_one::(Test, key_3).await.unwrap().is_some()); + + delete_all(Test).await.unwrap(); + + assert!(get_one::(Test, key_1).await.unwrap().is_none()); + assert!(get_one::(Test, key_2).await.unwrap().is_none()); + assert!(get_one::(Test, key_3).await.unwrap().is_none()); + } +} diff --git a/packages/backend-rs/src/config/meta.rs b/packages/backend-rs/src/config/meta.rs index 969d9d3..18e0c87 100644 --- a/packages/backend-rs/src/config/meta.rs +++ b/packages/backend-rs/src/config/meta.rs @@ -1,31 +1,28 @@ //! Server information -use crate::{database::db_conn, model::entity::meta}; +use crate::{cache::Cache, database::db_conn, model::entity::meta}; +use chrono::Duration; use sea_orm::{prelude::*, ActiveValue}; -use std::sync::Mutex; type Meta = meta::Model; -static CACHE: Mutex> = Mutex::new(None); -fn set_cache(meta: &Meta) { - let _ = CACHE.lock().map(|mut cache| *cache = Some(meta.clone())); -} +static INSTANCE_META_CACHE: Cache = Cache::new_with_ttl(Duration::minutes(5)); #[macros::export(js_name = "fetchMeta")] pub async fn local_server_info() -> Result { - local_server_info_impl(true).await + local_server_info_impl(false).await } #[macros::export(js_name = "updateMetaCache")] pub async fn update() -> Result<(), DbErr> { - local_server_info_impl(false).await?; + local_server_info_impl(true).await?; Ok(()) } -async fn local_server_info_impl(use_cache: bool) -> Result { +async fn local_server_info_impl(force_update_cache: bool) -> Result { // try using cache - if use_cache { - if let Some(cache) = CACHE.lock().ok().and_then(|cache| cache.clone()) { + if !force_update_cache { + if let Some(cache) = INSTANCE_META_CACHE.get() { return Ok(cache); } } @@ -34,7 +31,7 @@ async fn local_server_info_impl(use_cache: bool) -> Result { let db = db_conn().await?; let meta = meta::Entity::find().one(db).await?; if let Some(meta) = meta { - set_cache(&meta); + INSTANCE_META_CACHE.set(meta.clone()); return Ok(meta); } @@ -45,7 +42,7 @@ async fn local_server_info_impl(use_cache: bool) -> Result { }) .exec_with_returning(db) .await?; - set_cache(&meta); + INSTANCE_META_CACHE.set(meta.clone()); Ok(meta) } diff --git a/packages/backend-rs/src/database/mod.rs b/packages/backend-rs/src/database/mod.rs index 03315c1..68c2c60 100644 --- a/packages/backend-rs/src/database/mod.rs +++ b/packages/backend-rs/src/database/mod.rs @@ -6,6 +6,5 @@ pub use redis::get_conn as redis_conn; pub use redis::key as redis_key; pub use redis::RedisConnError; -pub mod cache; pub mod postgresql; pub mod redis; diff --git a/packages/backend-rs/src/federation/activitypub/object/accept.rs b/packages/backend-rs/src/federation/activitypub/object/accept.rs new file mode 100644 index 0000000..f0de0a3 --- /dev/null +++ b/packages/backend-rs/src/federation/activitypub/object/accept.rs @@ -0,0 +1,29 @@ +use super::*; +use crate::misc::user; + +#[macros::export(object)] +pub struct ApAccept { + pub id: String, + pub r#type: Activity, + pub actor: String, + pub object: follow::ApFollow, +} + +impl ApObject for ApAccept {} + +impl ApAccept { + #[allow(dead_code)] // TODO: remove this line by actually using it + fn new(user_id: String, follow_object: follow::ApFollow) -> Self { + Self { + id: random_local_uri(), + r#type: Activity::Accept, + actor: user::local_uri(user_id), + object: follow_object, + } + } +} + +#[macros::ts_export] +pub fn render_accept(user_id: String, follow_object: follow::ApFollow) -> ApAccept { + ApAccept::new(user_id, follow_object) +} diff --git a/packages/backend-rs/src/federation/activitypub/object/add.rs b/packages/backend-rs/src/federation/activitypub/object/add.rs new file mode 100644 index 0000000..183e38c --- /dev/null +++ b/packages/backend-rs/src/federation/activitypub/object/add.rs @@ -0,0 +1,34 @@ +//! Add note to featured collection (pinned posts) + +use super::*; +use crate::misc::{note, user}; + +#[macros::export(object)] +pub struct ApAdd { + pub r#type: Activity, + pub actor: String, + pub target: String, + pub object: String, +} + +impl ApObject for ApAdd {} + +impl ApAdd { + #[allow(dead_code)] // TODO: remove this line by actually using it + fn new(user_id: String, note_id: String) -> Self { + let actor_uri = user::local_uri(user_id); + let collection_uri = format!("{}/collections/featured", actor_uri); + + Self { + r#type: Activity::Add, + actor: actor_uri, + target: collection_uri, + object: note::local_uri(note_id), + } + } +} + +#[macros::ts_export] +pub fn render_add(user_id: String, note_id: String) -> ApAdd { + ApAdd::new(user_id, note_id) +} diff --git a/packages/backend-rs/src/federation/activitypub/object/emoji.rs b/packages/backend-rs/src/federation/activitypub/object/emoji.rs new file mode 100644 index 0000000..afc8b00 --- /dev/null +++ b/packages/backend-rs/src/federation/activitypub/object/emoji.rs @@ -0,0 +1,48 @@ +use super::*; +use crate::{misc, model::entity::emoji}; +use chrono::Utc; + +#[macros::export(object)] +pub struct ApEmoji { + pub id: String, + pub r#type: Activity, + pub name: String, + pub updated: String, + pub icon: Icon, +} + +#[macros::export(object)] +pub struct Icon { + pub r#type: Activity, + pub media_type: String, + pub url: String, +} + +impl ApObject for ApEmoji {} + +impl ApEmoji { + pub fn new(emoji: emoji::Model) -> Self { + Self { + id: misc::emoji::local_uri(&emoji.name), + r#type: Activity::Emoji, + name: format!(":{}:", emoji.name), + updated: emoji + .updated_at + .unwrap_or_else(|| Utc::now().into()) + .to_rfc3339(), + icon: Icon { + r#type: Activity::Image, + media_type: emoji.r#type.unwrap_or_else(|| "image/png".to_owned()), + url: emoji.public_url, + }, + } + } +} + +#[macros::for_ts] // https://github.com/napi-rs/napi-rs/issues/2060 +type Emoji = emoji::Model; + +#[macros::ts_export] +pub fn render_emoji(emoji: Emoji) -> ApEmoji { + ApEmoji::new(emoji) +} diff --git a/packages/backend-rs/src/federation/activitypub/object/flag.rs b/packages/backend-rs/src/federation/activitypub/object/flag.rs new file mode 100644 index 0000000..3b37b60 --- /dev/null +++ b/packages/backend-rs/src/federation/activitypub/object/flag.rs @@ -0,0 +1,36 @@ +use super::*; +use crate::{federation::internal_actor, misc::user}; + +#[macros::export(object)] +pub struct ApFlag { + pub r#type: Activity, + pub actor: String, + pub content: String, + // TODO: object can be an array of uri's + pub object: String, +} + +impl ApObject for ApFlag {} + +impl ApFlag { + #[allow(dead_code)] // TODO: remove this line by actually using it + async fn new( + target_user_uri: String, + comment: String, + ) -> Result { + Ok(Self { + r#type: Activity::Flag, + actor: user::local_uri(&internal_actor::instance::get().await?.id), + content: comment, + object: target_user_uri, + }) + } +} + +#[macros::ts_export] +pub async fn render_flag( + target_user_uri: String, + comment: String, +) -> Result { + ApFlag::new(target_user_uri, comment).await +} diff --git a/packages/backend-rs/src/federation/activitypub/object/follow.rs b/packages/backend-rs/src/federation/activitypub/object/follow.rs new file mode 100644 index 0000000..8ac67ee --- /dev/null +++ b/packages/backend-rs/src/federation/activitypub/object/follow.rs @@ -0,0 +1,70 @@ +use super::*; +use crate::{config::CONFIG, federation::internal_actor, misc::user}; + +#[macros::export(object)] +pub struct ApFollow { + pub id: String, + pub r#type: Activity, + pub actor: String, + pub object: String, +} + +impl ApObject for ApFollow {} + +#[macros::errors] +pub enum Error { + #[error("follower uri is missing")] + MissingFollowerUri, + #[error("followee uri is missing")] + MissingFolloweeUri, +} + +impl ApFollow { + #[allow(dead_code)] // TODO: remove this line by actually using it + fn new( + follower: UserLike, + followee: UserLike, + request_id: Option, + ) -> Result { + Ok(Self { + id: request_id.unwrap_or_else(|| { + format!("{}/follows/{}/{}", CONFIG.url, follower.id, followee.id) + }), + r#type: Activity::Follow, + actor: match user::is_local!(follower) { + true => user::local_uri(follower.id), + false => follower.uri.ok_or(Error::MissingFollowerUri)?, + }, + object: match user::is_local!(followee) { + true => user::local_uri(followee.id), + false => followee.uri.ok_or(Error::MissingFolloweeUri)?, + }, + }) + } + + #[allow(dead_code)] // TODO: remove this line by actually using it + async fn new_relay(relay_id: String) -> Result { + Ok(Self { + id: format!("{}/activities/follow-relay/{}", CONFIG.url, relay_id), + r#type: Activity::Follow, + actor: user::local_uri(internal_actor::relay::get_id().await?), + object: AS_PUBLIC_URL.to_owned(), + }) + } +} + +#[macros::ts_export] +pub fn render_follow( + follower: UserLike, + followee: UserLike, + request_id: Option, +) -> Result { + ApFollow::new(follower, followee, request_id) +} + +#[macros::ts_export] +pub async fn render_follow_relay( + relay_id: String, +) -> Result { + ApFollow::new_relay(relay_id).await +} diff --git a/packages/backend-rs/src/federation/activitypub/object/hashtag.rs b/packages/backend-rs/src/federation/activitypub/object/hashtag.rs new file mode 100644 index 0000000..7418750 --- /dev/null +++ b/packages/backend-rs/src/federation/activitypub/object/hashtag.rs @@ -0,0 +1,27 @@ +use super::*; +use crate::config::CONFIG; + +#[macros::export(object)] +pub struct ApHashtag { + pub id: String, + pub r#type: Activity, + pub name: String, +} + +impl ApObject for ApHashtag {} + +impl ApHashtag { + #[allow(dead_code)] // TODO: remove this line by actually using it + fn new(tag_name: &str) -> Self { + Self { + id: format!("{}/tags/{}", CONFIG.url, urlencoding::encode(tag_name)), + r#type: Activity::Hashtag, + name: format!("#{}", tag_name), + } + } +} + +#[macros::ts_export] +pub fn render_hashtag(tag_name: &str) -> ApHashtag { + ApHashtag::new(tag_name) +} diff --git a/packages/backend-rs/src/federation/activitypub/object/like.rs b/packages/backend-rs/src/federation/activitypub/object/like.rs new file mode 100644 index 0000000..7a786f0 --- /dev/null +++ b/packages/backend-rs/src/federation/activitypub/object/like.rs @@ -0,0 +1,74 @@ +use super::{emoji::ApEmoji, *}; +use crate::{ + config::CONFIG, + database::db_conn, + misc::{self, user}, + model::entity::{emoji, note, note_reaction}, +}; +use sea_orm::{ColumnTrait, DbErr, EntityTrait, QueryFilter, QuerySelect}; + +#[macros::errors] +pub enum Error { + #[doc = "Nonexistent note"] + #[error("note {0} not found")] + NoteNotFound(String), + #[doc = "Database error"] + #[error(transparent)] + Db(#[from] DbErr), +} + +#[macros::export(object, use_nullable = false)] +pub struct ApLike { + pub id: String, + pub r#type: Activity, + pub actor: String, + pub object: String, + pub content: String, + pub tag: Option>, +} + +impl ApObject for ApLike {} + +impl ApLike { + #[allow(dead_code)] // TODO: remove this line by actually using it + async fn new(reaction: note_reaction::Model) -> Result { + let db = db_conn().await?; + + let note_uri = { + let note_uri = note::Entity::find() + .select_only() + .column(note::Column::Uri) + .filter(note::Column::Id.eq(&reaction.note_id)) + .into_tuple::>() + .one(db) + .await?; + + match note_uri { + Some(Some(uri)) => uri, + Some(None) => misc::note::local_uri(reaction.note_id), + None => return Err(Error::NoteNotFound(reaction.note_id)), + } + }; + + let tag = emoji::Entity::find() + .filter(emoji::Column::Name.eq(reaction.reaction.replace(':', ""))) + .filter(emoji::Column::Host.is_null()) + .one(db) + .await? + .map(|emoji| vec![ApEmoji::new(emoji)]); + + Ok(Self { + id: format!("{}/likes/{}", CONFIG.url, reaction.id), + r#type: Activity::Like, + actor: user::local_uri(reaction.user_id), + object: note_uri, + content: reaction.reaction, + tag, + }) + } +} + +#[macros::ts_export] +pub async fn render_like(reaction: note_reaction::Model) -> Result { + ApLike::new(reaction).await +} diff --git a/packages/backend-rs/src/federation/activitypub/object/mention.rs b/packages/backend-rs/src/federation/activitypub/object/mention.rs new file mode 100644 index 0000000..e18302c --- /dev/null +++ b/packages/backend-rs/src/federation/activitypub/object/mention.rs @@ -0,0 +1,40 @@ +use super::*; +use crate::{federation::acct::Acct, misc::user}; + +#[derive(thiserror::Error, Debug)] +#[error("remote user's uri is missing")] +pub struct MissingRemoteUserUri; + +#[macros::export(object)] +pub struct ApMention { + pub r#type: Activity, + pub href: String, + pub name: String, +} + +impl ApObject for ApMention {} + +impl ApMention { + #[allow(dead_code)] // TODO: remove this line by actually using it + fn new(user: UserLike) -> Result { + Ok(Self { + r#type: Activity::Mention, + href: match user::is_local!(user) { + true => user::local_uri(user.id), + false => user.uri.ok_or(MissingRemoteUserUri)?, + }, + name: format!( + "@{}", + Acct { + username: user.username, + host: user.host + } + ), + }) + } +} + +#[macros::ts_export] +pub fn render_mention(user: UserLike) -> Result { + ApMention::new(user) +} diff --git a/packages/backend-rs/src/federation/activitypub/object/mod.rs b/packages/backend-rs/src/federation/activitypub/object/mod.rs index b245bca..8ed84f5 100644 --- a/packages/backend-rs/src/federation/activitypub/object/mod.rs +++ b/packages/backend-rs/src/federation/activitypub/object/mod.rs @@ -1,9 +1,48 @@ -pub mod relay; +pub mod accept; +pub mod add; +pub mod emoji; +pub mod flag; +pub mod follow; +pub mod hashtag; +pub mod like; +pub mod mention; +pub mod read; +pub mod reject; +pub mod remove; +pub mod tombstone; -pub trait ActivityPubObject {} +pub trait ApObject {} -#[derive(serde::Serialize)] #[macros::export(string_enum)] pub enum Activity { + Accept, + Add, + Emoji, + Flag, Follow, + Hashtag, + Like, + Mention, + Image, + Read, + Reject, + Remove, + Tombstone, +} + +const AS_PUBLIC_URL: &str = "https://www.w3.org/ns/activitystreams#Public"; + +use crate::config::CONFIG; +use uuid::Uuid; + +fn random_local_uri() -> String { + format!("{}/{}", CONFIG.url, Uuid::new_v4()) +} + +#[macros::export(object)] +pub struct UserLike { + pub id: String, + pub username: String, + pub host: Option, + pub uri: Option, } diff --git a/packages/backend-rs/src/federation/activitypub/object/read.rs b/packages/backend-rs/src/federation/activitypub/object/read.rs new file mode 100644 index 0000000..73ebcbc --- /dev/null +++ b/packages/backend-rs/src/federation/activitypub/object/read.rs @@ -0,0 +1,27 @@ +use super::*; +use crate::misc::user; + +#[macros::export(object)] +pub struct ApRead { + pub r#type: Activity, + pub actor: String, + pub object: String, +} + +impl ApObject for ApRead {} + +impl ApRead { + #[allow(dead_code)] // TODO: remove this line by actually using it + fn new(user_id: String, message_uri: String) -> Self { + Self { + r#type: Activity::Read, + actor: user::local_uri(user_id), + object: message_uri, + } + } +} + +#[macros::ts_export] +pub fn render_read(user_id: String, message_uri: String) -> ApRead { + ApRead::new(user_id, message_uri) +} diff --git a/packages/backend-rs/src/federation/activitypub/object/reject.rs b/packages/backend-rs/src/federation/activitypub/object/reject.rs new file mode 100644 index 0000000..d8e4d28 --- /dev/null +++ b/packages/backend-rs/src/federation/activitypub/object/reject.rs @@ -0,0 +1,29 @@ +use super::*; +use crate::misc::user; + +#[macros::export(object)] +pub struct ApReject { + pub id: String, + pub r#type: Activity, + pub actor: String, + pub object: follow::ApFollow, +} + +impl ApObject for ApReject {} + +impl ApReject { + #[allow(dead_code)] // TODO: remove this line by actually using it + fn new(user_id: String, follow_object: follow::ApFollow) -> Self { + Self { + id: random_local_uri(), + r#type: Activity::Accept, + actor: user::local_uri(user_id), + object: follow_object, + } + } +} + +#[macros::ts_export] +pub fn render_reject(user_id: String, follow_object: follow::ApFollow) -> ApReject { + ApReject::new(user_id, follow_object) +} diff --git a/packages/backend-rs/src/federation/activitypub/object/remove.rs b/packages/backend-rs/src/federation/activitypub/object/remove.rs new file mode 100644 index 0000000..53ff98b --- /dev/null +++ b/packages/backend-rs/src/federation/activitypub/object/remove.rs @@ -0,0 +1,34 @@ +//! Remove note from featured collection (pinned posts) + +use super::*; +use crate::misc::{note, user}; + +#[macros::export(object)] +pub struct ApRemove { + pub r#type: Activity, + pub actor: String, + pub target: String, + pub object: String, +} + +impl ApObject for ApRemove {} + +impl ApRemove { + #[allow(dead_code)] // TODO: remove this line by actually using it + fn new(user_id: String, note_id: String) -> Self { + let actor_uri = user::local_uri(user_id); + let collection_uri = format!("{}/collections/featured", actor_uri); + + Self { + r#type: Activity::Remove, + actor: actor_uri, + target: collection_uri, + object: note::local_uri(note_id), + } + } +} + +#[macros::ts_export] +pub fn render_remove(user_id: String, note_id: String) -> ApRemove { + ApRemove::new(user_id, note_id) +} diff --git a/packages/backend-rs/src/federation/activitypub/object/tombstone.rs b/packages/backend-rs/src/federation/activitypub/object/tombstone.rs new file mode 100644 index 0000000..00d47c2 --- /dev/null +++ b/packages/backend-rs/src/federation/activitypub/object/tombstone.rs @@ -0,0 +1,25 @@ +use super::*; +use crate::misc::note; + +#[macros::export(object)] +pub struct ApTombstone { + pub id: String, + pub r#type: Activity, +} + +impl ApObject for ApTombstone {} + +impl ApTombstone { + #[allow(dead_code)] // TODO: remove this line by actually using it + fn new(note_id: String) -> Self { + Self { + id: note::local_uri(note_id), + r#type: Activity::Tombstone, + } + } +} + +#[macros::ts_export] +pub fn render_tombstone(note_id: String) -> ApTombstone { + ApTombstone::new(note_id) +} diff --git a/packages/backend-rs/src/federation/internal_actor/instance.rs b/packages/backend-rs/src/federation/internal_actor/instance.rs index 957aa57..8c5a03f 100644 --- a/packages/backend-rs/src/federation/internal_actor/instance.rs +++ b/packages/backend-rs/src/federation/internal_actor/instance.rs @@ -4,23 +4,19 @@ use crate::{database::db_conn, model::entity::user}; use sea_orm::prelude::*; use tokio::sync::OnceCell; -// for napi export -// https://github.com/napi-rs/napi-rs/issues/2060 -type User = user::Model; - pub const USERNAME: &str = "instance.actor"; -static INSTANCE_ACTOR: OnceCell = OnceCell::const_new(); +static INSTANCE_ACTOR: OnceCell = OnceCell::const_new(); #[macros::errors] pub enum Error { #[error("@instance.actor not found")] InstanceActorNotFound, #[error(transparent)] - #[doc = "database error"] + #[doc = "Database error"] Db(#[from] DbErr), } -async fn set_cache() -> Result<&'static User, Error> { +async fn set_cache() -> Result<&'static user::Model, Error> { let instance_actor = INSTANCE_ACTOR .get_or_try_init(|| async { tracing::debug!("caching @instance.actor"); @@ -37,13 +33,16 @@ async fn set_cache() -> Result<&'static User, Error> { Ok(instance_actor) } -pub async fn get() -> Result<&'static User, Error> { +pub async fn get() -> Result<&'static user::Model, Error> { match INSTANCE_ACTOR.get() { Some(model) => Ok(model), None => set_cache().await, } } +#[macros::for_ts] // https://github.com/napi-rs/napi-rs/issues/2060 +type User = user::Model; + #[macros::ts_export(js_name = "getInstanceActor")] pub async fn get_js() -> Result { Ok(get().await?.to_owned()) diff --git a/packages/backend-rs/src/federation/internal_actor/relay.rs b/packages/backend-rs/src/federation/internal_actor/relay.rs index 2c7c4ba..8fe9e95 100644 --- a/packages/backend-rs/src/federation/internal_actor/relay.rs +++ b/packages/backend-rs/src/federation/internal_actor/relay.rs @@ -1,7 +1,7 @@ //! In-memory relay actor id cache use crate::{database::db_conn, model::entity::user}; -use sea_orm::{prelude::*, QuerySelect, SelectColumns}; +use sea_orm::{prelude::*, QuerySelect}; use tokio::sync::OnceCell; pub const USERNAME: &str = "relay.actor"; @@ -12,7 +12,7 @@ pub enum Error { #[error("@relay.actor not found")] RelayActorNotFound, #[error(transparent)] - #[doc = "database error"] + #[doc = "Database error"] Db(#[from] DbErr), } @@ -22,7 +22,7 @@ async fn set_id_cache() -> Result<&'static str, Error> { tracing::debug!("caching @relay.actor"); let found_id = user::Entity::find() .select_only() - .select_column(user::Column::Id) + .column(user::Column::Id) .filter(user::Column::Username.eq(USERNAME)) .filter(user::Column::Host.is_null()) .into_tuple::() diff --git a/packages/backend-rs/src/federation/nodeinfo/fetch.rs b/packages/backend-rs/src/federation/nodeinfo/fetch.rs index 307d86d..a4e6fcd 100644 --- a/packages/backend-rs/src/federation/nodeinfo/fetch.rs +++ b/packages/backend-rs/src/federation/nodeinfo/fetch.rs @@ -14,7 +14,7 @@ pub enum Error { HttpClient(#[from] http_client::Error), #[error("HTTP request failed")] Http(#[from] isahc::Error), - #[doc = "bad HTTP status"] + #[doc = "Bad HTTP status"] #[error("bad HTTP status ({0})")] BadStatus(String), #[error("failed to parse HTTP response body as text")] diff --git a/packages/backend-rs/src/federation/nodeinfo/generate.rs b/packages/backend-rs/src/federation/nodeinfo/generate.rs index 4ab7ef6..6dd3e9d 100644 --- a/packages/backend-rs/src/federation/nodeinfo/generate.rs +++ b/packages/backend-rs/src/federation/nodeinfo/generate.rs @@ -1,23 +1,19 @@ //! NodeInfo generator use crate::{ + cache::Cache, config::{local_server_info, CONFIG}, database::db_conn, federation::nodeinfo::schema::*, misc, model::entity::{note, user}, }; +use chrono::Duration; use sea_orm::prelude::*; use serde_json::json; -use std::{collections::HashMap, sync::Mutex}; +use std::collections::HashMap; -static CACHE: Mutex> = Mutex::new(None); - -fn set_cache(nodeinfo: &Nodeinfo21) { - let _ = CACHE - .lock() - .map(|mut cache| *cache = Some(nodeinfo.to_owned())); -} +static NODEINFO_CACHE: Cache = Cache::new_with_ttl(Duration::hours(1)); /// Fetches the number of total/active local users and local posts. /// @@ -127,35 +123,29 @@ async fn generate_nodeinfo_2_1() -> Result { }) } -async fn nodeinfo_2_1_impl(use_cache: bool) -> Result { - if use_cache { - if let Some(nodeinfo) = CACHE.lock().ok().and_then(|cache| cache.to_owned()) { - return Ok(nodeinfo); - } +/// Returns NodeInfo (version 2.1) of the local server. +pub async fn nodeinfo_2_1() -> Result { + if let Some(nodeinfo) = NODEINFO_CACHE.get() { + return Ok(nodeinfo); } let nodeinfo = generate_nodeinfo_2_1().await?; tracing::info!("updating cache"); - set_cache(&nodeinfo); + NODEINFO_CACHE.set(nodeinfo.clone()); Ok(nodeinfo) } -/// Returns NodeInfo (version 2.1) of the local server. -pub async fn nodeinfo_2_1() -> Result { - nodeinfo_2_1_impl(true).await -} - /// Returns NodeInfo (version 2.0) of the local server. pub async fn nodeinfo_2_0() -> Result { Ok(nodeinfo_2_1().await?.into()) } -#[cfg(any(test, doctest, feature = "napi"))] +#[macros::for_ts] #[macros::errors] pub enum Error { - #[doc = "database error"] + #[doc = "Database error"] #[error(transparent)] Db(#[from] DbErr), #[error("failed to serialize nodeinfo into JSON")] @@ -171,9 +161,3 @@ pub async fn nodeinfo_2_1_as_json() -> Result { pub async fn nodeinfo_2_0_as_json() -> Result { Ok(serde_json::to_value(nodeinfo_2_0().await?)?) } - -#[macros::ts_export(js_name = "updateNodeinfoCache")] -pub async fn update_cache() -> Result<(), DbErr> { - nodeinfo_2_1_impl(false).await?; - Ok(()) -} diff --git a/packages/backend-rs/src/init/system_info.rs b/packages/backend-rs/src/init/system_info.rs index 82505a1..b49fb59 100644 --- a/packages/backend-rs/src/init/system_info.rs +++ b/packages/backend-rs/src/init/system_info.rs @@ -1,28 +1,10 @@ -use std::sync::{Mutex, MutexGuard, OnceLock, PoisonError}; +use crate::misc::system_info; use sysinfo::System; -pub type SysinfoPoisonError = PoisonError>; - -static SYSTEM_INFO: OnceLock> = OnceLock::new(); - -/// Gives an access to the shared static [System] object. -/// -/// # Example -/// -/// ``` -/// # use backend_rs::init::system_info::{system_info, SysinfoPoisonError}; -/// let system_info = system_info().lock()?; -/// println!("The number of CPU threads is {}.", system_info.cpus().len()); -/// # Ok::<(), SysinfoPoisonError>(()) -/// ``` -pub fn system_info() -> &'static std::sync::Mutex { - SYSTEM_INFO.get_or_init(|| Mutex::new(System::new_all())) -} - /// Prints the server hardware information as the server info log. #[macros::export] -pub fn show_server_info() -> Result<(), SysinfoPoisonError> { - let system_info = system_info().lock()?; +pub fn show_server_info() { + let system_info = system_info::get(); tracing::info!( "Hostname: {}", @@ -45,6 +27,4 @@ pub fn show_server_info() -> Result<(), SysinfoPoisonError> { tracing::info!("Free memory: {} MiB", system_info.free_memory() / 1048576); tracing::info!("Total swap: {} MiB", system_info.total_swap() / 1048576); tracing::info!("Free swap: {} MiB", system_info.free_swap() / 1048576); - - Ok(()) } diff --git a/packages/backend-rs/src/lib.rs b/packages/backend-rs/src/lib.rs index ff7a49a..89d357e 100644 --- a/packages/backend-rs/src/lib.rs +++ b/packages/backend-rs/src/lib.rs @@ -1,5 +1,6 @@ #![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] +pub mod cache; pub mod config; pub mod database; pub mod federation; diff --git a/packages/backend-rs/src/misc/emoji/mod.rs b/packages/backend-rs/src/misc/emoji/mod.rs new file mode 100644 index 0000000..bb89f3d --- /dev/null +++ b/packages/backend-rs/src/misc/emoji/mod.rs @@ -0,0 +1,9 @@ +pub mod reaction; +pub mod unicode; + +use crate::config::CONFIG; + +/// Returns URI of a local custom emoji. +pub fn local_uri(emoji_code: impl std::fmt::Display) -> String { + format!("{}/emojis/{}", CONFIG.url, emoji_code) +} diff --git a/packages/backend-rs/src/misc/emoji/reaction.rs b/packages/backend-rs/src/misc/emoji/reaction.rs new file mode 100644 index 0000000..afae0a6 --- /dev/null +++ b/packages/backend-rs/src/misc/emoji/reaction.rs @@ -0,0 +1,198 @@ +use crate::{config::local_server_info, database::db_conn, model::entity::emoji}; +use once_cell::sync::Lazy; +use regex::Regex; +use sea_orm::prelude::*; +use std::collections::HashMap; + +#[cfg_attr(test, derive(PartialEq, Debug))] +#[macros::export(object)] +pub struct DecodedReaction { + pub reaction: String, + pub name: Option, + pub host: Option, +} + +#[macros::export] +pub fn decode_reaction(reaction: &str) -> DecodedReaction { + // Misskey allows you to include "+" and "-" in emoji shortcodes + // MFM spec: https://github.com/misskey-dev/mfm.js/blob/6aaf68089023c6adebe44123eebbc4dcd75955e0/docs/syntax.md?plain=1#L583 + // Misskey's implementation: https://github.com/misskey-dev/misskey/blob/bba3097765317cbf95d09627961b5b5dce16a972/packages/backend/src/core/ReactionService.ts#L68 + static RE: Lazy = + Lazy::new(|| Regex::new(r"^:([0-9A-Za-z_+-]+)(?:@([0-9A-Za-z_.-]+))?:$").unwrap()); + + if let Some(captures) = RE.captures(reaction) { + let name = &captures[1]; + let host = captures.get(2).map(|s| s.as_str()); + + DecodedReaction { + reaction: format!(":{}@{}:", name, host.unwrap_or(".")), + name: Some(name.to_owned()), + host: host.map(|s| s.to_owned()), + } + } else { + DecodedReaction { + reaction: reaction.to_owned(), + name: None, + host: None, + } + } +} + +#[macros::export] +pub fn count_reactions(reactions: &HashMap) -> HashMap { + let mut res = HashMap::::new(); + + for (reaction, count) in reactions.iter() { + if count > &0 { + let decoded = decode_reaction(reaction).reaction; + let total = res.entry(decoded).or_insert(0); + *total += count; + } + } + + res +} + +#[macros::errors] +pub enum Error { + #[doc = "UTS #46 process has failed"] + #[error(transparent)] + Idna(#[from] idna::Errors), + #[error(transparent)] + Db(#[from] DbErr), +} + +#[macros::export] +pub async fn to_db_reaction(reaction: Option<&str>, host: Option<&str>) -> Result { + if let Some(reaction) = reaction { + // FIXME: Is it okay to do this only here? + // This was introduced in https://firefish.dev/firefish/firefish/-/commit/af730e75b6fc1a57ca680ce83459d7e433b130cf + if reaction.contains('❤') || reaction.contains("♥️") { + return Ok("❤️".to_owned()); + } + + // check if the reaction is an Unicode emoji + if emojis::get(reaction).is_some() { + return Ok(reaction.to_owned()); + } + + static RE: Lazy = + Lazy::new(|| Regex::new(r"^:([0-9A-Za-z_+-]+)(?:@\.)?:$").unwrap()); + + if let Some(captures) = RE.captures(reaction) { + let name = &captures[1]; + let db = db_conn().await?; + + if let Some(host) = host { + // remote emoji + let ascii_host = idna::domain_to_ascii(host)?; + + // TODO: Does SeaORM have the `exists` method? + if emoji::Entity::find() + .filter(emoji::Column::Name.eq(name)) + .filter(emoji::Column::Host.eq(&ascii_host)) + .one(db) + .await? + .is_some() + { + return Ok(format!(":{name}@{ascii_host}:")); + } + + tracing::info!("nonexistent remote custom emoji: :{name}@{ascii_host}:"); + } else { + // local emoji + // TODO: Does SeaORM have the `exists` method? + if emoji::Entity::find() + .filter(emoji::Column::Name.eq(name)) + .filter(emoji::Column::Host.is_null()) + .one(db) + .await? + .is_some() + { + return Ok(format!(":{name}:")); + } + + tracing::info!("nonexistent local custom emoji: :{name}:"); + } + }; + }; + + Ok(local_server_info().await?.default_reaction) +} + +#[cfg(test)] +mod unit_test { + use super::DecodedReaction; + use pretty_assertions::{assert_eq, assert_ne}; + + #[test] + fn decode_reaction() { + let unicode_emoji_1 = DecodedReaction { + reaction: "⭐".to_owned(), + name: None, + host: None, + }; + let unicode_emoji_2 = DecodedReaction { + reaction: "🩷".to_owned(), + name: None, + host: None, + }; + + assert_eq!(super::decode_reaction("⭐"), unicode_emoji_1); + assert_eq!(super::decode_reaction("🩷"), unicode_emoji_2); + + assert_ne!(super::decode_reaction("⭐"), unicode_emoji_2); + assert_ne!(super::decode_reaction("🩷"), unicode_emoji_1); + + let unicode_emoji_3 = DecodedReaction { + reaction: "🖖🏿".to_owned(), + name: None, + host: None, + }; + assert_eq!(super::decode_reaction("🖖🏿"), unicode_emoji_3); + + let local_emoji = DecodedReaction { + reaction: ":meow_melt_tears@.:".to_owned(), + name: Some("meow_melt_tears".to_owned()), + host: None, + }; + assert_eq!(super::decode_reaction(":meow_melt_tears:"), local_emoji); + + let remote_emoji_1 = DecodedReaction { + reaction: ":meow_uwu@some-domain.example.org:".to_owned(), + name: Some("meow_uwu".to_owned()), + host: Some("some-domain.example.org".to_owned()), + }; + assert_eq!( + super::decode_reaction(":meow_uwu@some-domain.example.org:"), + remote_emoji_1 + ); + + let remote_emoji_2 = DecodedReaction { + reaction: ":C++23@xn--eckwd4c7c.example.org:".to_owned(), + name: Some("C++23".to_owned()), + host: Some("xn--eckwd4c7c.example.org".to_owned()), + }; + assert_eq!( + super::decode_reaction(":C++23@xn--eckwd4c7c.example.org:"), + remote_emoji_2 + ); + + let invalid_reaction_1 = DecodedReaction { + reaction: ":foo".to_owned(), + name: None, + host: None, + }; + assert_eq!(super::decode_reaction(":foo"), invalid_reaction_1); + + let invalid_reaction_2 = DecodedReaction { + reaction: ":foo&@example.com:".to_owned(), + name: None, + host: None, + }; + assert_eq!( + super::decode_reaction(":foo&@example.com:"), + invalid_reaction_2 + ); + } +} diff --git a/packages/backend-rs/src/misc/emoji/unicode.rs b/packages/backend-rs/src/misc/emoji/unicode.rs new file mode 100644 index 0000000..47c2e0d --- /dev/null +++ b/packages/backend-rs/src/misc/emoji/unicode.rs @@ -0,0 +1,6 @@ +//! This module is used in the TypeScript backend only. + +#[macros::ts_export] +pub fn is_unicode_emoji(s: &str) -> bool { + emojis::get(s).is_some() +} diff --git a/packages/backend-rs/src/misc/get_image_size.rs b/packages/backend-rs/src/misc/get_image_size.rs index d101514..172ac21 100644 --- a/packages/backend-rs/src/misc/get_image_size.rs +++ b/packages/backend-rs/src/misc/get_image_size.rs @@ -1,7 +1,8 @@ -use crate::{database::cache, util::http_client}; +use crate::{cache, util::http_client}; +use chrono::Duration; use futures_util::AsyncReadExt; use image::{ImageError, ImageFormat, ImageReader}; -use isahc::prelude::*; +use isahc::AsyncReadResponseExt; use nom_exif::{parse_jpeg_exif, EntryValue, ExifTag}; use std::io::Cursor; use tokio::sync::Mutex; @@ -9,12 +10,12 @@ use tokio::sync::Mutex; #[macros::errors] pub enum Error { #[error("Redis cache operation has failed")] - Cache(#[from] cache::Error), + Cache(#[from] cache::redis::Error), #[error("failed to acquire an HTTP client")] HttpClient(#[from] http_client::Error), #[error("HTTP request failed")] Isahc(#[from] isahc::Error), - #[doc = "bad HTTP status"] + #[doc = "Bad HTTP status"] #[error("bad HTTP status ({0})")] BadStatus(String), #[error("failed to decode an image")] @@ -23,10 +24,10 @@ pub enum Error { Io(#[from] std::io::Error), #[error("failed to extract the exif data")] Exif(#[from] nom_exif::Error), - #[doc = "too many fetch attempts"] + #[doc = "Too many fetch attempts"] #[error("too many fetch attempts for {0}")] TooManyAttempts(String), - #[doc = "unsupported image type"] + #[doc = "Unsupported image type"] #[error("unsupported image type ({0})")] UnsupportedImage(String), } @@ -63,7 +64,7 @@ pub async fn get_image_size_from_url(url: &str) -> Result { .is_some(); if !attempted { - cache::set_one(cache::Category::FetchUrl, url, &true, 10 * 60).await?; + cache::set_one(cache::Category::FetchUrl, url, &true, Duration::minutes(10)).await?; } } @@ -134,7 +135,7 @@ pub async fn get_image_size_from_url(url: &str) -> Result { #[cfg(test)] mod unit_test { use super::ImageSize; - use crate::database::cache; + use crate::cache; use pretty_assertions::assert_eq; #[tokio::test] diff --git a/packages/backend-rs/src/misc/latest_version.rs b/packages/backend-rs/src/misc/latest_version.rs index a7e5568..5df10de 100644 --- a/packages/backend-rs/src/misc/latest_version.rs +++ b/packages/backend-rs/src/misc/latest_version.rs @@ -1,14 +1,13 @@ //! Fetch latest Firefish version from the Firefish repository -use crate::{database::cache, util::http_client}; +use crate::{cache::Cache, util::http_client}; +use chrono::Duration; use futures_util::AsyncReadExt; use isahc::AsyncReadResponseExt; use serde::Deserialize; #[macros::errors] pub enum Error { - #[error("Redis cache operation has failed")] - Cache(#[from] cache::Error), #[error("HTTP request failed")] Isahc(#[from] isahc::Error), #[error("failed to acquire an HTTP client")] @@ -22,15 +21,17 @@ pub enum Error { Json(#[from] serde_json::Error), } +#[derive(Clone, Deserialize)] +struct PackageJson { + version: String, +} + const UPSTREAM_PACKAGE_JSON_URL: &str = "https://firefish.dev/firefish/firefish/-/raw/main/package.json"; -async fn get_latest_version() -> Result { - #[derive(Debug, Deserialize)] - struct Response { - version: String, - } +static PACKAGE_JSON_CACHE: Cache = Cache::new_with_ttl(Duration::hours(3)); +async fn get_package_json() -> Result { // Read up to 1 MiB of the response body let mut response = http_client::client()? .get_async(UPSTREAM_PACKAGE_JSON_URL) @@ -42,42 +43,33 @@ async fn get_latest_version() -> Result { return Err(Error::BadStatus(response.status().to_string())); } - let res_parsed: Response = serde_json::from_str(&response.text().await?)?; + let package_json: PackageJson = serde_json::from_str(&response.text().await?)?; - Ok(res_parsed.version) + Ok(package_json) } /// Returns the latest Firefish version. #[macros::export] pub async fn latest_version() -> Result { - let version: Option = - cache::get_one(cache::Category::FetchUrl, UPSTREAM_PACKAGE_JSON_URL).await?; - - if let Some(v) = version { - tracing::trace!("use cached value: {}", v); - Ok(v) + if let Some(package_json) = PACKAGE_JSON_CACHE.get() { + tracing::trace!("use cached value: {}", package_json.version); + Ok(package_json.version) } else { tracing::trace!("cache is expired, fetching the latest version"); - let fetched_version = get_latest_version().await?; - tracing::trace!("fetched value: {}", fetched_version); + let package_json = get_package_json().await?; + tracing::trace!("fetched value: {}", package_json.version); - cache::set_one( - cache::Category::FetchUrl, - UPSTREAM_PACKAGE_JSON_URL, - &fetched_version, - 3 * 60 * 60, - ) - .await?; - Ok(fetched_version) + PACKAGE_JSON_CACHE.set(package_json.clone()); + Ok(package_json.version) } } #[cfg(test)] mod unit_test { - use super::{latest_version, UPSTREAM_PACKAGE_JSON_URL}; - use crate::database::cache; + use super::latest_version; + use pretty_assertions::assert_eq; - fn validate_version(version: String) { + fn validate_version(version: &str) { // version: YYYYMMDD or YYYYMMDD-X assert!(version.len() >= 8); assert!(version[..8].chars().all(|c| c.is_ascii_digit())); @@ -103,15 +95,14 @@ mod unit_test { #[tokio::test] #[cfg_attr(miri, ignore)] // can't call foreign function `getaddrinfo` on OS `linux` async fn get_latest_version() { - // delete caches in case you run this test multiple times - cache::delete_one(cache::Category::FetchUrl, UPSTREAM_PACKAGE_JSON_URL) - .await - .unwrap(); - // fetch from firefish.dev - validate_version(latest_version().await.unwrap()); + let version_1 = latest_version().await.unwrap(); + validate_version(&version_1); // use cache - validate_version(latest_version().await.unwrap()); + let version_2 = latest_version().await.unwrap(); + validate_version(&version_2); + + assert_eq!(version_1, version_2); } } diff --git a/packages/backend-rs/src/misc/mod.rs b/packages/backend-rs/src/misc/mod.rs index 4c715f9..64bfae0 100644 --- a/packages/backend-rs/src/misc/mod.rs +++ b/packages/backend-rs/src/misc/mod.rs @@ -13,7 +13,7 @@ pub mod latest_version; pub mod note; pub mod nyaify; pub mod password; -pub mod reaction; +pub mod random_icon; pub mod remove_old_attestation_challenges; pub mod should_nyaify; pub mod system_info; diff --git a/packages/backend-rs/src/misc/note/mod.rs b/packages/backend-rs/src/misc/note/mod.rs index 84e2560..366bcaa 100644 --- a/packages/backend-rs/src/misc/note/mod.rs +++ b/packages/backend-rs/src/misc/note/mod.rs @@ -3,3 +3,10 @@ pub use summarize::summarize; pub mod elaborate; pub mod summarize; + +use crate::config::CONFIG; + +/// Returns URI of a local post. +pub fn local_uri(note_id: impl std::fmt::Display) -> String { + format!("{}/notes/{}", CONFIG.url, note_id) +} diff --git a/packages/backend-rs/src/misc/random_icon.rs b/packages/backend-rs/src/misc/random_icon.rs new file mode 100644 index 0000000..9e05f4c --- /dev/null +++ b/packages/backend-rs/src/misc/random_icon.rs @@ -0,0 +1,43 @@ +use crate::cache; +use chrono::Duration; +use identicon_rs::{error::IdenticonError, Identicon}; + +#[macros::errors] +pub enum Error { + #[doc = "Failed to generate identicon"] + #[error(transparent)] + Identicon(#[from] IdenticonError), + #[error("Redis cache operation has failed")] + Cache(#[from] cache::redis::Error), +} + +pub async fn generate(id: &str) -> Result, Error> { + if let Some(icon) = cache::get_one::>(cache::Category::RandomIcon, id).await? { + Ok(icon) + } else { + let icon = Identicon::new(id) + .set_border(16) + .set_scale(96)? + .export_png_data()?; + cache::set_one( + cache::Category::RandomIcon, + id, + &icon, + Duration::minutes(10), + ) + .await?; + Ok(icon) + } +} + +#[cfg(feature = "napi")] +#[napi_derive::napi(js_name = "genIdenticon")] +pub async fn generate_js(id: String) -> napi::Result { + match generate(&id).await { + Ok(icon) => Ok(icon.into()), + Err(err) => Err(napi::Error::from_reason(format!( + "\n{}\n", + crate::util::error_chain::format_error(&err) + ))), + } +} diff --git a/packages/backend-rs/src/misc/should_nyaify.rs b/packages/backend-rs/src/misc/should_nyaify.rs index 8476678..78360b5 100644 --- a/packages/backend-rs/src/misc/should_nyaify.rs +++ b/packages/backend-rs/src/misc/should_nyaify.rs @@ -1,19 +1,18 @@ //! Determine whether to enable the cat language conversion -use crate::{ - database::{cache, db_conn}, - model::entity::user, -}; -use sea_orm::{DbErr, EntityTrait, QuerySelect, SelectColumns}; +use crate::{cache, database::db_conn, model::entity::user}; +use chrono::Duration; +use sea_orm::{DbErr, EntityTrait, QuerySelect}; #[macros::errors] pub enum Error { - #[doc = "database error"] + #[doc = "Database error"] #[error(transparent)] Db(#[from] DbErr), - #[doc = "cache error"] + #[doc = "Cache error"] #[error(transparent)] - Cache(#[from] cache::Error), + Cache(#[from] cache::redis::Error), + #[doc = "User not found"] #[error("user {0} not found")] NotFound(String), } @@ -27,7 +26,7 @@ pub async fn should_nyaify(reader_user_id: &str) -> Result { let fetched_value = user::Entity::find_by_id(reader_user_id) .select_only() - .select_column(user::Column::ReadCatLanguage) + .column(user::Column::ReadCatLanguage) .into_tuple::() .one(db_conn().await?) .await? @@ -37,7 +36,7 @@ pub async fn should_nyaify(reader_user_id: &str) -> Result { cache::Category::CatLang, reader_user_id, &fetched_value, - 10 * 60, + Duration::minutes(10), ) .await?; diff --git a/packages/backend-rs/src/misc/system_info.rs b/packages/backend-rs/src/misc/system_info.rs index d5191c1..bb8d537 100644 --- a/packages/backend-rs/src/misc/system_info.rs +++ b/packages/backend-rs/src/misc/system_info.rs @@ -1,10 +1,35 @@ //! Utilities to check hardware information such as cpu, memory, storage usage - -use crate::init::system_info::{system_info, SysinfoPoisonError}; -use sysinfo::{Disks, MemoryRefreshKind}; - // TODO: i64 -> u64 (we can't export u64 to Node.js) +use std::sync::{Mutex, OnceLock}; +use sysinfo::{Disks, MemoryRefreshKind, System}; + +static SYSTEM_INFO: OnceLock> = OnceLock::new(); + +/// Gives you access to the shared static [System] object. +/// +/// # Example +/// +/// ``` +/// # use backend_rs::misc::system_info; +/// let system_info = system_info::get(); +/// println!("The number of CPU threads is {}.", system_info.cpus().len()); +/// println!("The total memory is {} MiB.", system_info.total_memory() / 1048576); +/// ``` +pub fn get() -> std::sync::MutexGuard<'static, System> { + let guard = SYSTEM_INFO + .get_or_init(|| Mutex::new(System::new_all())) + .lock(); + + if let Err(err) = guard { + let mut inner = err.into_inner(); + *inner = System::new_all(); + inner + } else { + guard.unwrap() + } +} + #[macros::export(object)] pub struct Cpu { pub model: String, @@ -31,10 +56,10 @@ pub struct Storage { } #[macros::export] -pub fn cpu_info() -> Result { - let system_info = system_info().lock()?; +pub fn cpu_info() -> Cpu { + let system_info = get(); - Ok(Cpu { + Cpu { model: match system_info.cpus() { [] => { tracing::debug!("failed to get CPU info"); @@ -43,31 +68,31 @@ pub fn cpu_info() -> Result { cpus => cpus[0].brand().to_owned(), }, cores: system_info.cpus().len() as u16, - }) + } } #[macros::export] -pub fn cpu_usage() -> Result { - let mut system_info = system_info().lock()?; +pub fn cpu_usage() -> f32 { + let mut system_info = get(); system_info.refresh_cpu_usage(); let total_cpu_usage: f32 = system_info.cpus().iter().map(|cpu| cpu.cpu_usage()).sum(); let cpu_threads = system_info.cpus().len(); - Ok(total_cpu_usage / (cpu_threads as f32)) + total_cpu_usage / (cpu_threads as f32) } #[macros::export] -pub fn memory_usage() -> Result { - let mut system_info = system_info().lock()?; +pub fn memory_usage() -> Memory { + let mut system_info = get(); system_info.refresh_memory_specifics(MemoryRefreshKind::new().with_ram()); - Ok(Memory { + Memory { total: system_info.total_memory() as i64, used: system_info.used_memory() as i64, available: system_info.available_memory() as i64, - }) + } } #[macros::export] diff --git a/packages/backend-rs/src/misc/translate.rs b/packages/backend-rs/src/misc/translate.rs index d783b86..fa84979 100644 --- a/packages/backend-rs/src/misc/translate.rs +++ b/packages/backend-rs/src/misc/translate.rs @@ -5,7 +5,7 @@ use crate::{ #[macros::errors] pub enum Error { - #[doc = "database error"] + #[doc = "Database error"] #[error(transparent)] Db(#[from] sea_orm::DbErr), #[error("failed to acquire an HTTP client")] diff --git a/packages/backend-rs/src/misc/user/mod.rs b/packages/backend-rs/src/misc/user/mod.rs index 16ee43a..9272084 100644 --- a/packages/backend-rs/src/misc/user/mod.rs +++ b/packages/backend-rs/src/misc/user/mod.rs @@ -1 +1,30 @@ pub mod count; + +use crate::config::CONFIG; + +/// Returns URI of a local user. +pub fn local_uri(user_id: impl std::fmt::Display) -> String { + format!("{}/users/{}", CONFIG.url, user_id) +} + +#[doc(hidden)] // hide the macro in the top doc page +#[macro_export] +macro_rules! is_local { + ($user_like:expr) => { + $user_like.host.is_none() + }; +} + +#[doc(inline)] // show the macro in the module doc page +pub use is_local; + +#[doc(hidden)] // hide the macro in the top doc page +#[macro_export] +macro_rules! is_remote { + ($user_like:expr) => { + $user_like.host.is_some() + }; +} + +#[doc(inline)] // show the macro in the module doc page +pub use is_remote; diff --git a/packages/backend-rs/src/model/entity/abuse_user_report.rs b/packages/backend-rs/src/model/entity/abuse_user_report.rs index 70ab552..d18af43 100644 --- a/packages/backend-rs/src/model/entity/abuse_user_report.rs +++ b/packages/backend-rs/src/model/entity/abuse_user_report.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/access_token.rs b/packages/backend-rs/src/model/entity/access_token.rs index 5895217..7b82c30 100644 --- a/packages/backend-rs/src/model/entity/access_token.rs +++ b/packages/backend-rs/src/model/entity/access_token.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/ad.rs b/packages/backend-rs/src/model/entity/ad.rs index 5449503..a245ced 100644 --- a/packages/backend-rs/src/model/entity/ad.rs +++ b/packages/backend-rs/src/model/entity/ad.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/announcement.rs b/packages/backend-rs/src/model/entity/announcement.rs index 750c8d1..3a02f6c 100644 --- a/packages/backend-rs/src/model/entity/announcement.rs +++ b/packages/backend-rs/src/model/entity/announcement.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/announcement_read.rs b/packages/backend-rs/src/model/entity/announcement_read.rs index 169b6c9..18100a9 100644 --- a/packages/backend-rs/src/model/entity/announcement_read.rs +++ b/packages/backend-rs/src/model/entity/announcement_read.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/antenna.rs b/packages/backend-rs/src/model/entity/antenna.rs index 45d40f1..796e240 100644 --- a/packages/backend-rs/src/model/entity/antenna.rs +++ b/packages/backend-rs/src/model/entity/antenna.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use super::sea_orm_active_enums::AntennaSrc; use sea_orm::entity::prelude::*; diff --git a/packages/backend-rs/src/model/entity/app.rs b/packages/backend-rs/src/model/entity/app.rs index 06c1d50..5664b75 100644 --- a/packages/backend-rs/src/model/entity/app.rs +++ b/packages/backend-rs/src/model/entity/app.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/attestation_challenge.rs b/packages/backend-rs/src/model/entity/attestation_challenge.rs index ca3804a..f81c996 100644 --- a/packages/backend-rs/src/model/entity/attestation_challenge.rs +++ b/packages/backend-rs/src/model/entity/attestation_challenge.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/auth_session.rs b/packages/backend-rs/src/model/entity/auth_session.rs index 1dc6acd..981ff5e 100644 --- a/packages/backend-rs/src/model/entity/auth_session.rs +++ b/packages/backend-rs/src/model/entity/auth_session.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/blocking.rs b/packages/backend-rs/src/model/entity/blocking.rs index 3a9a075..c6a3ee1 100644 --- a/packages/backend-rs/src/model/entity/blocking.rs +++ b/packages/backend-rs/src/model/entity/blocking.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/channel.rs b/packages/backend-rs/src/model/entity/channel.rs index 221a72b..cc1f998 100644 --- a/packages/backend-rs/src/model/entity/channel.rs +++ b/packages/backend-rs/src/model/entity/channel.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/channel_following.rs b/packages/backend-rs/src/model/entity/channel_following.rs index 9be21b8..4113c1e 100644 --- a/packages/backend-rs/src/model/entity/channel_following.rs +++ b/packages/backend-rs/src/model/entity/channel_following.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/channel_note_pining.rs b/packages/backend-rs/src/model/entity/channel_note_pining.rs index d03ee96..002f535 100644 --- a/packages/backend-rs/src/model/entity/channel_note_pining.rs +++ b/packages/backend-rs/src/model/entity/channel_note_pining.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/clip.rs b/packages/backend-rs/src/model/entity/clip.rs index fcd5b11..a9428bf 100644 --- a/packages/backend-rs/src/model/entity/clip.rs +++ b/packages/backend-rs/src/model/entity/clip.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/clip_note.rs b/packages/backend-rs/src/model/entity/clip_note.rs index 9cce432..17ad84b 100644 --- a/packages/backend-rs/src/model/entity/clip_note.rs +++ b/packages/backend-rs/src/model/entity/clip_note.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/drive_file.rs b/packages/backend-rs/src/model/entity/drive_file.rs index e2139a9..4ec086e 100644 --- a/packages/backend-rs/src/model/entity/drive_file.rs +++ b/packages/backend-rs/src/model/entity/drive_file.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use super::sea_orm_active_enums::DriveFileUsageHint; use sea_orm::entity::prelude::*; @@ -31,11 +31,11 @@ pub struct Model { pub thumbnail_url: Option, #[sea_orm(column_name = "webpublicUrl")] pub webpublic_url: Option, - #[sea_orm(column_name = "accessKey")] + #[sea_orm(column_name = "accessKey", unique)] pub access_key: Option, - #[sea_orm(column_name = "thumbnailAccessKey")] + #[sea_orm(column_name = "thumbnailAccessKey", unique)] pub thumbnail_access_key: Option, - #[sea_orm(column_name = "webpublicAccessKey")] + #[sea_orm(column_name = "webpublicAccessKey", unique)] pub webpublic_access_key: Option, pub uri: Option, pub src: Option, diff --git a/packages/backend-rs/src/model/entity/drive_folder.rs b/packages/backend-rs/src/model/entity/drive_folder.rs index 428fc70..757be7c 100644 --- a/packages/backend-rs/src/model/entity/drive_folder.rs +++ b/packages/backend-rs/src/model/entity/drive_folder.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/emoji.rs b/packages/backend-rs/src/model/entity/emoji.rs index 44744ed..3a058c1 100644 --- a/packages/backend-rs/src/model/entity/emoji.rs +++ b/packages/backend-rs/src/model/entity/emoji.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/follow_request.rs b/packages/backend-rs/src/model/entity/follow_request.rs index f7b6231..fec79c6 100644 --- a/packages/backend-rs/src/model/entity/follow_request.rs +++ b/packages/backend-rs/src/model/entity/follow_request.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/following.rs b/packages/backend-rs/src/model/entity/following.rs index 3b4459b..874fe8e 100644 --- a/packages/backend-rs/src/model/entity/following.rs +++ b/packages/backend-rs/src/model/entity/following.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/gallery_like.rs b/packages/backend-rs/src/model/entity/gallery_like.rs index 062854e..eda880e 100644 --- a/packages/backend-rs/src/model/entity/gallery_like.rs +++ b/packages/backend-rs/src/model/entity/gallery_like.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/gallery_post.rs b/packages/backend-rs/src/model/entity/gallery_post.rs index 3fc93da..b5c008a 100644 --- a/packages/backend-rs/src/model/entity/gallery_post.rs +++ b/packages/backend-rs/src/model/entity/gallery_post.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/hashtag.rs b/packages/backend-rs/src/model/entity/hashtag.rs index f9bb74b..28a23b8 100644 --- a/packages/backend-rs/src/model/entity/hashtag.rs +++ b/packages/backend-rs/src/model/entity/hashtag.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; @@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize}; pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id: String, + #[sea_orm(unique)] pub name: String, #[sea_orm(column_name = "mentionedUserIds")] pub mentioned_user_ids: Vec, diff --git a/packages/backend-rs/src/model/entity/instance.rs b/packages/backend-rs/src/model/entity/instance.rs index 9a49e25..4711ffe 100644 --- a/packages/backend-rs/src/model/entity/instance.rs +++ b/packages/backend-rs/src/model/entity/instance.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; @@ -12,6 +12,7 @@ pub struct Model { pub id: String, #[sea_orm(column_name = "caughtAt")] pub caught_at: DateTimeWithTimeZone, + #[sea_orm(unique)] pub host: String, #[sea_orm(column_name = "usersCount")] pub users_count: i32, diff --git a/packages/backend-rs/src/model/entity/messaging_message.rs b/packages/backend-rs/src/model/entity/messaging_message.rs index 202496f..0b84db7 100644 --- a/packages/backend-rs/src/model/entity/messaging_message.rs +++ b/packages/backend-rs/src/model/entity/messaging_message.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/meta.rs b/packages/backend-rs/src/model/entity/meta.rs index 023fcef..6d6fd4a 100644 --- a/packages/backend-rs/src/model/entity/meta.rs +++ b/packages/backend-rs/src/model/entity/meta.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/migrations.rs b/packages/backend-rs/src/model/entity/migrations.rs index b03aaab..f597ee4 100644 --- a/packages/backend-rs/src/model/entity/migrations.rs +++ b/packages/backend-rs/src/model/entity/migrations.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/mod.rs b/packages/backend-rs/src/model/entity/mod.rs index ffb2135..ae55b9b 100644 --- a/packages/backend-rs/src/model/entity/mod.rs +++ b/packages/backend-rs/src/model/entity/mod.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 pub mod prelude; diff --git a/packages/backend-rs/src/model/entity/moderation_log.rs b/packages/backend-rs/src/model/entity/moderation_log.rs index 2ce71a9..43a40df 100644 --- a/packages/backend-rs/src/model/entity/moderation_log.rs +++ b/packages/backend-rs/src/model/entity/moderation_log.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/muted_note.rs b/packages/backend-rs/src/model/entity/muted_note.rs index 5e89c2e..9b441fc 100644 --- a/packages/backend-rs/src/model/entity/muted_note.rs +++ b/packages/backend-rs/src/model/entity/muted_note.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use super::sea_orm_active_enums::MutedNoteReason; use sea_orm::entity::prelude::*; diff --git a/packages/backend-rs/src/model/entity/muting.rs b/packages/backend-rs/src/model/entity/muting.rs index eff07c6..15dd8d3 100644 --- a/packages/backend-rs/src/model/entity/muting.rs +++ b/packages/backend-rs/src/model/entity/muting.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/note.rs b/packages/backend-rs/src/model/entity/note.rs index e3bf3c9..66cd80a 100644 --- a/packages/backend-rs/src/model/entity/note.rs +++ b/packages/backend-rs/src/model/entity/note.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use super::sea_orm_active_enums::NoteVisibility; use sea_orm::entity::prelude::*; @@ -33,6 +33,7 @@ pub struct Model { #[sea_orm(column_type = "JsonBinary")] pub reactions: Json, pub visibility: NoteVisibility, + #[sea_orm(unique)] pub uri: Option, pub score: i32, #[sea_orm(column_name = "fileIds")] diff --git a/packages/backend-rs/src/model/entity/note_edit.rs b/packages/backend-rs/src/model/entity/note_edit.rs index 66e9a8e..985b4bd 100644 --- a/packages/backend-rs/src/model/entity/note_edit.rs +++ b/packages/backend-rs/src/model/entity/note_edit.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/note_favorite.rs b/packages/backend-rs/src/model/entity/note_favorite.rs index 8db8612..7bdede2 100644 --- a/packages/backend-rs/src/model/entity/note_favorite.rs +++ b/packages/backend-rs/src/model/entity/note_favorite.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/note_file.rs b/packages/backend-rs/src/model/entity/note_file.rs index 6742bf6..12ce748 100644 --- a/packages/backend-rs/src/model/entity/note_file.rs +++ b/packages/backend-rs/src/model/entity/note_file.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/note_reaction.rs b/packages/backend-rs/src/model/entity/note_reaction.rs index e329db8..970017b 100644 --- a/packages/backend-rs/src/model/entity/note_reaction.rs +++ b/packages/backend-rs/src/model/entity/note_reaction.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/note_thread_muting.rs b/packages/backend-rs/src/model/entity/note_thread_muting.rs index 29aa782..8d0e777 100644 --- a/packages/backend-rs/src/model/entity/note_thread_muting.rs +++ b/packages/backend-rs/src/model/entity/note_thread_muting.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/note_unread.rs b/packages/backend-rs/src/model/entity/note_unread.rs index da312e6..3f15880 100644 --- a/packages/backend-rs/src/model/entity/note_unread.rs +++ b/packages/backend-rs/src/model/entity/note_unread.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/note_watching.rs b/packages/backend-rs/src/model/entity/note_watching.rs index 5ab675a..174dfb6 100644 --- a/packages/backend-rs/src/model/entity/note_watching.rs +++ b/packages/backend-rs/src/model/entity/note_watching.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/notification.rs b/packages/backend-rs/src/model/entity/notification.rs index e73a9cd..7c4cdfa 100644 --- a/packages/backend-rs/src/model/entity/notification.rs +++ b/packages/backend-rs/src/model/entity/notification.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use super::sea_orm_active_enums::NotificationType; use sea_orm::entity::prelude::*; diff --git a/packages/backend-rs/src/model/entity/page.rs b/packages/backend-rs/src/model/entity/page.rs index f252a43..1bfdf53 100644 --- a/packages/backend-rs/src/model/entity/page.rs +++ b/packages/backend-rs/src/model/entity/page.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use super::sea_orm_active_enums::PageVisibility; use sea_orm::entity::prelude::*; diff --git a/packages/backend-rs/src/model/entity/page_like.rs b/packages/backend-rs/src/model/entity/page_like.rs index d6aa22c..4f39d80 100644 --- a/packages/backend-rs/src/model/entity/page_like.rs +++ b/packages/backend-rs/src/model/entity/page_like.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/password_reset_request.rs b/packages/backend-rs/src/model/entity/password_reset_request.rs index bd4978a..f097b1a 100644 --- a/packages/backend-rs/src/model/entity/password_reset_request.rs +++ b/packages/backend-rs/src/model/entity/password_reset_request.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; @@ -12,6 +12,7 @@ pub struct Model { pub id: String, #[sea_orm(column_name = "createdAt")] pub created_at: DateTimeWithTimeZone, + #[sea_orm(unique)] pub token: String, #[sea_orm(column_name = "userId")] pub user_id: String, diff --git a/packages/backend-rs/src/model/entity/poll.rs b/packages/backend-rs/src/model/entity/poll.rs index 5012432..c150ca1 100644 --- a/packages/backend-rs/src/model/entity/poll.rs +++ b/packages/backend-rs/src/model/entity/poll.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use super::sea_orm_active_enums::PollNoteVisibility; use sea_orm::entity::prelude::*; diff --git a/packages/backend-rs/src/model/entity/poll_vote.rs b/packages/backend-rs/src/model/entity/poll_vote.rs index f3a84d2..83c42b8 100644 --- a/packages/backend-rs/src/model/entity/poll_vote.rs +++ b/packages/backend-rs/src/model/entity/poll_vote.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/prelude.rs b/packages/backend-rs/src/model/entity/prelude.rs index 57fff02..00ffe2e 100644 --- a/packages/backend-rs/src/model/entity/prelude.rs +++ b/packages/backend-rs/src/model/entity/prelude.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 pub use super::abuse_user_report::Entity as AbuseUserReport; pub use super::access_token::Entity as AccessToken; diff --git a/packages/backend-rs/src/model/entity/promo_note.rs b/packages/backend-rs/src/model/entity/promo_note.rs index 24b1b98..bb4726d 100644 --- a/packages/backend-rs/src/model/entity/promo_note.rs +++ b/packages/backend-rs/src/model/entity/promo_note.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/promo_read.rs b/packages/backend-rs/src/model/entity/promo_read.rs index 097b6e3..ee17eee 100644 --- a/packages/backend-rs/src/model/entity/promo_read.rs +++ b/packages/backend-rs/src/model/entity/promo_read.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/registration_ticket.rs b/packages/backend-rs/src/model/entity/registration_ticket.rs index 7a25c01..69e8828 100644 --- a/packages/backend-rs/src/model/entity/registration_ticket.rs +++ b/packages/backend-rs/src/model/entity/registration_ticket.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; @@ -12,6 +12,7 @@ pub struct Model { pub id: String, #[sea_orm(column_name = "createdAt")] pub created_at: DateTimeWithTimeZone, + #[sea_orm(unique)] pub code: String, } diff --git a/packages/backend-rs/src/model/entity/registry_item.rs b/packages/backend-rs/src/model/entity/registry_item.rs index 7dbc5e3..c6be925 100644 --- a/packages/backend-rs/src/model/entity/registry_item.rs +++ b/packages/backend-rs/src/model/entity/registry_item.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/relay.rs b/packages/backend-rs/src/model/entity/relay.rs index 6648179..57ea865 100644 --- a/packages/backend-rs/src/model/entity/relay.rs +++ b/packages/backend-rs/src/model/entity/relay.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use super::sea_orm_active_enums::RelayStatus; use sea_orm::entity::prelude::*; @@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize}; pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id: String, + #[sea_orm(unique)] pub inbox: String, pub status: RelayStatus, } diff --git a/packages/backend-rs/src/model/entity/renote_muting.rs b/packages/backend-rs/src/model/entity/renote_muting.rs index 1adc965..2da55c5 100644 --- a/packages/backend-rs/src/model/entity/renote_muting.rs +++ b/packages/backend-rs/src/model/entity/renote_muting.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/reply_muting.rs b/packages/backend-rs/src/model/entity/reply_muting.rs index 5f01f22..acd7727 100644 --- a/packages/backend-rs/src/model/entity/reply_muting.rs +++ b/packages/backend-rs/src/model/entity/reply_muting.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/sea_orm_active_enums.rs b/packages/backend-rs/src/model/entity/sea_orm_active_enums.rs index 4cdbccf..672ba3c 100644 --- a/packages/backend-rs/src/model/entity/sea_orm_active_enums.rs +++ b/packages/backend-rs/src/model/entity/sea_orm_active_enums.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/signin.rs b/packages/backend-rs/src/model/entity/signin.rs index aec9f2d..89f18a0 100644 --- a/packages/backend-rs/src/model/entity/signin.rs +++ b/packages/backend-rs/src/model/entity/signin.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/sw_subscription.rs b/packages/backend-rs/src/model/entity/sw_subscription.rs index c675781..0a67b15 100644 --- a/packages/backend-rs/src/model/entity/sw_subscription.rs +++ b/packages/backend-rs/src/model/entity/sw_subscription.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use super::sea_orm_active_enums::PushSubscriptionType; use sea_orm::entity::prelude::*; diff --git a/packages/backend-rs/src/model/entity/used_username.rs b/packages/backend-rs/src/model/entity/used_username.rs index 84e55ba..61bd6ab 100644 --- a/packages/backend-rs/src/model/entity/used_username.rs +++ b/packages/backend-rs/src/model/entity/used_username.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/user.rs b/packages/backend-rs/src/model/entity/user.rs index f13dc4c..38b2736 100644 --- a/packages/backend-rs/src/model/entity/user.rs +++ b/packages/backend-rs/src/model/entity/user.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use super::sea_orm_active_enums::UserEmojiModPerm; use sea_orm::entity::prelude::*; diff --git a/packages/backend-rs/src/model/entity/user_group.rs b/packages/backend-rs/src/model/entity/user_group.rs index f035750..c97536e 100644 --- a/packages/backend-rs/src/model/entity/user_group.rs +++ b/packages/backend-rs/src/model/entity/user_group.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/user_group_invitation.rs b/packages/backend-rs/src/model/entity/user_group_invitation.rs index 41073b3..fc69b36 100644 --- a/packages/backend-rs/src/model/entity/user_group_invitation.rs +++ b/packages/backend-rs/src/model/entity/user_group_invitation.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/user_group_invite.rs b/packages/backend-rs/src/model/entity/user_group_invite.rs index 28c8311..467ec6a 100644 --- a/packages/backend-rs/src/model/entity/user_group_invite.rs +++ b/packages/backend-rs/src/model/entity/user_group_invite.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/user_group_joining.rs b/packages/backend-rs/src/model/entity/user_group_joining.rs index 99f38d1..bbed05b 100644 --- a/packages/backend-rs/src/model/entity/user_group_joining.rs +++ b/packages/backend-rs/src/model/entity/user_group_joining.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/user_ip.rs b/packages/backend-rs/src/model/entity/user_ip.rs index 326d82b..1e6e093 100644 --- a/packages/backend-rs/src/model/entity/user_ip.rs +++ b/packages/backend-rs/src/model/entity/user_ip.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/user_keypair.rs b/packages/backend-rs/src/model/entity/user_keypair.rs index cbc203e..6b47e5b 100644 --- a/packages/backend-rs/src/model/entity/user_keypair.rs +++ b/packages/backend-rs/src/model/entity/user_keypair.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/user_list.rs b/packages/backend-rs/src/model/entity/user_list.rs index 839eab1..061ddb1 100644 --- a/packages/backend-rs/src/model/entity/user_list.rs +++ b/packages/backend-rs/src/model/entity/user_list.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/user_list_joining.rs b/packages/backend-rs/src/model/entity/user_list_joining.rs index 3158cba..c7d9ddb 100644 --- a/packages/backend-rs/src/model/entity/user_list_joining.rs +++ b/packages/backend-rs/src/model/entity/user_list_joining.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/user_note_pining.rs b/packages/backend-rs/src/model/entity/user_note_pining.rs index 2a4c4e8..4139e78 100644 --- a/packages/backend-rs/src/model/entity/user_note_pining.rs +++ b/packages/backend-rs/src/model/entity/user_note_pining.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/user_pending.rs b/packages/backend-rs/src/model/entity/user_pending.rs index 6c0265b..5f731a9 100644 --- a/packages/backend-rs/src/model/entity/user_pending.rs +++ b/packages/backend-rs/src/model/entity/user_pending.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; @@ -12,6 +12,7 @@ pub struct Model { pub id: String, #[sea_orm(column_name = "createdAt")] pub created_at: DateTimeWithTimeZone, + #[sea_orm(unique)] pub code: String, pub username: String, pub email: String, diff --git a/packages/backend-rs/src/model/entity/user_profile.rs b/packages/backend-rs/src/model/entity/user_profile.rs index afebc6f..e33e242 100644 --- a/packages/backend-rs/src/model/entity/user_profile.rs +++ b/packages/backend-rs/src/model/entity/user_profile.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use super::sea_orm_active_enums::UserProfileFfvisibility; use super::sea_orm_active_enums::UserProfileMutingNotificationTypes; diff --git a/packages/backend-rs/src/model/entity/user_publickey.rs b/packages/backend-rs/src/model/entity/user_publickey.rs index a0605e9..0b00e66 100644 --- a/packages/backend-rs/src/model/entity/user_publickey.rs +++ b/packages/backend-rs/src/model/entity/user_publickey.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; pub struct Model { #[sea_orm(column_name = "userId", primary_key, auto_increment = false, unique)] pub user_id: String, - #[sea_orm(column_name = "keyId")] + #[sea_orm(column_name = "keyId", unique)] pub key_id: String, #[sea_orm(column_name = "keyPem")] pub key_pem: String, diff --git a/packages/backend-rs/src/model/entity/user_security_key.rs b/packages/backend-rs/src/model/entity/user_security_key.rs index fdc6163..1b48849 100644 --- a/packages/backend-rs/src/model/entity/user_security_key.rs +++ b/packages/backend-rs/src/model/entity/user_security_key.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/model/entity/webhook.rs b/packages/backend-rs/src/model/entity/webhook.rs index b2695be..943146a 100644 --- a/packages/backend-rs/src/model/entity/webhook.rs +++ b/packages/backend-rs/src/model/entity/webhook.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/service/antenna/check_hit.rs b/packages/backend-rs/src/service/antenna/check_hit.rs index 0ef8f13..596f112 100644 --- a/packages/backend-rs/src/service/antenna/check_hit.rs +++ b/packages/backend-rs/src/service/antenna/check_hit.rs @@ -1,18 +1,20 @@ use crate::{ + cache, config::CONFIG, - database::{cache, db_conn}, + database::db_conn, federation::acct::Acct, model::entity::{antenna, blocking, following, note, sea_orm_active_enums::*}, }; +use chrono::Duration; use sea_orm::{prelude::*, QuerySelect}; #[macros::errors] pub enum AntennaCheckError { - #[doc = "database error"] + #[doc = "Database error"] #[error(transparent)] Db(#[from] DbErr), #[error("Redis cache operation has failed")] - Cache(#[from] cache::Error), + Cache(#[from] cache::redis::Error), } fn match_all(space_separated_words: &str, text: &str, case_sensitive: bool) -> bool { @@ -108,7 +110,13 @@ pub(super) async fn check_hit_antenna( .into_tuple::() .all(db) .await?; - cache::set_one(cache::Category::Block, ¬e.user_id, &blocks, 10 * 60).await?; + cache::set_one( + cache::Category::Block, + ¬e.user_id, + &blocks, + Duration::minutes(10), + ) + .await?; blocks }; @@ -137,7 +145,7 @@ pub(super) async fn check_hit_antenna( cache::Category::Follow, &antenna.user_id, &following, - 10 * 60, + Duration::minutes(10), ) .await?; following diff --git a/packages/backend-rs/src/service/antenna/mod.rs b/packages/backend-rs/src/service/antenna/mod.rs index 2f25558..975b1a6 100644 --- a/packages/backend-rs/src/service/antenna/mod.rs +++ b/packages/backend-rs/src/service/antenna/mod.rs @@ -1,4 +1,24 @@ -mod cache; mod check_hit; pub mod process_new_note; pub mod update; + +use crate::{cache::Cache, database::db_conn, model::entity::antenna}; +use sea_orm::prelude::*; +use std::sync::Arc; + +static ANTENNAS_CACHE: Cache> = Cache::new(); + +async fn update() -> Result, DbErr> { + tracing::debug!("updating cache"); + let antennas: Arc<[antenna::Model]> = + antenna::Entity::find().all(db_conn().await?).await?.into(); + ANTENNAS_CACHE.set(antennas.clone()); + Ok(antennas) +} + +async fn get_antennas() -> Result, DbErr> { + if let Some(cache) = ANTENNAS_CACHE.get() { + return Ok(cache); + } + update().await +} diff --git a/packages/backend-rs/src/service/antenna/process_new_note.rs b/packages/backend-rs/src/service/antenna/process_new_note.rs index d9099f8..d04392c 100644 --- a/packages/backend-rs/src/service/antenna/process_new_note.rs +++ b/packages/backend-rs/src/service/antenna/process_new_note.rs @@ -1,5 +1,6 @@ use crate::{ - database::{cache, redis_conn, redis_key, RedisConnError}, + cache, + database::{redis_conn, redis_key, RedisConnError}, federation::acct::Acct, misc::note::elaborate, model::entity::note, @@ -15,16 +16,16 @@ use sea_orm::prelude::*; #[macros::errors] pub enum Error { - #[doc = "database error"] + #[doc = "Database error"] #[error(transparent)] Db(#[from] DbErr), #[error("Redis cache operation has failed")] - Cache(#[from] cache::Error), + Cache(#[from] cache::redis::Error), #[error("failed to execute a Redis command")] Redis(#[from] RedisError), #[error("bad Redis connection")] RedisConn(#[from] RedisConnError), - #[doc = "provided string is not a valid Firefish ID"] + #[doc = "Provided string is not a valid Firefish ID"] #[error(transparent)] InvalidId(#[from] InvalidIdError), #[error("Redis stream operation has failed")] @@ -46,7 +47,7 @@ pub async fn update_antennas_on_new_note( let note_all_texts = elaborate!(note, false).await?; // TODO: do this in parallel - for antenna in antenna::cache::get().await?.iter() { + for antenna in antenna::get_antennas().await?.iter() { if note_muted_users.contains(&antenna.user_id) { continue; } @@ -59,8 +60,11 @@ pub async fn update_antennas_on_new_note( } async fn add_note_to_antenna(antenna_id: &str, note: &Note) -> Result<(), Error> { + // for streaming API + stream::antenna::publish(antenna_id.to_owned(), note).await?; + // for timeline API - redis_conn() + Ok(redis_conn() .await? .xadd_maxlen( redis_key(format!("antennaTimeline:{}", antenna_id)), @@ -68,10 +72,5 @@ async fn add_note_to_antenna(antenna_id: &str, note: &Note) -> Result<(), Error> format!("{}-*", get_timestamp(¬e.id)?), &[("note", ¬e.id)], ) - .await?; - - // for streaming API - stream::antenna::publish(antenna_id.to_owned(), note).await?; - - Ok(()) + .await?) } diff --git a/packages/backend-rs/src/service/antenna/update.rs b/packages/backend-rs/src/service/antenna/update.rs index 169e574..d08ec2c 100644 --- a/packages/backend-rs/src/service/antenna/update.rs +++ b/packages/backend-rs/src/service/antenna/update.rs @@ -2,6 +2,6 @@ #[macros::ts_export] pub async fn update_antenna_cache() -> Result<(), sea_orm::DbErr> { - super::cache::update().await?; + super::update().await?; Ok(()) } diff --git a/packages/backend-rs/src/service/push_notification.rs b/packages/backend-rs/src/service/push_notification.rs index cbf3be0..bbe7189 100644 --- a/packages/backend-rs/src/service/push_notification.rs +++ b/packages/backend-rs/src/service/push_notification.rs @@ -15,17 +15,17 @@ use web_push::*; #[macros::errors] pub enum Error { - #[doc = "database error"] + #[doc = "Database error"] #[error(transparent)] Db(#[from] DbErr), #[error("web push has failed")] WebPush(#[from] WebPushError), #[error("failed to (de)serialize an object")] Serialize(#[from] serde_json::Error), - #[doc = "provided content is invalid"] + #[doc = "Provided content is invalid"] #[error("invalid content ({0})")] InvalidContent(String), - #[doc = "found Mastodon subscription is invalid"] + #[doc = "Found Mastodon subscription is invalid"] #[error("invalid subscription ({0})")] InvalidSubscription(String), #[error("invalid notification ID")] diff --git a/packages/backend-rs/src/service/stream.rs b/packages/backend-rs/src/service/stream.rs index bd412b2..4c8f05b 100644 --- a/packages/backend-rs/src/service/stream.rs +++ b/packages/backend-rs/src/service/stream.rs @@ -112,13 +112,11 @@ pub async fn publish_to_stream( value.ok_or(Error::InvalidContent)? }; - redis_conn() + Ok(redis_conn() .await? .publish( &CONFIG.host, format!("{{\"channel\":\"{}\",\"message\":{}}}", channel, message), ) - .await?; - - Ok(()) + .await?) } diff --git a/packages/backend-rs/src/util/id.rs b/packages/backend-rs/src/util/id.rs index 4cce589..0c580b3 100644 --- a/packages/backend-rs/src/util/id.rs +++ b/packages/backend-rs/src/util/id.rs @@ -3,11 +3,10 @@ use crate::config::CONFIG; use basen::BASE36; use chrono::{DateTime, NaiveDateTime, Utc}; -use once_cell::sync::OnceCell; -use std::cmp; +use std::{cmp, sync::OnceLock}; -static FINGERPRINT: OnceCell = OnceCell::new(); -static GENERATOR: OnceCell = OnceCell::new(); +static FINGERPRINT: OnceLock = OnceLock::new(); +static GENERATOR: OnceLock = OnceLock::new(); const TIME_2000: i64 = 946_684_800_000; const TIMESTAMP_LENGTH: u8 = 8; diff --git a/packages/backend/package.json b/packages/backend/package.json index d0f89a4..cea4f20 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -13,33 +13,33 @@ "build": "pnpm tsc --project tsconfig.json ; pnpm tsc-alias --project tsconfig.json", "build:debug": "pnpm tsc --sourceMap --project tsconfig.json ; pnpm tsc-alias --project tsconfig.json", "watch": "pnpm tsc --project tsconfig.json --watch ; pnpm tsc-alias --project tsconfig.json --watch", - "lint": "pnpm biome check --write **/*.ts", - "format": "pnpm biome format * --write" + "lint": "pnpm biome check --write src", + "format": "pnpm biome format --write src" }, "dependencies": { - "@bull-board/api": "5.21.1", - "@bull-board/koa": "5.21.1", - "@bull-board/ui": "5.21.1", - "@discordapp/twemoji": "15.0.3", + "@bull-board/api": "5.21.4", + "@bull-board/koa": "5.21.4", + "@bull-board/ui": "5.21.4", + "@discordapp/twemoji": "15.1.0", "@koa/cors": "5.0.0", "@koa/multer": "3.0.2", - "@koa/router": "12.0.1", + "@koa/router": "13.0.0", "@ladjs/koa-views": "9.0.0", "@peertube/http-signature": "1.7.0", - "@redocly/openapi-core": "1.18.1", + "@redocly/openapi-core": "1.19.0", "@sinonjs/fake-timers": "11.2.2", - "adm-zip": "0.5.14", + "adm-zip": "0.5.15", "ajv": "8.17.1", "archiver": "7.0.1", "async-lock": "1.4.1", "async-mutex": "0.5.0", - "aws-sdk": "2.1664.0", - "axios": "1.7.2", + "aws-sdk": "2.1677.0", + "axios": "1.7.4", "backend-rs": "workspace:*", "blurhash": "2.0.5", - "bull": "4.15.1", + "bull": "4.16.0", "cacheable-lookup": "git+https://github.com/TheEssem/cacheable-lookup.git#dd2fb616366a3c68dcf321a57a67295967b204bf", - "cbor-x": "1.5.9", + "cbor-x": "1.6.0", "chalk": "5.3.0", "cli-highlight": "2.1.11", "color-convert": "2.0.1", @@ -49,7 +49,7 @@ "deep-email-validator": "0.1.21", "escape-regexp": "0.0.1", "feed": "4.2.2", - "file-type": "19.3.0", + "file-type": "19.4.1", "firefish-js": "workspace:*", "fluent-ffmpeg": "2.1.3", "form-data": "4.0.0", @@ -58,7 +58,7 @@ "hpagent": "1.2.0", "ioredis": "5.4.1", "ip-cidr": "4.0.2", - "is-svg": "5.0.1", + "is-svg": "5.1.0", "jsdom": "24.1.1", "json5": "2.2.3", "jsonld": "8.3.2", @@ -87,10 +87,8 @@ "probe-image-size": "7.2.3", "promise-limit": "2.7.0", "punycode": "2.3.1", - "pureimage": "0.4.13", - "qrcode": "1.5.3", - "qs": "6.12.3", - "random-seed": "0.3.0", + "qrcode": "1.5.4", + "qs": "6.13.0", "ratelimiter": "3.4.1", "redis-semaphore": "5.6.0", "reflect-metadata": "0.2.2", @@ -99,7 +97,7 @@ "rss-parser": "3.13.0", "sanitize-html": "2.13.0", "semver": "7.6.3", - "sharp": "0.33.4", + "sharp": "0.33.5", "stringz": "2.1.0", "summaly": "2.7.0", "syslog-pro": "1.0.0", @@ -120,7 +118,7 @@ "@types/color-convert": "2.0.3", "@types/content-disposition": "0.5.8", "@types/escape-regexp": "0.0.3", - "@types/fluent-ffmpeg": "2.1.24", + "@types/fluent-ffmpeg": "2.1.25", "@types/jsdom": "21.1.7", "@types/jsonld": "1.5.15", "@types/jsrsasign": "10.5.14", @@ -135,7 +133,7 @@ "@types/koa__cors": "5.0.0", "@types/koa__multer": "2.0.7", "@types/koa__router": "12.0.4", - "@types/node": "20.14.13", + "@types/node": "20.15.0", "@types/node-fetch": "2.6.11", "@types/nodemailer": "6.4.15", "@types/oauth": "0.9.5", @@ -157,7 +155,7 @@ "@types/tmp": "0.2.6", "@types/uuid": "10.0.0", "@types/websocket": "1.0.10", - "@types/ws": "8.5.11", + "@types/ws": "8.5.12", "cross-env": "7.0.3", "pug": "3.0.3", "strict-event-emitter-types": "2.0.0", @@ -165,7 +163,7 @@ "ts-node": "10.9.2", "tsc-alias": "1.8.10", "tsconfig-paths": "4.2.0", - "type-fest": "4.23.0", + "type-fest": "4.25.0", "typescript": "5.5.4", "webpack": "5.93.0", "ws": "8.18.0" diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index 801d2cf..aae9cc2 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -7,8 +7,6 @@ import { greet, removeOldAttestationChallenges, showServerInfo, - updateMetaCache, - updateNodeinfoCache, type Config, } from "backend-rs"; import { config } from "@/config.js"; @@ -29,7 +27,6 @@ export async function masterMain() { showEnvironment(); showNodejsVersion(); await connectDb(); - await updateMetaCache(); } catch (e) { bootLogger.error( `Fatal error occurred during initialization:\n${inspect(e)}`, @@ -51,10 +48,6 @@ export async function masterMain() { import("../daemons/server-stats.js").then((x) => x.default()); import("../daemons/queue-stats.js").then((x) => x.default()); - // Update meta cache every 5 minitues - setInterval(() => updateMetaCache(), 1000 * 60 * 5); - // Update nodeinfo cache every hour - setInterval(() => updateNodeinfoCache(), 1000 * 60 * 60); // Remove old attestation challenges setInterval(() => removeOldAttestationChallenges(), 1000 * 60 * 30); } @@ -77,7 +70,7 @@ function showNodejsVersion(): void { nodejsLogger.info(`Version ${process.version} detected.`); - const minVersion = "v18.19.0"; + const minVersion = "v18.20.0"; if (semver.lt(process.version, minVersion)) { nodejsLogger.error(`At least Node.js ${minVersion} required!`); process.exit(1); diff --git a/packages/backend/src/migration/1644010796173-convert-hard-mutes.ts b/packages/backend/src/migration/1644010796173-convert-hard-mutes.ts index ccadea6..a2de678 100644 --- a/packages/backend/src/migration/1644010796173-convert-hard-mutes.ts +++ b/packages/backend/src/migration/1644010796173-convert-hard-mutes.ts @@ -2,11 +2,11 @@ import type { MigrationInterface, QueryRunner } from "typeorm"; export class convertHardMutes1644010796173 implements MigrationInterface { async up(queryRunner: QueryRunner): Promise { - let entries = await queryRunner.query( + const entries = await queryRunner.query( `SELECT "userId", "mutedWords" FROM "user_profile" WHERE "userHost" IS NULL`, ); for (let i = 0; i < entries.length; i++) { - let words = entries[i].mutedWords + const words = entries[i].mutedWords .map((line) => { if (typeof line === "string") return []; const regexp = line.join(" ").match(/^\/(.+)\/(.*)$/); @@ -39,11 +39,11 @@ export class convertHardMutes1644010796173 implements MigrationInterface { } async down(queryRunner: QueryRunner): Promise { - let entries = await queryRunner.query( + const entries = await queryRunner.query( `SELECT "userId", "mutedWords" FROM "user_profile"`, ); for (let i = 0; i < entries.length; i++) { - let words = entries[i].mutedWords + const words = entries[i].mutedWords .map((line) => { if (Array.isArray(line)) { return line; diff --git a/packages/backend/src/migration/1652859567549-uniform-themecolor.ts b/packages/backend/src/migration/1652859567549-uniform-themecolor.ts index 3d15b6e..0c62254 100644 --- a/packages/backend/src/migration/1652859567549-uniform-themecolor.ts +++ b/packages/backend/src/migration/1652859567549-uniform-themecolor.ts @@ -5,7 +5,7 @@ import tinycolor from "tinycolor2"; export class uniformThemecolor1652859567549 implements MigrationInterface { async up(queryRunner: QueryRunner): Promise { const formatColor = (color) => { - let tc = new tinycolor(color); + const tc = new tinycolor(color); if (tc.isValid()) { return tc.toHexString(); } else { diff --git a/packages/backend/src/migration/1722346019160-set-emoji-public-url.ts b/packages/backend/src/migration/1722346019160-set-emoji-public-url.ts new file mode 100644 index 0000000..8f3c95c --- /dev/null +++ b/packages/backend/src/migration/1722346019160-set-emoji-public-url.ts @@ -0,0 +1,18 @@ +import type { MigrationInterface, QueryRunner } from "typeorm"; + +export class SetEmojiPublicUrl1722346019160 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `UPDATE "emoji" SET "publicUrl" = "originalUrl" WHERE "publicUrl" = ''`, + ); + await queryRunner.query( + `ALTER TABLE "emoji" ALTER COLUMN "publicUrl" DROP DEFAULT`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "emoji" ALTER COLUMN "publicUrl" SET DEFAULT ''`, + ); + } +} diff --git a/packages/backend/src/models/entities/emoji.ts b/packages/backend/src/models/entities/emoji.ts index 87b525d..0e5dcc3 100644 --- a/packages/backend/src/models/entities/emoji.ts +++ b/packages/backend/src/models/entities/emoji.ts @@ -38,7 +38,6 @@ export class Emoji { @Column("varchar", { length: 512, - default: "", }) public publicUrl: string; diff --git a/packages/backend/src/models/entities/note.ts b/packages/backend/src/models/entities/note.ts index 0001777..43e289c 100644 --- a/packages/backend/src/models/entities/note.ts +++ b/packages/backend/src/models/entities/note.ts @@ -138,7 +138,6 @@ export class Note { @Column("integer", { default: 0, - select: false, }) public score: number; diff --git a/packages/backend/src/models/entities/user-profile.ts b/packages/backend/src/models/entities/user-profile.ts index f74e04d..3d514db 100644 --- a/packages/backend/src/models/entities/user-profile.ts +++ b/packages/backend/src/models/entities/user-profile.ts @@ -195,7 +195,6 @@ export class UserProfile { @Index() @Column("boolean", { default: false, - select: false, }) public enableWordMute: boolean; diff --git a/packages/backend/src/models/entities/user.ts b/packages/backend/src/models/entities/user.ts index e71e04e..3a6bd96 100644 --- a/packages/backend/src/models/entities/user.ts +++ b/packages/backend/src/models/entities/user.ts @@ -56,7 +56,6 @@ export class User { @Index() @Column("varchar", { length: 128, - select: false, comment: "The username (lowercased) of the User.", }) public usernameLower: string; diff --git a/packages/backend/src/models/repositories/renote-muting.ts b/packages/backend/src/models/repositories/renote-muting.ts index 18fd343..156d1a9 100644 --- a/packages/backend/src/models/repositories/renote-muting.ts +++ b/packages/backend/src/models/repositories/renote-muting.ts @@ -1,7 +1,7 @@ import { db } from "@/db/postgre.js"; -import { Packed } from "@/misc/schema.js"; +import type { Packed } from "@/misc/schema.js"; import { RenoteMuting } from "@/models/entities/renote-muting.js"; -import { User } from "@/models/entities/user.js"; +import type { User } from "@/models/entities/user.js"; import { awaitAll } from "@/prelude/await-all.js"; import { Users } from "../index.js"; diff --git a/packages/backend/src/models/repositories/reply-muting.ts b/packages/backend/src/models/repositories/reply-muting.ts index 948dc7d..a446941 100644 --- a/packages/backend/src/models/repositories/reply-muting.ts +++ b/packages/backend/src/models/repositories/reply-muting.ts @@ -1,7 +1,7 @@ import { db } from "@/db/postgre.js"; -import { Packed } from "@/misc/schema.js"; +import type { Packed } from "@/misc/schema.js"; import { ReplyMuting } from "@/models/entities/reply-muting.js"; -import { User } from "@/models/entities/user.js"; +import type { User } from "@/models/entities/user.js"; import { awaitAll } from "@/prelude/await-all.js"; import { Users } from "../index.js"; diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts index c04fb97..f64c067 100644 --- a/packages/backend/src/models/repositories/user.ts +++ b/packages/backend/src/models/repositories/user.ts @@ -471,6 +471,7 @@ export const UserRepository = db.getRepository(User).extend({ .fetch( user.host, () => Instances.findOneBy({ host: user.host! }), + false, (v) => v != null, ) .then((instance) => diff --git a/packages/backend/src/queue/processors/db/import-firefish-post.ts b/packages/backend/src/queue/processors/db/import-firefish-post.ts index d004481..08d5331 100644 --- a/packages/backend/src/queue/processors/db/import-firefish-post.ts +++ b/packages/backend/src/queue/processors/db/import-firefish-post.ts @@ -39,7 +39,7 @@ export async function importCkPost( */ const urls = (post.files || []) .map((x: any) => x.url) - .filter((x: String) => x.startsWith("http")); + .filter((x: string) => x.startsWith("http")); const files: DriveFile[] = []; for (const url of urls) { try { diff --git a/packages/backend/src/queue/processors/db/import-masto-post.ts b/packages/backend/src/queue/processors/db/import-masto-post.ts index 4bf351e..31749a1 100644 --- a/packages/backend/src/queue/processors/db/import-masto-post.ts +++ b/packages/backend/src/queue/processors/db/import-masto-post.ts @@ -68,7 +68,7 @@ export async function importMastoPost( if (!isRenote && files.length == 0) { const urls = post.object.attachment .map((x: any) => x.url) - .filter((x: String) => x.startsWith("http")); + .filter((x: string) => x.startsWith("http")); files = []; for (const url of urls) { try { diff --git a/packages/backend/src/remote/activitypub/kernel/like.ts b/packages/backend/src/remote/activitypub/kernel/like.ts index 7b30d1c..eb5ff55 100644 --- a/packages/backend/src/remote/activitypub/kernel/like.ts +++ b/packages/backend/src/remote/activitypub/kernel/like.ts @@ -12,11 +12,7 @@ export default async (actor: CacheableRemoteUser, activity: ILike) => { await extractEmojis(activity.tag || [], actor.host).catch(() => null); - return await create( - actor, - note, - activity._misskey_reaction || activity.content || activity.name, - ) + return await create(actor, note, activity.content || activity.name) .catch((e) => { if (e.id === "51c42bb4-931a-456b-bff7-e5a8a70dd298") { return "skip: already reacted"; diff --git a/packages/backend/src/remote/activitypub/misc/contexts.ts b/packages/backend/src/remote/activitypub/misc/contexts.ts index d0b3f56..a3bc03c 100644 --- a/packages/backend/src/remote/activitypub/misc/contexts.ts +++ b/packages/backend/src/remote/activitypub/misc/contexts.ts @@ -550,7 +550,6 @@ export const WellKnownContext = { // Misskey misskey: "https://misskey-hub.net/ns#", _misskey_talk: "misskey:_misskey_talk", - _misskey_reaction: "misskey:_misskey_reaction", _misskey_votes: "misskey:_misskey_votes", _misskey_summary: "misskey:_misskey_summary", isCat: "misskey:isCat", diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index e9c1b5b..663f9be 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -207,7 +207,7 @@ export async function createPerson( try { const data = await fetch(person.followers, { headers: { Accept: "application/json" }, - size: 1024 * 1024 + size: 1024 * 1024, }); const json_data = JSON.parse(await data.text()); @@ -223,7 +223,7 @@ export async function createPerson( try { const data = await fetch(person.following, { headers: { Accept: "application/json" }, - size: 1024 * 1024 + size: 1024 * 1024, }); const json_data = JSON.parse(await data.text()); @@ -492,7 +492,7 @@ export async function updatePerson( try { const data = await fetch(person.followers, { headers: { Accept: "application/json" }, - size: 1024 * 1024 + size: 1024 * 1024, }); const json_data = JSON.parse(await data.text()); @@ -508,7 +508,7 @@ export async function updatePerson( try { const data = await fetch(person.following, { headers: { Accept: "application/json" }, - size: 1024 * 1024 + size: 1024 * 1024, }); const json_data = JSON.parse(await data.text()); diff --git a/packages/backend/src/remote/activitypub/renderer/note.ts b/packages/backend/src/remote/activitypub/renderer/note.ts index dc978f1..fd099cb 100644 --- a/packages/backend/src/remote/activitypub/renderer/note.ts +++ b/packages/backend/src/remote/activitypub/renderer/note.ts @@ -6,9 +6,7 @@ import { DriveFiles, Notes, Users, Emojis, Polls } from "@/models/index.js"; import type { Emoji } from "@/models/entities/emoji.js"; import type { Poll } from "@/models/entities/poll.js"; import toHtml from "@/remote/activitypub/misc/get-note-html.js"; -import renderEmoji from "./emoji.js"; -import renderMention from "./mention.js"; -import renderHashtag from "./hashtag.js"; +import { renderEmoji, renderHashtag, renderMention } from "backend-rs"; import renderDocument from "./document.js"; export default async function renderNote( diff --git a/packages/backend/src/remote/activitypub/renderer/person.ts b/packages/backend/src/remote/activitypub/renderer/person.ts index c637144..39642c1 100644 --- a/packages/backend/src/remote/activitypub/renderer/person.ts +++ b/packages/backend/src/remote/activitypub/renderer/person.ts @@ -4,12 +4,11 @@ import { config } from "@/config.js"; import type { ILocalUser } from "@/models/entities/user.js"; import { DriveFiles, UserProfiles } from "@/models/index.js"; import { getUserKeypair } from "@/misc/keypair-store.js"; -import { toHtml } from "../../../mfm/to-html.js"; +import { toHtml } from "@/mfm/to-html.js"; import renderImage from "./image.js"; import renderKey from "./key.js"; import { getEmojis } from "./note.js"; -import renderEmoji from "./emoji.js"; -import renderHashtag from "./hashtag.js"; +import { renderEmoji, renderHashtag } from "backend-rs"; import type { IIdentifier } from "../models/identifier.js"; export async function renderPerson(user: ILocalUser) { diff --git a/packages/backend/src/remote/activitypub/request.ts b/packages/backend/src/remote/activitypub/request.ts index f1f4962..22906b9 100644 --- a/packages/backend/src/remote/activitypub/request.ts +++ b/packages/backend/src/remote/activitypub/request.ts @@ -42,7 +42,7 @@ export default async (user: { id: User["id"] }, url: string, object: any) => { export async function apGet( url: string, user?: ILocalUser, - redirects: boolean = true, + redirects = true, ): Promise<{ finalUrl: string; content: IObject }> { if (!isSafeUrl(url)) { throw new StatusError("Invalid URL", 400); diff --git a/packages/backend/src/remote/activitypub/resolver.ts b/packages/backend/src/remote/activitypub/resolver.ts index c9d94e9..55eaa0d 100644 --- a/packages/backend/src/remote/activitypub/resolver.ts +++ b/packages/backend/src/remote/activitypub/resolver.ts @@ -6,6 +6,8 @@ import { isAllowedServer, isBlockedServer, isSelfHost, + renderFollow, + renderLike, } from "backend-rs"; import { apGet } from "./request.js"; import type { IObject, ICollection, IOrderedCollection } from "./type.js"; @@ -19,12 +21,10 @@ import { } from "@/models/index.js"; import { parseUri } from "./db-resolver.js"; import renderNote from "@/remote/activitypub/renderer/note.js"; -import { renderLike } from "@/remote/activitypub/renderer/like.js"; import { renderPerson } from "@/remote/activitypub/renderer/person.js"; import renderQuestion from "@/remote/activitypub/renderer/question.js"; import renderCreate from "@/remote/activitypub/renderer/create.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js"; -import renderFollow from "@/remote/activitypub/renderer/follow.js"; import { apLogger } from "@/remote/activitypub/logger.js"; import { IsNull, Not } from "typeorm"; @@ -181,7 +181,7 @@ export default class Resolver { } case "likes": { const reaction = await NoteReactions.findOneByOrFail({ id: parsed.id }); - return renderActivity(renderLike(reaction, { uri: null })); + return renderActivity(await renderLike(reaction)); } case "follows": { // if rest is a diff --git a/packages/backend/src/remote/activitypub/type.ts b/packages/backend/src/remote/activitypub/type.ts index 9288938..13358f6 100644 --- a/packages/backend/src/remote/activitypub/type.ts +++ b/packages/backend/src/remote/activitypub/type.ts @@ -303,7 +303,7 @@ export interface IRemove extends IActivity { export interface ILike extends IActivity { type: "Like" | "EmojiReaction" | "EmojiReact"; - _misskey_reaction?: string; + content?: string; } export interface IAnnounce extends IActivity { diff --git a/packages/backend/src/server/activitypub.ts b/packages/backend/src/server/activitypub.ts index 81513f5..bd028bb 100644 --- a/packages/backend/src/server/activitypub.ts +++ b/packages/backend/src/server/activitypub.ts @@ -7,9 +7,15 @@ import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import renderNote from "@/remote/activitypub/renderer/note.js"; import renderKey from "@/remote/activitypub/renderer/key.js"; import { renderPerson } from "@/remote/activitypub/renderer/person.js"; -import renderEmoji from "@/remote/activitypub/renderer/emoji.js"; import { inbox as processInbox } from "@/queue/index.js"; -import { fetchMeta, getInstanceActor, isSelfHost } from "backend-rs"; +import { + fetchMeta, + getInstanceActor, + isSelfHost, + renderEmoji, + renderFollow, + renderLike, +} from "backend-rs"; import { Notes, Users, @@ -18,13 +24,11 @@ import { FollowRequests, } from "@/models/index.js"; import type { ILocalUser, User } from "@/models/entities/user.js"; -import { renderLike } from "@/remote/activitypub/renderer/like.js"; import { getUserKeypair } from "@/misc/keypair-store.js"; import { checkFetch, getSignatureUser, } from "@/remote/activitypub/check-fetch.js"; -import renderFollow from "@/remote/activitypub/renderer/follow.js"; import Featured from "./activitypub/featured.js"; import Following from "./activitypub/following.js"; import Followers from "./activitypub/followers.js"; @@ -462,7 +466,7 @@ router.get("/likes/:like", async (ctx) => { return; } - ctx.body = renderActivity(await renderLike(reaction, note)); + ctx.body = renderActivity(await renderLike(reaction)); const instanceMeta = await fetchMeta(); if (instanceMeta.secureMode || instanceMeta.privateMode) { ctx.set("Cache-Control", "private, max-age=0, must-revalidate"); diff --git a/packages/backend/src/server/api/common/inject-featured.ts b/packages/backend/src/server/api/common/inject-featured.ts index 30ba3ec..3c6cb39 100644 --- a/packages/backend/src/server/api/common/inject-featured.ts +++ b/packages/backend/src/server/api/common/inject-featured.ts @@ -19,7 +19,6 @@ export async function injectFeatured(timeline: Note[], user?: User | null) { const day = 1000 * 60 * 60 * 24 * 3; // 3日前まで const query = Notes.createQueryBuilder("note") - .addSelect("note.score") .where("note.userHost IS NULL") .andWhere("note.score > 0") .andWhere("note.createdAt > :date", { date: new Date(Date.now() - day) }) 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 e15924d..99c9f3d 100644 --- a/packages/backend/src/server/api/common/read-messaging-message.ts +++ b/packages/backend/src/server/api/common/read-messaging-message.ts @@ -2,6 +2,7 @@ import { publishToChatStream, publishToGroupChatStream, publishToChatIndexStream, + renderRead, sendPushNotification, publishToMainStream, Event, @@ -13,10 +14,10 @@ import { In } from "typeorm"; import { IdentifiableError } from "@/misc/identifiable-error.js"; import type { UserGroup } from "@/models/entities/user-group.js"; import { toArray } from "@/prelude/array.js"; -import { renderReadActivity } from "@/remote/activitypub/renderer/read.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import { deliver } from "@/queue/index.js"; import orderedCollection from "@/remote/activitypub/renderer/ordered-collection.js"; +import { unsafeCast } from "@/prelude/unsafe-cast.js"; /** * Mark messages as read @@ -164,7 +165,9 @@ export async function deliverReadActivity( messages: MessagingMessage | MessagingMessage[], ) { messages = toArray(messages).filter((x) => x.uri); - const contents = messages.map((x) => renderReadActivity(user, x)); + const contents = messages.map((x) => + unsafeCast>(renderRead(user.id, x.uri)), + ); if (contents.length > 1) { const collection = orderedCollection( diff --git a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts index c93024b..bcab034 100644 --- a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts +++ b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts @@ -1,10 +1,9 @@ import define from "@/server/api/define.js"; import { AbuseUserReports, Users } from "@/models/index.js"; -import { getInstanceActor } from "backend-rs"; +import { getInstanceActor, renderFlag } from "backend-rs"; import { deliver } from "@/queue/index.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js"; -import { renderFlag } from "@/remote/activitypub/renderer/flag.js"; -import { ILocalUser } from "@/models/entities/user"; +import type { ILocalUser } from "@/models/entities/user"; export const meta = { tags: ["admin"], @@ -35,7 +34,7 @@ export default define(meta, paramDef, async (ps, me) => { deliver( actor.id, - renderActivity(renderFlag(actor, [targetUser.uri!], report.comment)), + renderActivity(await renderFlag(targetUser.uri, report.comment)), targetUser.inbox, ); } diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts index 4f90145..0d0f2cd 100644 --- a/packages/backend/src/server/api/endpoints/notes/featured.ts +++ b/packages/backend/src/server/api/endpoints/notes/featured.ts @@ -42,7 +42,6 @@ export default define(meta, paramDef, async (ps, user) => { const day = 1000 * 60 * 60 * 24 * ps.days; const query = Notes.createQueryBuilder("note") - .addSelect("note.score") .andWhere("note.score > 0") .andWhere("note.createdAt > :date", { date: new Date(Date.now() - day) }) .andWhere("note.visibility = 'public'") diff --git a/packages/backend/src/server/api/mastodon/helpers/misc.ts b/packages/backend/src/server/api/mastodon/helpers/misc.ts index 01a3212..9d6b374 100644 --- a/packages/backend/src/server/api/mastodon/helpers/misc.ts +++ b/packages/backend/src/server/api/mastodon/helpers/misc.ts @@ -360,7 +360,6 @@ export class MiscHelpers { ): Promise { if (limit > 40) limit = 40; const query = Notes.createQueryBuilder("note") - .addSelect("note.score") .andWhere("note.score > 0") .andWhere("note.createdAt > :date", { date: new Date(Date.now() - 1000 * 60 * 60 * 24), diff --git a/packages/backend/src/server/file/byte-range-readable.ts b/packages/backend/src/server/file/byte-range-readable.ts index 9699f95..d557245 100644 --- a/packages/backend/src/server/file/byte-range-readable.ts +++ b/packages/backend/src/server/file/byte-range-readable.ts @@ -82,7 +82,7 @@ function extractRanges( } function createBoundary(len: number): string { - let chars = []; + const chars = []; for (let i = 0; i < len; i = i + 1) { chars[i] = BOUNDARY_CHARS.charAt( Math.floor(Math.random() * BOUNDARY_CHARS.length), diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts index 6db3c37..685d86f 100644 --- a/packages/backend/src/server/index.ts +++ b/packages/backend/src/server/index.ts @@ -15,8 +15,7 @@ import { IsNull } from "typeorm"; import { config } from "@/config.js"; import Logger from "@/services/logger.js"; import { Users } from "@/models/index.js"; -import { fetchMeta, stringToAcct } from "backend-rs"; -import { genIdenticon } from "@/misc/gen-identicon.js"; +import { fetchMeta, genIdenticon, stringToAcct } from "backend-rs"; import { createTemp } from "@/misc/create-temp.js"; import activityPub from "./activitypub.js"; import nodeinfo from "./nodeinfo.js"; @@ -120,7 +119,7 @@ router.get("/identicon/:x", async (ctx) => { const instanceMeta = await fetchMeta(); if (instanceMeta.enableIdenticonGeneration) { const [temp, cleanup] = await createTemp(); - await genIdenticon(ctx.params.x, fs.createWriteStream(temp)); + fs.writeFileSync(temp, await genIdenticon(ctx.params.x)); ctx.set("Content-Type", "image/png"); ctx.body = fs.createReadStream(temp).on("close", () => cleanup()); } else { diff --git a/packages/backend/src/server/web/bios.css b/packages/backend/src/server/web/bios.css index d6a1285..db00bea 100644 --- a/packages/backend/src/server/web/bios.css +++ b/packages/backend/src/server/web/bios.css @@ -1,6 +1,6 @@ main > .tabs { padding: 16px; - border-bottom: 4px solid #908caa; + border-block-end: 4px solid #908caa; } #lsEditor > .adder { margin: 16px; @@ -9,21 +9,21 @@ main > .tabs { } #lsEditor > .adder > textarea { display: block; - width: 100%; - min-height: 5em; + inline-size: 100%; + min-block-size: 5em; box-sizing: border-box; } #lsEditor > .record { padding: 16px; - border-bottom: 1px solid #908caa; + border-block-end: 1px solid #908caa; } #lsEditor > .record > header { font-weight: 700; } #lsEditor > .record > textarea { display: block; - width: 100%; - min-height: 5em; + inline-size: 100%; + min-block-size: 5em; box-sizing: border-box; } @@ -36,7 +36,7 @@ main { } #tl > div { padding: 16px; - border-bottom: 1px solid #908caa; + border-block-end: 1px solid #908caa; } #tl > div > header { font-weight: 700; @@ -59,11 +59,14 @@ html { } button { border-radius: 999px; - padding: 0px 12px 0px 12px; + padding-block-start: 0px; + padding-inline-end: 12px; + padding-block-end: 0px; + padding-inline-start: 12px; border: none; cursor: pointer; - margin-bottom: 12px; - background: linear-gradient(90deg, rgb(156, 207, 216), rgb(49, 116, 143)); + margin-block-end: 12px; + background: linear-gradient(var(--gradient-to-inline-end), rgb(156, 207, 216), rgb(49, 116, 143)); line-height: 50px; color: #191724; font-weight: bold; @@ -72,25 +75,29 @@ button { } button { border-radius: 999px; - padding: 0px 12px 0px 12px; + padding-block-start: 0px; + padding-inline-end: 12px; + padding-block-end: 0px; + padding-inline-start: 12px; border: none; cursor: pointer; - margin-bottom: 12px; + margin-block-end: 12px; } button { background: #444; line-height: 40px; color: rgb(156, 207, 216); font-size: 16px; - padding: 0 20px; - margin-right: 5px; - margin-left: 5px; + padding-block: 0; + padding-inline: 20px; + margin-inline-end: 5px; + margin-inline-start: 5px; } button:hover { background: #555; } #ls { - background: linear-gradient(90deg, rgb(156, 207, 216), rgb(49, 116, 143)); + background: linear-gradient(var(--gradient-to-inline-end), rgb(156, 207, 216), rgb(49, 116, 143)); line-height: 30px; color: #191724; font-weight: bold; @@ -120,10 +127,10 @@ textarea { border: solid #aaa; border-radius: 10px; color: #e0def4; - margin-top: 1rem; - margin-bottom: 1rem; - width: 20rem; - height: 7.5rem; + margin-block-start: 1rem; + margin-block-end: 1rem; + inline-size: 20rem; + block-size: 7.5rem; padding: 0.5rem; } @@ -135,10 +142,10 @@ input { border: solid #aaa; border-radius: 10px; color: #e0def4; - margin-top: 1rem; - margin-bottom: 1rem; - width: 10rem; - height: 1rem; + margin-block-start: 1rem; + margin-block-end: 1rem; + inline-size: 10rem; + block-size: 1rem; padding: 0.5rem; } diff --git a/packages/backend/src/server/web/cli.css b/packages/backend/src/server/web/cli.css index 740a2aa..311a962 100644 --- a/packages/backend/src/server/web/cli.css +++ b/packages/backend/src/server/web/cli.css @@ -10,7 +10,7 @@ main { border-radius: 10px; margin: 10px; padding: 10px; - width: fit-content; + inline-size: fit-content; } #tl > div > header { font-weight: 700; @@ -19,7 +19,7 @@ main { img { border-radius: 10px; - margin-right: 10px; + margin-inline-end: 10px; } #form { @@ -42,11 +42,11 @@ html { button { border-radius:999px; padding:0 40px; - margin-top: 1rem; + margin-block-start: 1rem; border:none; cursor:pointer; - margin-bottom:12px; - background:linear-gradient(90deg,#9ccfd8,#31748f); + margin-block-end:12px; + background:linear-gradient(var(--gradient-to-inline-end),#9ccfd8,#31748f); line-height:50px; color:#191724; font-weight:700; @@ -75,9 +75,9 @@ code { border: solid #aaa; border-radius: 10px; color: #e0def4; - margin-top: 3rem; - width: 20rem; - height: 5rem; + margin-block-start: 3rem; + inline-size: 20rem; + block-size: 5rem; padding: 0.5rem; } @@ -85,8 +85,8 @@ code { border: solid #eee; } -@media screen and (max-width: 500px) { +@media screen and (max-inline-size: 500px) { #text { - width: 80% + inline-size: 80% } } diff --git a/packages/backend/src/server/web/style.css b/packages/backend/src/server/web/style.css index 2a93253..6477874 100644 --- a/packages/backend/src/server/web/style.css +++ b/packages/backend/src/server/web/style.css @@ -23,10 +23,10 @@ html { #splash { position: fixed; z-index: 10000; - top: 0; - left: 0; - width: 100vw; - height: 100vh; + inset-block-start: 0; + inset-inline-start: 0; + inline-size: 100vi; + block-size: 100vb; cursor: wait; background-color: var(--bg); opacity: 1; @@ -35,36 +35,30 @@ html { #splashIcon { position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; + inset: 0; margin: auto; - width: 64px; - height: 64px; + inline-size: 64px; + block-size: 64px; pointer-events: none; } #splashSpinner { position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; + inset: 0; margin: auto; display: inline-block; - width: 28px; - height: 28px; + inline-size: 28px; + block-size: 28px; transform: translateY(110px); display: none; color: var(--accent); } #splashSpinner > .spinner { position: absolute; - top: 0; - left: 0; - width: 28px; - height: 28px; + inset-block-start: 0; + inset-inline-start: 0; + inline-size: 28px; + block-size: 28px; fill-rule: evenodd; clip-rule: evenodd; stroke-linecap: round; @@ -123,15 +117,12 @@ html { #splashText { position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; + inset: 0; margin: auto; display: inline-block; - width: 70%; - height: 0; + inline-size: 70%; + block-size: 0; text-align: center; - padding-top: 100px; + padding-block-start: 100px; font-family: "Atkinson Hyperlegible", sans-serif; } diff --git a/packages/backend/src/services/blocking/create.ts b/packages/backend/src/services/blocking/create.ts index 1d7b80f..8471791 100644 --- a/packages/backend/src/services/blocking/create.ts +++ b/packages/backend/src/services/blocking/create.ts @@ -1,9 +1,7 @@ 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 { renderBlock } from "@/remote/activitypub/renderer/block.js"; import { deliver } from "@/queue/index.js"; -import renderReject from "@/remote/activitypub/renderer/reject.js"; import type { Blocking } from "@/models/entities/blocking.js"; import type { User } from "@/models/entities/user.js"; import { @@ -20,6 +18,8 @@ import { publishToMainStream, publishToUserStream, UserEvent, + renderFollow, + renderReject, } from "backend-rs"; import { getActiveWebhooks } from "@/misc/webhook-cache.js"; import { webhookDeliver } from "@/queue/index.js"; @@ -103,8 +103,8 @@ async function cancelRequest(follower: User, followee: User) { if (Users.isRemoteUser(follower) && Users.isLocalUser(followee)) { const content = renderActivity( renderReject( + followee.id, renderFollow(follower, followee, request.requestId!), - followee, ), ); deliver(followee.id, content, follower.inbox); diff --git a/packages/backend/src/services/fetch-instance-metadata.ts b/packages/backend/src/services/fetch-instance-metadata.ts index 494027a..d0bcdd3 100644 --- a/packages/backend/src/services/fetch-instance-metadata.ts +++ b/packages/backend/src/services/fetch-instance-metadata.ts @@ -156,7 +156,7 @@ async function fetchFaviconUrl( // TODO //timeout: 10000, agent: getAgentByUrl, - size: 1024 * 1024 + size: 1024 * 1024, }); if (favicon.ok) { diff --git a/packages/backend/src/services/following/create.ts b/packages/backend/src/services/following/create.ts index aea5bd4..403fa72 100644 --- a/packages/backend/src/services/following/create.ts +++ b/packages/backend/src/services/following/create.ts @@ -1,7 +1,4 @@ import { renderActivity } from "@/remote/activitypub/renderer/index.js"; -import renderFollow from "@/remote/activitypub/renderer/follow.js"; -import renderAccept from "@/remote/activitypub/renderer/accept.js"; -import renderReject from "@/remote/activitypub/renderer/reject.js"; import { deliver } from "@/queue/index.js"; import createFollowRequest from "./requests/create.js"; import { registerOrFetchInstanceDoc } from "@/services/register-or-fetch-instance-doc.js"; @@ -23,6 +20,9 @@ import { publishToMainStream, publishToUserStream, UserEvent, + renderAccept, + renderFollow, + renderReject, } from "backend-rs"; import { createNotification } from "@/services/create-notification.js"; import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js"; @@ -196,7 +196,7 @@ export default async function ( if (Users.isRemoteUser(follower) && Users.isLocalUser(followee) && blocked) { // リモートフォローを受けてブロックしていた場合は、エラーにするのではなくRejectを送り返しておしまい。 const content = renderActivity( - renderReject(renderFollow(follower, followee, requestId), followee), + renderReject(followee.id, renderFollow(follower, followee, requestId)), ); deliver(followee.id, content, follower.inbox); return; @@ -275,7 +275,7 @@ export default async function ( if (Users.isRemoteUser(follower) && Users.isLocalUser(followee)) { const content = renderActivity( - renderAccept(renderFollow(follower, followee, requestId), followee), + renderAccept(followee.id, renderFollow(follower, followee, requestId)), ); deliver(followee.id, content, follower.inbox); } diff --git a/packages/backend/src/services/following/delete.ts b/packages/backend/src/services/following/delete.ts index 9d0135b..6d5775f 100644 --- a/packages/backend/src/services/following/delete.ts +++ b/packages/backend/src/services/following/delete.ts @@ -3,11 +3,11 @@ import { publishToMainStream, publishToUserStream, UserEvent, + renderFollow, + renderReject, } from "backend-rs"; 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, webhookDeliver } from "@/queue/index.js"; import Logger from "../logger.js"; import { registerOrFetchInstanceDoc } from "@/services/register-or-fetch-instance-doc.js"; @@ -22,6 +22,7 @@ export default async function ( id: User["id"]; host: User["host"]; uri: User["host"]; + username: User["username"]; inbox: User["inbox"]; sharedInbox: User["sharedInbox"]; }, @@ -29,6 +30,7 @@ export default async function ( id: User["id"]; host: User["host"]; uri: User["host"]; + username: User["username"]; inbox: User["inbox"]; sharedInbox: User["sharedInbox"]; }, @@ -79,7 +81,7 @@ export default async function ( if (Users.isLocalUser(followee) && Users.isRemoteUser(follower)) { // local user has null host const content = renderActivity( - renderReject(renderFollow(follower, followee), followee), + renderReject(followee.id, renderFollow(follower, followee)), ); deliver(followee.id, content, follower.inbox); } diff --git a/packages/backend/src/services/following/reject.ts b/packages/backend/src/services/following/reject.ts index 1523573..d0c2562 100644 --- a/packages/backend/src/services/following/reject.ts +++ b/packages/backend/src/services/following/reject.ts @@ -1,12 +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, webhookDeliver } from "@/queue/index.js"; import { Event, publishToMainStream, publishToUserStream, UserEvent, + renderFollow, + renderReject, } from "backend-rs"; import type { ILocalUser, IRemoteUser } from "@/models/entities/user.js"; import { Users, FollowRequests, Followings } from "@/models/index.js"; @@ -19,6 +19,7 @@ type Local = id: ILocalUser["id"]; host: ILocalUser["host"]; uri: ILocalUser["uri"]; + username: ILocalUser["username"]; }; type Remote = | IRemoteUser @@ -26,6 +27,7 @@ type Remote = id: IRemoteUser["id"]; host: IRemoteUser["host"]; uri: IRemoteUser["uri"]; + username: IRemoteUser["username"]; inbox: IRemoteUser["inbox"]; }; type Both = Local | Remote; @@ -109,8 +111,8 @@ async function deliverReject(followee: Local, follower: Remote) { const content = renderActivity( renderReject( + followee.id, renderFollow(follower, followee, request?.requestId || undefined), - followee, ), ); deliver(followee.id, content, follower.inbox); diff --git a/packages/backend/src/services/following/requests/accept.ts b/packages/backend/src/services/following/requests/accept.ts index bcd7d2c..ba1d401 100644 --- a/packages/backend/src/services/following/requests/accept.ts +++ b/packages/backend/src/services/following/requests/accept.ts @@ -1,8 +1,11 @@ import { renderActivity } from "@/remote/activitypub/renderer/index.js"; -import renderFollow from "@/remote/activitypub/renderer/follow.js"; -import renderAccept from "@/remote/activitypub/renderer/accept.js"; import { deliver } from "@/queue/index.js"; -import { Event, publishToMainStream } from "backend-rs"; +import { + Event, + publishToMainStream, + renderAccept, + renderFollow, +} from "backend-rs"; import { insertFollowingDoc } from "../create.js"; import type { User, CacheableUser } from "@/models/entities/user.js"; import { FollowRequests, Users } from "@/models/index.js"; @@ -35,8 +38,8 @@ export default async function ( if (Users.isRemoteUser(follower) && Users.isLocalUser(followee)) { const content = renderActivity( renderAccept( + followee.id, renderFollow(follower, followee, request.requestId!), - followee, ), ); deliver(followee.id, content, follower.inbox); diff --git a/packages/backend/src/services/following/requests/cancel.ts b/packages/backend/src/services/following/requests/cancel.ts index eb1b2dc..435c86f 100644 --- a/packages/backend/src/services/following/requests/cancel.ts +++ b/packages/backend/src/services/following/requests/cancel.ts @@ -1,8 +1,7 @@ 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 { deliver } from "@/queue/index.js"; -import { Event, publishToMainStream } from "backend-rs"; +import { Event, publishToMainStream, renderFollow } from "backend-rs"; import { IdentifiableError } from "@/misc/identifiable-error.js"; import type { User } from "@/models/entities/user.js"; import { Users, FollowRequests } from "@/models/index.js"; diff --git a/packages/backend/src/services/following/requests/create.ts b/packages/backend/src/services/following/requests/create.ts index 40f383c..cc6b426 100644 --- a/packages/backend/src/services/following/requests/create.ts +++ b/packages/backend/src/services/following/requests/create.ts @@ -1,9 +1,8 @@ import { renderActivity } from "@/remote/activitypub/renderer/index.js"; -import renderFollow from "@/remote/activitypub/renderer/follow.js"; import { deliver } from "@/queue/index.js"; import type { User } from "@/models/entities/user.js"; import { Blockings, FollowRequests, Users } from "@/models/index.js"; -import { Event, genIdAt, publishToMainStream } from "backend-rs"; +import { Event, genIdAt, publishToMainStream, renderFollow } from "backend-rs"; import { createNotification } from "@/services/create-notification.js"; import { config } from "@/config.js"; diff --git a/packages/backend/src/services/i/pin.ts b/packages/backend/src/services/i/pin.ts index 42fc09b..574e036 100644 --- a/packages/backend/src/services/i/pin.ts +++ b/packages/backend/src/services/i/pin.ts @@ -1,13 +1,10 @@ -import { config } from "@/config.js"; -import renderAdd from "@/remote/activitypub/renderer/add.js"; -import renderRemove from "@/remote/activitypub/renderer/remove.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import { IdentifiableError } from "@/misc/identifiable-error.js"; import type { User } from "@/models/entities/user.js"; import type { Note } from "@/models/entities/note.js"; import { Notes, UserNotePinings, Users } from "@/models/index.js"; import type { UserNotePining } from "@/models/entities/user-note-pining.js"; -import { genIdAt } from "backend-rs"; +import { genIdAt, renderAdd, renderRemove } from "backend-rs"; import { deliverToFollowers } from "@/remote/activitypub/deliver-manager.js"; import { deliverToRelays } from "@/services/relay.js"; @@ -115,12 +112,8 @@ export async function deliverPinnedChange( if (!Users.isLocalUser(user)) return; - const target = `${config.url}/users/${user.id}/collections/featured`; - const item = `${config.url}/notes/${noteId}`; const content = renderActivity( - isAddition - ? renderAdd(user, target, item) - : renderRemove(user, target, item), + isAddition ? renderAdd(user.id, noteId) : renderRemove(user.id, noteId), ); deliverToFollowers(user, content); diff --git a/packages/backend/src/services/logger.ts b/packages/backend/src/services/logger.ts index f7843ea..24ae145 100644 --- a/packages/backend/src/services/logger.ts +++ b/packages/backend/src/services/logger.ts @@ -132,7 +132,7 @@ export default class Logger { ? message : null; - let log = `${l} ${worker}\t[${domains.join(" ")}]\t${m}`; + const log = `${l} ${worker}\t[${domains.join(" ")}]\t${m}`; console.log(important ? chalk.bold(log) : log); diff --git a/packages/backend/src/services/messages/delete.ts b/packages/backend/src/services/messages/delete.ts index f0b6515..aa9eb0e 100644 --- a/packages/backend/src/services/messages/delete.ts +++ b/packages/backend/src/services/messages/delete.ts @@ -1,10 +1,12 @@ -import { config } from "@/config.js"; import { MessagingMessages, Users } from "@/models/index.js"; import type { MessagingMessage } from "@/models/entities/messaging-message.js"; -import { publishToChatStream, publishToGroupChatStream } from "backend-rs"; +import { + publishToChatStream, + publishToGroupChatStream, + renderTombstone, +} from "backend-rs"; import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import renderDelete from "@/remote/activitypub/renderer/delete.js"; -import renderTombstone from "@/remote/activitypub/renderer/tombstone.js"; import { deliver } from "@/queue/index.js"; export async function deleteMessage(message: MessagingMessage) { @@ -36,10 +38,7 @@ async function postDeleteMessage(message: MessagingMessage) { if (Users.isLocalUser(user) && Users.isRemoteUser(recipient)) { const activity = renderActivity( - renderDelete( - renderTombstone(`${config.url}/notes/${message.id}`), - user, - ), + renderDelete(renderTombstone(message.id), user), ); deliver(user.id, activity, recipient.inbox); } diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index 4b21c67..63c6239 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -62,7 +62,7 @@ import { db } from "@/db/postgre.js"; import { getActiveWebhooks } from "@/misc/webhook-cache.js"; import { redisClient } from "@/db/redis.js"; import { Mutex } from "redis-semaphore"; -import { langmap } from "firefish-js"; +import { bcp47Pattern } from "firefish-js"; import Logger from "@/services/logger.js"; import { inspect } from "node:util"; import { toRustObject } from "@/prelude/undefined-to-null.js"; @@ -273,8 +273,7 @@ export default async ( data.text = data.text?.trim() ?? null; if (data.lang != null) { - if (!Object.keys(langmap).includes(data.lang.toLowerCase())) - throw new Error("invalid param"); + if (!bcp47Pattern.test(data.lang)) rej("Invalid language code"); data.lang = data.lang.toLowerCase(); } else { data.lang = null; @@ -317,7 +316,7 @@ export default async ( } if (!isDraft && data.visibility === "specified") { - if (data.visibleUsers == null) throw new Error("invalid param"); + if (data.visibleUsers == null) rej("invalid param"); for (const u of data.visibleUsers) { if (!mentionedUsers.some((x) => x.id === u.id)) { @@ -444,7 +443,7 @@ export default async ( // 未読通知を作成 if (data.visibility === "specified") { - if (data.visibleUsers == null) throw new Error("invalid param"); + if (data.visibleUsers == null) rej("invalid param"); for (const u of data.visibleUsers) { // ローカルユーザーのみ diff --git a/packages/backend/src/services/note/delete.ts b/packages/backend/src/services/note/delete.ts index 0ae03a6..f599a55 100644 --- a/packages/backend/src/services/note/delete.ts +++ b/packages/backend/src/services/note/delete.ts @@ -3,7 +3,6 @@ import renderDelete from "@/remote/activitypub/renderer/delete.js"; import renderAnnounce from "@/remote/activitypub/renderer/announce.js"; 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.js"; import type { User, ILocalUser, IRemoteUser } from "@/models/entities/user.js"; import type { Note, IMentionedRemoteUsers } from "@/models/entities/note.js"; @@ -16,7 +15,7 @@ import { countSameRenotes } from "@/misc/count-same-renotes.js"; import { registerOrFetchInstanceDoc } from "@/services/register-or-fetch-instance-doc.js"; import { deliverToRelays } from "@/services/relay.js"; import type { IActivity } from "@/remote/activitypub/type.js"; -import { NoteEvent, publishToNoteStream } from "backend-rs"; +import { NoteEvent, publishToNoteStream, renderTombstone } from "backend-rs"; async function recalculateNotesCountOfLocalUser(user: { id: User["id"]; @@ -102,7 +101,7 @@ export default async function ( ), user.id, ) - : renderDelete(renderTombstone(`${config.url}/notes/${note.id}`), user), + : renderDelete(renderTombstone(note.id), user), ); deliverToConcerned(user, note, content); @@ -127,10 +126,7 @@ export default async function ( affectedLocalUsers[cascadingNote.user.id] ??= cascadingNote.user; if (cascadingNote.localOnly) continue; // filter out local-only notes const content = renderActivity( - renderDelete( - renderTombstone(`${config.url}/notes/${cascadingNote.id}`), - cascadingNote.user, - ), + renderDelete(renderTombstone(cascadingNote.id), cascadingNote.user), ); deliverToConcerned(cascadingNote.user, cascadingNote, content); } diff --git a/packages/backend/src/services/note/reaction/create.ts b/packages/backend/src/services/note/reaction/create.ts index f868a4c..bb3bf12 100644 --- a/packages/backend/src/services/note/reaction/create.ts +++ b/packages/backend/src/services/note/reaction/create.ts @@ -1,4 +1,3 @@ -import { renderLike } from "@/remote/activitypub/renderer/like.js"; import DeliverManager from "@/remote/activitypub/deliver-manager.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import type { User, IRemoteUser } from "@/models/entities/user.js"; @@ -17,6 +16,7 @@ import { genIdAt, NoteEvent, publishToNoteStream, + renderLike, toDbReaction, } from "backend-rs"; import { createNotification } from "@/services/create-notification.js"; @@ -152,7 +152,7 @@ export default async ( !note.localOnly && note.visibility !== "hidden" ) { - const content = renderActivity(await renderLike(record, note)); + const content = renderActivity(await renderLike(record)); const dm = new DeliverManager(user, content); if (note.userHost != null) { const reactee = await Users.findOneBy({ id: note.userId }); diff --git a/packages/backend/src/services/note/reaction/delete.ts b/packages/backend/src/services/note/reaction/delete.ts index 00b7f18..5da7691 100644 --- a/packages/backend/src/services/note/reaction/delete.ts +++ b/packages/backend/src/services/note/reaction/delete.ts @@ -1,4 +1,3 @@ -import { renderLike } from "@/remote/activitypub/renderer/like.js"; import { renderUndo } from "@/remote/activitypub/renderer/undo.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import DeliverManager from "@/remote/activitypub/deliver-manager.js"; @@ -6,7 +5,12 @@ import { IdentifiableError } from "@/misc/identifiable-error.js"; import type { User, IRemoteUser } from "@/models/entities/user.js"; import type { Note } from "@/models/entities/note.js"; import { NoteReactions, Users, Notes } from "@/models/index.js"; -import { decodeReaction, NoteEvent, publishToNoteStream } from "backend-rs"; +import { + decodeReaction, + NoteEvent, + publishToNoteStream, + renderLike, +} from "backend-rs"; export default async ( user: { id: User["id"]; host: User["host"] }, @@ -55,7 +59,7 @@ export default async ( //#region 配信 if (Users.isLocalUser(user) && !note.localOnly) { const content = renderActivity( - renderUndo(await renderLike(reaction, note), user.id), + renderUndo(await renderLike(reaction), user.id), ); const dm = new DeliverManager(user, content); if (note.userHost != null) { diff --git a/packages/client/package.json b/packages/client/package.json index eb93c9d..d2d1567 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -8,10 +8,9 @@ "build": "pnpm vite build", "build:debug": "pnpm run build", "lint": "pnpm run lint:ts ; pnpm run lint:vue", - "lint:ts": "pnpm biome check **/*.ts --write", - "lint:vue": "pnpm biome check **/*.vue --write", - "types:check": "pnpm vue-tsc --noEmit", - "format": "pnpm biome format * --write" + "lint:ts": "pnpm biome check --write src/*.ts src/**/*.ts src/**/**/*.ts", + "lint:vue": "pnpm vue-tsc --noEmit", + "format": "pnpm biome format --write src" }, "devDependencies": { "@phosphor-icons/web": "2.1.1", @@ -33,8 +32,8 @@ "@types/throttle-debounce": "5.0.2", "@types/tinycolor2": "1.4.6", "@types/uuid": "10.0.0", - "@vitejs/plugin-vue": "5.1.1", - "@vue/runtime-core": "3.4.34", + "@vitejs/plugin-vue": "5.1.2", + "@vue/runtime-core": "3.4.38", "autobind-decorator": "2.4.0", "autosize": "6.0.1", "broadcast-channel": "7.0.0", @@ -50,7 +49,7 @@ "date-fns": "3.6.0", "emojilib": "3.0.12", "eventemitter3": "5.0.1", - "fast-blurhash": "1.1.2", + "fast-blurhash": "1.1.4", "firefish-js": "workspace:*", "focus-trap": "7.5.4", "focus-trap-vue": "4.0.3", @@ -68,14 +67,14 @@ "photoswipe": "5.4.4", "prismjs": "1.29.0", "punycode": "2.3.1", - "qrcode": "1.5.3", + "qrcode": "1.5.4", "qrcode-vue3": "1.6.8", - "rollup": "4.19.1", + "rollup": "4.20.0", "s-age": "1.1.2", "sass": "1.77.8", "seedrandom": "3.0.5", "stringz": "2.1.0", - "swiper": "11.1.8", + "swiper": "11.1.9", "textarea-caret": "3.1.0", "throttle-debounce": "5.0.2", "tinycolor2": "1.6.0", @@ -83,10 +82,10 @@ "typescript": "5.5.4", "unicode-emoji-json": "0.6.0", "uuid": "10.0.0", - "vite": "5.3.5", + "vite": "5.4.1", "vite-plugin-compression": "0.5.1", - "vue": "3.4.34", - "vue-draggable-plus": "0.5.2", + "vue": "3.4.38", + "vue-draggable-plus": "0.5.3", "vue-plyr": "7.0.0", "vue-prism-editor": "2.0.0-alpha.2", "vue-tsc": "2.0.29" diff --git a/packages/client/src/components/MkAbuseReport.vue b/packages/client/src/components/MkAbuseReport.vue index b190652..a361f2f 100644 --- a/packages/client/src/components/MkAbuseReport.vue +++ b/packages/client/src/components/MkAbuseReport.vue @@ -99,11 +99,11 @@ function resolve() { display: flex; > .target { - width: 35%; + inline-size: 35%; box-sizing: border-box; - text-align: left; + text-align: start; padding: 24px; - border-right: solid 1px var(--divider); + border-inline-end: solid 1px var(--divider); > .info { display: flex; @@ -125,13 +125,14 @@ function resolve() { background-size: 16px 16px; > .avatar { - width: 42px; - height: 42px; + inline-size: 42px; + block-size: 42px; } > .names { - margin-left: 0.3em; - padding: 0 8px; + margin-inline-start: 0.3em; + padding-block: 0; + padding-inline: 8px; flex: 1; > .name { diff --git a/packages/client/src/components/MkAbuseReportWindow.vue b/packages/client/src/components/MkAbuseReportWindow.vue index bde54b7..3f3b02a 100644 --- a/packages/client/src/components/MkAbuseReportWindow.vue +++ b/packages/client/src/components/MkAbuseReportWindow.vue @@ -9,7 +9,7 @@ - + @@ -156,7 +156,7 @@ function getStatus(instance) { .taeiyria { > .query { background: var(--bg); - margin-bottom: 16px; + margin-block-end: 16px; } } diff --git a/packages/client/src/pages/about.vue b/packages/client/src/pages/about.vue index 3102f31..8ce8222 100644 --- a/packages/client/src/pages/about.vue +++ b/packages/client/src/pages/about.vue @@ -301,6 +301,11 @@ let swiperRef: SwiperType | null = null; function setSwiperRef(swiper: SwiperType) { swiperRef = swiper; syncSlide(tabs.indexOf(tab.value)); + const styles = getComputedStyle(swiper.el); + swiper.changeLanguageDirection(styles.direction as "rtl" | "ltr"); + if (styles["writing-mode"].startsWith("vertical")) { + swiper.changeDirection("vertical"); + } } function onSlideChange() { @@ -325,8 +330,11 @@ function syncSlide(index: number) { > .icon { display: block; - margin: 16px auto 0 auto; - height: 64px; + margin-block-start: 16px; + margin-inline-end: auto; + margin-block-end: 0; + margin-inline-start: auto; + block-size: 64px; } > .spin { diff --git a/packages/client/src/pages/admin-file.vue b/packages/client/src/pages/admin-file.vue index b2778dd..c6dd208 100644 --- a/packages/client/src/pages/admin-file.vue +++ b/packages/client/src/pages/admin-file.vue @@ -264,6 +264,11 @@ let swiperRef = null; function setSwiperRef(swiper) { swiperRef = swiper; syncSlide(tabs.indexOf(tab.value)); + const styles = getComputedStyle(swiper.el); + swiper.changeLanguageDirection(styles.direction as "rtl" | "ltr"); + if (styles["writing-mode"].startsWith("vertical")) { + swiper.changeDirection("vertical"); + } } function onSlideChange() { @@ -281,8 +286,8 @@ function syncSlide(index) { display: block; > .thumbnail { - height: 300px; - max-width: 100%; + block-size: 300px; + max-inline-size: 100%; } } diff --git a/packages/client/src/pages/admin/_header_.vue b/packages/client/src/pages/admin/_header_.vue index 5992438..272adf6 100644 --- a/packages/client/src/pages/admin/_header_.vue +++ b/packages/client/src/pages/admin/_header_.vue @@ -40,7 +40,7 @@ class="fullButton" primary @click.stop="action.handler" - >{{ action.text }}