♻️ Refer new firefish (.gitlab-ci.yml, .gitlab/issue_templates/default.md, .gitlab/issue_templates/discussion.md, .gitlab/issue_templates/feature.md, .gitlab/issue_templates/refactor.md, .gitlab/merge_request_templates/default.md, .gitlab/merge_request_templates/release.md, Cargo.lock, Cargo.toml, Dockerfile, dev/container/docker-compose.yml, dev/docs/local-installation.md, docs/changelog.md, docs/downgrade.sql, docs/install.md, docs/notice-for-admins.md, locales/ca-ES.yml, locales/en-US.yml, locales/eo.yml, locales/ja-JP.yml, locales/ko-KR.yml, locales/zh-CN.yml, locales/zh-TW.yml, package.json, packages/backend-rs/Cargo.toml, packages/backend-rs/index.d.ts, packages/backend-rs/index.js, packages/backend-rs/package.json, packages/backend-rs/src/cache/bare.rs, packages/backend-rs/src/cache/mod.rs, packages/backend-rs/src/cache/redis.rs, packages/backend-rs/src/config/meta.rs, packages/backend-rs/src/database/mod.rs, packages/backend-rs/src/federation/activitypub/object/accept.rs, packages/backend-rs/src/federation/activitypub/object/add.rs, packages/backend-rs/src/federation/activitypub/object/emoji.rs, packages/backend-rs/src/federation/activitypub/object/flag.rs, packages/backend-rs/src/federation/activitypub/object/follow.rs, packages/backend-rs/src/federation/activitypub/object/hashtag.rs, packages/backend-rs/src/federation/activitypub/object/like.rs, packages/backend-rs/src/federation/activitypub/object/mention.rs, packages/backend-rs/src/federation/activitypub/object/mod.rs, packages/backend-rs/src/federation/activitypub/object/read.rs, packages/backend-rs/src/federation/activitypub/object/reject.rs, packages/backend-rs/src/federation/activitypub/object/remove.rs, packages/backend-rs/src/federation/activitypub/object/tombstone.rs, packages/backend-rs/src/federation/internal_actor/instance.rs, packages/backend-rs/src/federation/internal_actor/relay.rs, packages/backend-rs/src/federation/nodeinfo/fetch.rs, packages/backend-rs/src/federation/nodeinfo/generate.rs, packages/backend-rs/src/init/system_info.rs, packages/backend-rs/src/lib.rs, packages/backend-rs/src/misc/emoji/mod.rs, packages/backend-rs/src/misc/emoji/reaction.rs, packages/backend-rs/src/misc/emoji/unicode.rs, packages/backend-rs/src/misc/get_image_size.rs, packages/backend-rs/src/misc/latest_version.rs, packages/backend-rs/src/misc/mod.rs, packages/backend-rs/src/misc/note/mod.rs, packages/backend-rs/src/misc/random_icon.rs, packages/backend-rs/src/misc/should_nyaify.rs, packages/backend-rs/src/misc/system_info.rs, packages/backend-rs/src/misc/translate.rs, packages/backend-rs/src/misc/user/mod.rs, packages/backend-rs/src/model/entity/abuse_user_report.rs, packages/backend-rs/src/model/entity/access_token.rs, packages/backend-rs/src/model/entity/ad.rs, packages/backend-rs/src/model/entity/announcement.rs, packages/backend-rs/src/model/entity/announcement_read.rs, packages/backend-rs/src/model/entity/antenna.rs, packages/backend-rs/src/model/entity/app.rs, packages/backend-rs/src/model/entity/attestation_challenge.rs, packages/backend-rs/src/model/entity/auth_session.rs, packages/backend-rs/src/model/entity/blocking.rs, packages/backend-rs/src/model/entity/channel.rs, packages/backend-rs/src/model/entity/channel_following.rs, packages/backend-rs/src/model/entity/channel_note_pining.rs, packages/backend-rs/src/model/entity/clip.rs, packages/backend-rs/src/model/entity/clip_note.rs, packages/backend-rs/src/model/entity/drive_file.rs, packages/backend-rs/src/model/entity/drive_folder.rs, packages/backend-rs/src/model/entity/emoji.rs, packages/backend-rs/src/model/entity/follow_request.rs, packages/backend-rs/src/model/entity/following.rs, packages/backend-rs/src/model/entity/gallery_like.rs, packages/backend-rs/src/model/entity/gallery_post.rs, packages/backend-rs/src/model/entity/hashtag.rs, packages/backend-rs/src/model/entity/instance.rs, packages/backend-rs/src/model/entity/messaging_message.rs, packages/backend-rs/src/model/entity/meta.rs, packages/backend-rs/src/model/entity/migrations.rs, packages/backend-rs/src/model/entity/mod.rs, packages/backend-rs/src/model/entity/moderation_log.rs, packages/backend-rs/src/model/entity/muted_note.rs, packages/backend-rs/src/model/entity/muting.rs, packages/backend-rs/src/model/entity/note.rs, packages/backend-rs/src/model/entity/note_edit.rs, packages/backend-rs/src/model/entity/note_favorite.rs, packages/backend-rs/src/model/entity/note_file.rs, packages/backend-rs/src/model/entity/note_reaction.rs, packages/backend-rs/src/model/entity/note_thread_muting.rs, packages/backend-rs/src/model/entity/note_unread.rs, packages/backend-rs/src/model/entity/note_watching.rs, packages/backend-rs/src/model/entity/notification.rs, packages/backend-rs/src/model/entity/page.rs, packages/backend-rs/src/model/entity/page_like.rs, packages/backend-rs/src/model/entity/password_reset_request.rs, packages/backend-rs/src/model/entity/poll.rs, packages/backend-rs/src/model/entity/poll_vote.rs, packages/backend-rs/src/model/entity/prelude.rs, packages/backend-rs/src/model/entity/promo_note.rs, packages/backend-rs/src/model/entity/promo_read.rs, packages/backend-rs/src/model/entity/registration_ticket.rs, packages/backend-rs/src/model/entity/registry_item.rs, packages/backend-rs/src/model/entity/relay.rs, packages/backend-rs/src/model/entity/renote_muting.rs, packages/backend-rs/src/model/entity/reply_muting.rs, packages/backend-rs/src/model/entity/sea_orm_active_enums.rs, packages/backend-rs/src/model/entity/signin.rs, packages/backend-rs/src/model/entity/sw_subscription.rs, packages/backend-rs/src/model/entity/used_username.rs, packages/backend-rs/src/model/entity/user.rs, packages/backend-rs/src/model/entity/user_group.rs, packages/backend-rs/src/model/entity/user_group_invitation.rs, packages/backend-rs/src/model/entity/user_group_invite.rs, packages/backend-rs/src/model/entity/user_group_joining.rs, packages/backend-rs/src/model/entity/user_ip.rs, packages/backend-rs/src/model/entity/user_keypair.rs, packages/backend-rs/src/model/entity/user_list.rs, packages/backend-rs/src/model/entity/user_list_joining.rs, packages/backend-rs/src/model/entity/user_note_pining.rs, packages/backend-rs/src/model/entity/user_pending.rs, packages/backend-rs/src/model/entity/user_profile.rs, packages/backend-rs/src/model/entity/user_publickey.rs, packages/backend-rs/src/model/entity/user_security_key.rs, packages/backend-rs/src/model/entity/webhook.rs, packages/backend-rs/src/service/antenna/check_hit.rs, packages/backend-rs/src/service/antenna/mod.rs, packages/backend-rs/src/service/antenna/process_new_note.rs, packages/backend-rs/src/service/antenna/update.rs, packages/backend-rs/src/service/push_notification.rs, packages/backend-rs/src/service/stream.rs, packages/backend-rs/src/util/id.rs, packages/backend/package.json, packages/backend/src/boot/master.ts, packages/backend/src/migration/1644010796173-convert-hard-mutes.ts, packages/backend/src/migration/1652859567549-uniform-themecolor.ts, packages/backend/src/migration/1722346019160-set-emoji-public-url.ts, packages/backend/src/models/entities/emoji.ts, packages/backend/src/models/entities/note.ts, packages/backend/src/models/entities/user-profile.ts, packages/backend/src/models/entities/user.ts, packages/backend/src/models/repositories/renote-muting.ts, packages/backend/src/models/repositories/reply-muting.ts, packages/backend/src/models/repositories/user.ts, packages/backend/src/queue/processors/db/import-firefish-post.ts, packages/backend/src/queue/processors/db/import-masto-post.ts, packages/backend/src/remote/activitypub/kernel/like.ts, packages/backend/src/remote/activitypub/misc/contexts.ts, packages/backend/src/remote/activitypub/models/person.ts, packages/backend/src/remote/activitypub/renderer/note.ts, packages/backend/src/remote/activitypub/renderer/person.ts, packages/backend/src/remote/activitypub/request.ts, packages/backend/src/remote/activitypub/resolver.ts, packages/backend/src/remote/activitypub/type.ts, packages/backend/src/server/activitypub.ts, packages/backend/src/server/api/common/inject-featured.ts, packages/backend/src/server/api/common/read-messaging-message.ts, packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts, packages/backend/src/server/api/endpoints/notes/featured.ts, packages/backend/src/server/api/mastodon/helpers/misc.ts, packages/backend/src/server/file/byte-range-readable.ts, packages/backend/src/server/index.ts, packages/backend/src/server/web/bios.css, packages/backend/src/server/web/cli.css, packages/backend/src/server/web/style.css, packages/backend/src/services/blocking/create.ts, packages/backend/src/services/fetch-instance-metadata.ts, packages/backend/src/services/following/create.ts, packages/backend/src/services/following/delete.ts, packages/backend/src/services/following/reject.ts, packages/backend/src/services/following/requests/accept.ts, packages/backend/src/services/following/requests/cancel.ts, packages/backend/src/services/following/requests/create.ts, packages/backend/src/services/i/pin.ts, packages/backend/src/services/logger.ts, packages/backend/src/services/messages/delete.ts, packages/backend/src/services/note/create.ts, packages/backend/src/services/note/delete.ts, packages/backend/src/services/note/reaction/create.ts, packages/backend/src/services/note/reaction/delete.ts, packages/client/package.json, packages/client/src/components/MkAbuseReport.vue, packages/client/src/components/MkAbuseReportWindow.vue, packages/client/src/components/MkAnnouncement.vue, packages/client/src/components/MkAutocomplete.vue, packages/client/src/components/MkAvatars.vue, packages/client/src/components/MkButton.vue, packages/client/src/components/MkChannelFollowButton.vue, packages/client/src/components/MkChannelPreview.vue, packages/client/src/components/MkChart.vue, packages/client/src/components/MkChartTooltip.vue, packages/client/src/components/MkChatPreview.vue, packages/client/src/components/MkContainer.vue, packages/client/src/components/MkCropperDialog.vue, packages/client/src/components/MkCwButton.vue, packages/client/src/components/MkDateSeparatedList.vue, packages/client/src/components/MkDialog.vue, packages/client/src/components/MkDonation.vue, packages/client/src/components/MkDrive.file.vue, packages/client/src/components/MkDrive.folder.vue, packages/client/src/components/MkDrive.navFolder.vue, packages/client/src/components/MkDrive.vue, packages/client/src/components/MkDriveFileThumbnail.vue, packages/client/src/components/MkDriveSelectDialog.vue, packages/client/src/components/MkEmojiPicker.section.vue, packages/client/src/components/MkEmojiPicker.vue, packages/client/src/components/MkEmojiPickerDialog.vue, packages/client/src/components/MkFileListForAdmin.vue, packages/client/src/components/MkFolder.vue, packages/client/src/components/MkFollowButton.vue, packages/client/src/components/MkForgotPassword.vue, packages/client/src/components/MkGalleryPostPreview.vue, packages/client/src/components/MkImageViewer.vue, packages/client/src/components/MkImgWithBlurhash.vue, packages/client/src/components/MkInfo.vue, packages/client/src/components/MkInstanceCardMini.vue, packages/client/src/components/MkInstanceSelectDialog.vue, packages/client/src/components/MkInstanceStats.vue, packages/client/src/components/MkInstanceTicker.vue, packages/client/src/components/MkKeyValue.vue, packages/client/src/components/MkLaunchPad.vue, packages/client/src/components/MkLink.vue, packages/client/src/components/MkManyAnnouncements.vue, packages/client/src/components/MkMedia.vue, packages/client/src/components/MkMediaBanner.vue, packages/client/src/components/MkMediaCaption.vue, packages/client/src/components/MkMediaList.vue, packages/client/src/components/MkMention.vue, packages/client/src/components/MkMenu.child.vue, packages/client/src/components/MkMenu.vue, packages/client/src/components/MkModPlayer.vue, packages/client/src/components/MkModal.vue, packages/client/src/components/MkModalPageWindow.vue, packages/client/src/components/MkModalWindow.vue, packages/client/src/components/MkMoved.vue, packages/client/src/components/MkNote.vue, packages/client/src/components/MkNoteDetailed.vue, packages/client/src/components/MkNotePreview.vue, packages/client/src/components/MkNoteSimple.vue, packages/client/src/components/MkNoteSub.vue, packages/client/src/components/MkNotification.vue, packages/client/src/components/MkNotificationFolded.vue, packages/client/src/components/MkNotificationToast.vue, packages/client/src/components/MkObjectView.value.vue, packages/client/src/components/MkPagePreview.vue, packages/client/src/components/MkPageWindow.vue, packages/client/src/components/MkPagination.vue, packages/client/src/components/MkPoll.vue, packages/client/src/components/MkPollEditor.vue, packages/client/src/components/MkPopupMenu.vue, packages/client/src/components/MkPostForm.vue, packages/client/src/components/MkPostFormAttaches.vue, packages/client/src/components/MkPostSearch.vue, packages/client/src/components/MkPullToRefresh.vue, packages/client/src/components/MkQrCode.vue, packages/client/src/components/MkQuoteButton.vue, packages/client/src/components/MkReactedUsers.vue, packages/client/src/components/MkReactionTooltip.vue, packages/client/src/components/MkReactionsViewer.details.vue, packages/client/src/components/MkReactionsViewer.reaction.vue, packages/client/src/components/MkReactionsViewer.vue, packages/client/src/components/MkRemoteCaution.vue, packages/client/src/components/MkRipple.vue, packages/client/src/components/MkSearchBar.vue, packages/client/src/components/MkShowMoreButton.vue, packages/client/src/components/MkSignin.vue, packages/client/src/components/MkSignup.vue, packages/client/src/components/MkSimpleTextWindow.vue, packages/client/src/components/MkSparkle.vue, packages/client/src/components/MkSuperMenu.vue, packages/client/src/components/MkTab.vue, packages/client/src/components/MkTagCloud.vue, packages/client/src/components/MkTimeline.vue, packages/client/src/components/MkToast.vue, packages/client/src/components/MkTokenGenerateWindow.vue, packages/client/src/components/MkTooltip.vue, packages/client/src/components/MkTutorialDialog.vue, packages/client/src/components/MkUpdated.vue, packages/client/src/components/MkUrlPreview.vue, packages/client/src/components/MkUrlPreviewPopup.vue, packages/client/src/components/MkUserCardMini.vue, packages/client/src/components/MkUserInfo.vue, packages/client/src/components/MkUserPreview.vue, packages/client/src/components/MkUserSelectDialog.vue, packages/client/src/components/MkUserSelectLocalDialog.vue, packages/client/src/components/MkUsersTooltip.vue, packages/client/src/components/MkVisibility.vue, packages/client/src/components/MkVisibilityPicker.vue, packages/client/src/components/MkWaitingDialog.vue, packages/client/src/components/MkWidgets.vue, packages/client/src/components/MkWindow.vue, packages/client/src/components/form/checkbox.vue, packages/client/src/components/form/folder.vue, packages/client/src/components/form/input.vue, packages/client/src/components/form/link.vue, packages/client/src/components/form/radio.vue, packages/client/src/components/form/radios.vue, packages/client/src/components/form/range.vue, packages/client/src/components/form/section.vue, packages/client/src/components/form/select.vue, packages/client/src/components/form/slot.vue, packages/client/src/components/form/suspense.vue, packages/client/src/components/form/switch.vue, packages/client/src/components/form/textarea.vue, packages/client/src/components/global/MkAd.vue, packages/client/src/components/global/MkAvatar.vue, packages/client/src/components/global/MkEmoji.vue, packages/client/src/components/global/MkError.vue, packages/client/src/components/global/MkLoading.vue, packages/client/src/components/global/MkMisskeyFlavoredMarkdown.vue, packages/client/src/components/global/MkPageHeader.vue, packages/client/src/components/global/MkSpacer.vue, packages/client/src/components/global/MkStickyContainer.vue, packages/client/src/components/global/MkUrl.vue, packages/client/src/components/note/MkNoteContent.vue, packages/client/src/components/note/MkNoteFooter.vue, packages/client/src/components/note/MkNoteFooterInfo.vue, packages/client/src/components/note/MkNoteHeader.vue, packages/client/src/components/note/MkNoteHeaderInfo.vue, packages/client/src/components/note/MkNoteMedia.vue, packages/client/src/components/note/MkNoteTranslation.vue, packages/client/src/components/note/MkRenoteBar.vue, packages/client/src/components/page/page.button.vue, packages/client/src/components/page/page.canvas.vue, packages/client/src/components/page/page.counter.vue, packages/client/src/components/page/page.note.vue, packages/client/src/components/page/page.number-input.vue, packages/client/src/components/page/page.post.vue, packages/client/src/components/page/page.section.vue, packages/client/src/components/page/page.switch.vue, packages/client/src/components/page/page.text-input.vue, packages/client/src/components/page/page.text.vue, packages/client/src/init.ts, packages/client/src/navbar.ts, packages/client/src/pages/_error_.vue, packages/client/src/pages/about-firefish.vue, packages/client/src/pages/about.emojis.vue, packages/client/src/pages/about.federation.vue, packages/client/src/pages/about.vue, packages/client/src/pages/admin-file.vue, packages/client/src/pages/admin/_header_.vue, packages/client/src/pages/admin/abuses.vue, packages/client/src/pages/admin/emoji-edit-dialog.vue, packages/client/src/pages/admin/emojis.vue, packages/client/src/pages/admin/files.vue, packages/client/src/pages/admin/index.vue, packages/client/src/pages/admin/overview.federation.vue, packages/client/src/pages/admin/overview.metrics.vue, packages/client/src/pages/admin/overview.moderators.vue, packages/client/src/pages/admin/overview.queue.vue, packages/client/src/pages/admin/overview.stats.vue, packages/client/src/pages/admin/overview.user.vue, packages/client/src/pages/admin/promotions.vue, packages/client/src/pages/admin/queue.chart.vue, packages/client/src/pages/admin/relays.vue, packages/client/src/pages/admin/settings.vue, packages/client/src/pages/admin/users.vue, packages/client/src/pages/announcements.vue, packages/client/src/pages/channel-editor.vue, packages/client/src/pages/channel.vue, packages/client/src/pages/channels.vue, packages/client/src/pages/clip.vue, packages/client/src/pages/emojis.emoji.vue, packages/client/src/pages/explore.featured.vue, packages/client/src/pages/explore.users.vue, packages/client/src/pages/explore.vue, packages/client/src/pages/follow-requests-sent.vue, packages/client/src/pages/follow-requests.vue, packages/client/src/pages/gallery/edit.vue, packages/client/src/pages/gallery/index.vue, packages/client/src/pages/gallery/post.vue, packages/client/src/pages/instance-info.vue, packages/client/src/pages/messaging/index.vue, packages/client/src/pages/messaging/messaging-room.form.vue, packages/client/src/pages/messaging/messaging-room.message.vue, packages/client/src/pages/messaging/messaging-room.vue, packages/client/src/pages/mfm-cheat-sheet.vue, packages/client/src/pages/miauth.vue, packages/client/src/pages/my-antennas/editor.vue, packages/client/src/pages/my-antennas/index.vue, packages/client/src/pages/my-clips/index.vue, packages/client/src/pages/my-groups/group.vue, packages/client/src/pages/my-groups/index.vue, packages/client/src/pages/my-lists/index.vue, packages/client/src/pages/my-lists/list.vue, packages/client/src/pages/no-graze.vue, packages/client/src/pages/note.vue, packages/client/src/pages/notifications.vue, packages/client/src/pages/oauth.vue, packages/client/src/pages/page-editor/els/page-editor.el.button.vue, packages/client/src/pages/page-editor/els/page-editor.el.if.vue, packages/client/src/pages/page-editor/els/page-editor.el.image.vue, packages/client/src/pages/page-editor/els/page-editor.el.note.vue, packages/client/src/pages/page-editor/els/page-editor.el.switch.vue, packages/client/src/pages/page-editor/els/page-editor.el.text.vue, packages/client/src/pages/page-editor/els/page-editor.el.textarea.vue, packages/client/src/pages/page-editor/page-editor.container.vue, packages/client/src/pages/page-editor/page-editor.script-block.vue, packages/client/src/pages/page-editor/page-editor.vue, packages/client/src/pages/page.vue, packages/client/src/pages/pages.vue, packages/client/src/pages/scratchpad.vue, packages/client/src/pages/search.vue, packages/client/src/pages/settings/2fa.qrdialog.vue, packages/client/src/pages/settings/2fa.vue, packages/client/src/pages/settings/accounts.vue, packages/client/src/pages/settings/apps.vue, packages/client/src/pages/settings/deck.vue, packages/client/src/pages/settings/drive.vue, packages/client/src/pages/settings/general.vue, packages/client/src/pages/settings/import-export.vue, packages/client/src/pages/settings/index.vue, packages/client/src/pages/settings/instance-mute.vue, packages/client/src/pages/settings/migration.vue, packages/client/src/pages/settings/mute-block.vue, packages/client/src/pages/settings/navbar.vue, packages/client/src/pages/settings/other.vue, packages/client/src/pages/settings/plugin.vue, packages/client/src/pages/settings/profile.vue, packages/client/src/pages/settings/reaction.vue, packages/client/src/pages/settings/security.vue, packages/client/src/pages/settings/sounds.vue, packages/client/src/pages/settings/statusbar.vue, packages/client/src/pages/settings/theme.vue, packages/client/src/pages/settings/word-mute.vue, packages/client/src/pages/share.vue, packages/client/src/pages/tag.vue, packages/client/src/pages/theme-editor.vue, packages/client/src/pages/timeline.vue, packages/client/src/pages/user-info.vue, packages/client/src/pages/user-list-timeline.vue, packages/client/src/pages/user/clips.vue, packages/client/src/pages/user/home.vue, packages/client/src/pages/user/index.photos.vue, packages/client/src/pages/user/index.timeline.vue, packages/client/src/pages/user/index.vue, packages/client/src/pages/user/media-list.vue, packages/client/src/pages/user/reactions.vue, packages/client/src/pages/welcome.entrance.a.vue, packages/client/src/pages/welcome.entrance.b.vue, packages/client/src/pages/welcome.entrance.c.vue, packages/client/src/pages/welcome.setup.vue, packages/client/src/pages/welcome.timeline.vue, packages/client/src/scripts/get-note-menu.ts, packages/client/src/scripts/get-user-menu.ts, packages/client/src/store.ts, packages/client/src/style.scss, packages/client/src/ui/_common_/common.vue, packages/client/src/ui/_common_/navbar-for-mobile.vue, packages/client/src/ui/_common_/navbar.vue, packages/client/src/ui/_common_/statusbar-federation.vue, packages/client/src/ui/_common_/statusbar-rss.vue, packages/client/src/ui/_common_/statusbar-user-list.vue, packages/client/src/ui/_common_/statusbars.vue, packages/client/src/ui/_common_/stream-indicator.vue, packages/client/src/ui/_common_/upload.vue, packages/client/src/ui/deck.vue, packages/client/src/ui/deck/antenna-column.vue, packages/client/src/ui/deck/channel-column.vue, packages/client/src/ui/deck/column.vue, packages/client/src/ui/deck/direct-column.vue, packages/client/src/ui/deck/list-column.vue, packages/client/src/ui/deck/mentions-column.vue, packages/client/src/ui/deck/notifications-column.vue, packages/client/src/ui/deck/tl-column.vue, packages/client/src/ui/deck/widgets-column.vue, packages/client/src/ui/universal.vue, packages/client/src/ui/universal.widgets.vue, packages/client/src/ui/visitor/a.vue, packages/client/src/ui/visitor/b.vue, packages/client/src/ui/visitor/header.vue, packages/client/src/ui/visitor/kanban.vue, packages/client/src/ui/zen.vue, packages/client/src/widgets/aiscript.vue, packages/client/src/widgets/calendar.vue, packages/client/src/widgets/clock.vue, packages/client/src/widgets/digital-clock.vue, packages/client/src/widgets/federation.vue, packages/client/src/widgets/instance-cloud.vue, packages/client/src/widgets/job-queue.vue, packages/client/src/widgets/memo.vue, packages/client/src/widgets/notifications.vue, packages/client/src/widgets/online-users.vue, packages/client/src/widgets/photos.vue, packages/client/src/widgets/rss-ticker.vue, packages/client/src/widgets/rss.vue, packages/client/src/widgets/server-info.vue, packages/client/src/widgets/server-metric/cpu-mem.vue, packages/client/src/widgets/server-metric/cpu.vue, packages/client/src/widgets/server-metric/disk.vue, packages/client/src/widgets/server-metric/mem.vue, packages/client/src/widgets/server-metric/pie.vue, packages/client/src/widgets/slideshow.vue, packages/client/src/widgets/timeline.vue, packages/client/src/widgets/trends.vue, packages/client/src/widgets/unix-clock.vue, packages/client/vite.config.ts, packages/firefish-js/package.json, packages/firefish-js/src/index.ts, packages/firefish-js/src/misc/langmap.ts, packages/macro-rs/macros-impl/src/lib.rs, packages/macro-rs/macros/src/lib.rs, packages/sw/package.json, packages/sw/src/scripts/notification-read.ts, packages/sw/vite.config.ts, pnpm-lock.yaml)

This commit is contained in:
ひでまる 2024-08-23 02:17:33 +09:00
parent 9911a47887
commit cdd8109e81
517 changed files with 7966 additions and 4444 deletions

View file

@ -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

View file

@ -0,0 +1,99 @@
<!--
This issue template is for bug reports.
There are other issue templates for feature requests, refactor proposals, and discussions,
so please use them if this is not a bug report.
Also, you don't need to prefix the issue title with "Bug:", because it's
managed by issue labels.
-->
<!-- 💖 Thanks for taking the time to fill out this bug report!
💁 Having trouble with deployment? [Ask the support chat.](https://matrix.to/#/#firefish-community:nitro.chat)
🔒 Found a security vulnerability? [Please disclose it responsibly.](https://firefish.dev/firefish/firefish/-/blob/develop/SECURITY.md)
🤝 By submitting this issue, you agree to follow our [Contribution Guidelines.](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md) -->
## What type of issue is this?
<!-- If this happens on your device and has to do with the user interface, it's client-side. If this happens on either with the API or the backend, or you got a server-side error in the client, it's server-side. -->
<!-- Uncomment (remove surrounding arrow signs) the following line(s) to specify the category of this issue. -->
<!-- * label: Server -->
<!-- * label: Client -->
<!-- * label: Mobile -->
<!-- * label: Third-party-client -->
<!-- * label: Docs -->
<!-- * label: Locale -->
<!-- * label: Build from source -->
<!-- * label: Container -->
<!-- * label: Firefish API -->
<!-- * label: Mastodon API -->
<!-- Please do not edit the next line -->
* label: Bug
## What happened?
<!-- Please give us a brief description of what happened. -->
## What did you expect to happen?
<!-- Please give us a brief description of what you expected to happen. -->
## Steps to reproduce the issue
<!-- Please describe how to reproduce this issue (preferably, in a ordered list) -->
## Reproduces how often
<!-- Is it always reproducible, or is it conditional/probabilistic ? -->
## What did you try to solve the issue / Do you have any insights
<!-- Not to repeat the same thing, let us share what you have tried so far. -->
## Version
<!-- What version of firefish is your instance running? You can find this by the instance information page. -->
<details>
### Instance
<!-- What instance of firefish are you using? -->
### 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
<!-- Please copy and paste any relevant log output. -->
</details>
## 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.
<!--
Please tell us how to fix this bug.
As noted in the contribution guidelines, there is a good chance that your
merge request will not be merged if there is no agreement with the project maintainers.
However, we are currently so understaffed that it is virtually impossible to
respond to every single proposal. So, feel free to implement it if there is no response
for more than a week or there is a thumbs-up emoji reaction from the project maintainer(s).
Many thanks for your involvement!
-->

View file

@ -0,0 +1,82 @@
<!--
This issue template is for discussions.
There are other issue templates for bug reports, feature requests, and refactor proposals,
so please use them if this is not a discussion issue.
Also, you don't need to prefix the issue title with "Discussion:", because it's
managed by issue labels.
-->
<!-- 💖 Thanks for taking the time to fill out this bug report!
💁 Having trouble with deployment? [Ask the support chat.](https://matrix.to/#/#firefish-community:nitro.chat)
🔒 Found a security vulnerability? [Please disclose it responsibly.](https://firefish.dev/firefish/firefish/-/blob/develop/SECURITY.md)
🤝 By submitting this issue, you agree to follow our [Contribution Guidelines.](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md) -->
## What type of issue is this?
<!-- If this happens on your device and has to do with the user interface, it's client-side. If this happens on either with the API or the backend, or you got a server-side error in the client, it's server-side. -->
<!-- Uncomment (remove surrounding arrow signs) the following line(s) to specify the category of this issue. -->
<!-- * label: Server -->
<!-- * label: Client -->
<!-- * label: Mobile -->
<!-- * label: Third-party-client -->
<!-- * label: Docs -->
<!-- * label: Locale -->
<!-- * label: Build from source -->
<!-- * label: Container -->
<!-- * label: Firefish API -->
<!-- * label: Mastodon API -->
<!-- Please do not edit the next line -->
* label: Discussion
## What do you think needs to be discussed?
<!-- Please tell us your idea. -->
## Relevant information (optional)
## Version
<!-- What version of firefish is your instance running? You can find this by the instance information page. -->
<details>
### Instance
<!-- What instance of firefish are you using? -->
### 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)
</details>
## 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.
<!--
Please tell us how do you want to implement your idea.
As noted in the contribution guidelines, there is a good chance that your
merge request will not be merged if there is no agreement with the project maintainers.
However, we are currently so understaffed that it is virtually impossible to
respond to every single proposal. So, feel free to implement it if there is no response
for more than a week or there is a thumbs-up emoji reaction from the project maintainer(s).
Many thanks for your involvement!
-->

View file

@ -0,0 +1,67 @@
<!--
This issue template is for feature requests.
There are other issue templates for bug reports, refactor proposals, and discussions,
so please use them if this is not a feature request.
Also, you don't need to prefix the issue title with "Feature:", because it's
managed by issue labels.
-->
<!-- 💖 Thanks for taking the time to fill out this feature request!
💁 Having trouble with deployment? [Ask the support chat.](https://matrix.to/#/#firefish-community:nitro.chat)
🔒 Found a security vulnerability? [Please disclose it responsibly.](https://firefish.dev/firefish/firefish/-/blob/develop/SECURITY.md)
🤝 By submitting this refactor proposal, you agree to follow our [Contribution Guidelines.](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md) -->
## What type of feature is this?
<!-- If this happens on your device and has to do with the user interface, it's client-side. If this happens on either with the API or the backend, or you got a server-side error in the client, it's server-side. -->
<!-- Uncomment (remove surrounding arrow signs) the following line(s) to specify the category of this issue. -->
<!-- * label: Server -->
<!-- * label: Client -->
<!-- * label: Mobile -->
<!-- * label: Third-party-client -->
<!-- * label: Docs -->
<!-- * label: Locale -->
<!-- * label: Build from source -->
<!-- * label: Container -->
<!-- * label: Firefish API -->
<!-- * label: Mastodon API -->
<!-- Please do not edit the next line -->
* label: Feature
## What feature would you like implemented?
<!-- Please give us a brief description of what you'd like to be refactored. -->
## Why should we add this feature?
<!-- Please give us a brief description of why your feature is important. -->
## Version
<!-- What version of firefish is your instance running? You can find this by clicking your instance's logo at the bottom left and then clicking instance information. -->
## Instance
<!-- What instance of Firefish are you using? -->
## 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.
<!--
Please tell us how to implement this feature.
As noted in the contribution guidelines, there is a good chance that your
merge request will not be merged if there is no agreement with the project maintainers.
However, we are currently so understaffed that it is virtually impossible to
respond to every single proposal. So, feel free to implement it if there is no response
for more than a week or there is a thumbs-up emoji reaction from the project maintainer(s).
Many thanks for your involvement!
-->

View file

@ -0,0 +1,67 @@
<!--
This issue template is for refactor proposals.
There are other issue templates for bug reports, feature requests, and discussions,
so please use them if this is not a refactor proposal.
Also, you don't need to prefix the issue title with "Refactor:", because it's
managed by issue labels.
-->
<!-- 💖 Thanks for taking the time to fill out this report!
💁 Having trouble with deployment? [Ask the support chat.](https://matrix.to/#/#firefish-community:nitro.chat)
🔒 Found a security vulnerability? [Please disclose it responsibly.](https://firefish.dev/firefish/firefish/-/blob/develop/SECURITY.md)
🤝 By submitting this feature request, you agree to follow our [Contribution Guidelines.](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md) -->
## What type of refactoring is this?
<!-- If this happens on your device and has to do with the user interface, it's client-side. If this happens on either with the API or the backend, or you got a server-side error in the client, it's server-side. -->
<!-- Uncomment (remove surrounding arrow signs) the following line(s) to specify the category of this issue. -->
<!-- * label: Server -->
<!-- * label: Client -->
<!-- * label: Mobile -->
<!-- * label: Third-party-client -->
<!-- * label: Docs -->
<!-- * label: Locale -->
<!-- * label: Build from source -->
<!-- * label: Container -->
<!-- * label: Firefish API -->
<!-- * label: Mastodon API -->
<!-- Please do not edit the next line -->
* label: Refactor
## What parts of the code do you think should be refactored?
<!-- Please give us a brief description of what you'd like. -->
## Why should the code be refactored that way?
<!-- Please give us a brief description of the reason of your proposal. -->
## Version
<!-- What version of firefish is your instance running? You can find this by clicking your instance's logo at the bottom left and then clicking instance information. -->
## Instance
<!-- What instance of Firefish are you using? -->
## 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.
<!--
Please tell us how to refactor the code.
As noted in the contribution guidelines, there is a good chance that your
merge request will not be merged if there is no agreement with the project maintainers.
However, we are currently so understaffed that it is virtually impossible to
respond to every single proposal. So, feel free to implement it if there is no response
for more than a week or there is a thumbs-up emoji reaction from the project maintainer(s).
Many thanks for your involvement!
-->

View file

@ -0,0 +1,18 @@
<!-- Thanks for taking the time to make Firefish better! -->
## What does this merge request do?
<!-- Please give us a brief description of what this merge request does. -->
## 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
<!-- Uncomment if your merge request has multiple authors -->
<!-- Co-authored-by: Name <email@example.com> -->

View file

@ -0,0 +1,18 @@
<!-- This template is used only when merging the develop branch into the main branch. Please don't use this for other merge requests. -->
/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

378
Cargo.lock generated
View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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).

View file

@ -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)

View file

@ -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";

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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: 各ユーザーが作れるアンテナの最大数

View file

@ -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: 모더레이션 노트

View file

@ -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/<bucket>/" 而非 "<bucket>.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 标签'

View file

@ -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: "僅發佈至關注者"

View file

@ -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"
}
}

View file

@ -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 }

View file

@ -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<ApEmoji>
}
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<Buffer>
export declare function getFullApAccount(username: string, host?: string | undefined | null): string
export declare function getImageSizeFromUrl(url: string): Promise<ImageSize>
@ -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<void>
export declare function renderFollowRelay(relayId: string): Promise<FollowRelay>
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<ApFlag>
export declare function renderFollow(follower: UserLike, followee: UserLike, requestId?: string | undefined | null): ApFollow
export declare function renderFollowRelay(relayId: string): Promise<ApFollow>
export declare function renderHashtag(tagName: string): ApHashtag
export declare function renderLike(reaction: Model): Promise<ApLike>
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<void>
export declare function updateNodeinfoCache(): Promise<void>
/** 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

View file

@ -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

View file

@ -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'"
}
}

199
packages/backend-rs/src/cache/bare.rs vendored Normal file
View file

@ -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<T: Clone> {
cache: Mutex<TimedData<T>>,
ttl: Option<Duration>,
}
struct TimedData<T: Clone> {
value: Option<T>,
last_updated: DateTime<Utc>,
}
impl<T: Clone> Default for Cache<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Clone> Cache<T> {
/// 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<i32> = 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<i32> = 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<T> {
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<Data> = Cache::new();
static CACHE_WITH_TTL: Cache<Data> = 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<Data> = 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<Data> = Cache::new();
static GLOBAL_CACHE_2: Cache<Data> = 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();
}
}
}

7
packages/backend-rs/src/cache/mod.rs vendored Normal file
View file

@ -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};

332
packages/backend-rs/src/cache/redis.rs vendored Normal file
View file

@ -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<dyn std::error::Error>> {
/// 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::<String>(key).await?;
///
/// assert!(cached_data.is_some());
/// assert_eq!(data, cached_data.unwrap());
/// # Ok(())
/// # }
/// ```
pub async fn set<V: for<'a> 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<dyn std::error::Error>> {
/// 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::<String>(key).await?;
/// assert!(cached_data.is_some());
/// assert_eq!(data, cached_data.unwrap());
///
/// // get nonexistent (or expired) cache
/// let no_cache = cache::get::<String>("nonexistent").await?;
/// assert!(no_cache.is_none());
/// # Ok(())
/// # }
/// ```
pub async fn get<V: for<'a> Deserialize<'a> + Serialize>(key: &str) -> Result<Option<V>, Error> {
let serialized_value: Option<Vec<u8>> = redis_conn().await?.get(prefix_key(key)).await?;
Ok(match serialized_value {
Some(v) => rmp_serde::from_slice::<V>(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<dyn std::error::Error>> {
/// 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::<String>("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<V: for<'a> 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<V: for<'a> Deserialize<'a> + Serialize>(
category: Category,
key: &str,
) -> Result<Option<V>, 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<Vec<u8>> = 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<i32> = 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<i32> = 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<Vec<i32>> = get(key_1).await.unwrap();
let expired_value_2: Option<Vec<i32>> = get(key_2).await.unwrap();
let expired_value_3: Option<Vec<i32>> = 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::<String>(Test, key_1).await.unwrap().unwrap(),
value_1
);
assert_eq!(get_one::<u32>(Test, key_2).await.unwrap().unwrap(), value_2);
assert_eq!(
get_one::<char>(Test, key_3).await.unwrap().unwrap(),
value_3
);
delete_one(Test, key_1).await.unwrap();
assert!(get_one::<String>(Test, key_1).await.unwrap().is_none());
assert!(get_one::<u32>(Test, key_2).await.unwrap().is_some());
assert!(get_one::<char>(Test, key_3).await.unwrap().is_some());
delete_all(Test).await.unwrap();
assert!(get_one::<String>(Test, key_1).await.unwrap().is_none());
assert!(get_one::<u32>(Test, key_2).await.unwrap().is_none());
assert!(get_one::<char>(Test, key_3).await.unwrap().is_none());
}
}

View file

@ -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<Option<Meta>> = Mutex::new(None);
fn set_cache(meta: &Meta) {
let _ = CACHE.lock().map(|mut cache| *cache = Some(meta.clone()));
}
static INSTANCE_META_CACHE: Cache<Meta> = Cache::new_with_ttl(Duration::minutes(5));
#[macros::export(js_name = "fetchMeta")]
pub async fn local_server_info() -> Result<Meta, DbErr> {
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<Meta, DbErr> {
async fn local_server_info_impl(force_update_cache: bool) -> Result<Meta, DbErr> {
// 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<Meta, DbErr> {
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<Meta, DbErr> {
})
.exec_with_returning(db)
.await?;
set_cache(&meta);
INSTANCE_META_CACHE.set(meta.clone());
Ok(meta)
}

View file

@ -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;

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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<Self, internal_actor::instance::Error> {
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, internal_actor::instance::Error> {
ApFlag::new(target_user_uri, comment).await
}

View file

@ -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<String>,
) -> Result<Self, Error> {
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<Self, internal_actor::relay::Error> {
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<String>,
) -> Result<ApFollow, Error> {
ApFollow::new(follower, followee, request_id)
}
#[macros::ts_export]
pub async fn render_follow_relay(
relay_id: String,
) -> Result<ApFollow, internal_actor::relay::Error> {
ApFollow::new_relay(relay_id).await
}

View file

@ -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)
}

View file

@ -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<Vec<ApEmoji>>,
}
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<Self, Error> {
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::<Option<String>>()
.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, Error> {
ApLike::new(reaction).await
}

View file

@ -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<Self, MissingRemoteUserUri> {
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, MissingRemoteUserUri> {
ApMention::new(user)
}

View file

@ -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<String>,
pub uri: Option<String>,
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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<User> = OnceCell::const_new();
static INSTANCE_ACTOR: OnceCell<user::Model> = 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<User, Error> {
Ok(get().await?.to_owned())

View file

@ -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::<String>()

View file

@ -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")]

View file

@ -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<Option<Nodeinfo21>> = Mutex::new(None);
fn set_cache(nodeinfo: &Nodeinfo21) {
let _ = CACHE
.lock()
.map(|mut cache| *cache = Some(nodeinfo.to_owned()));
}
static NODEINFO_CACHE: Cache<Nodeinfo21> = 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<Nodeinfo21, DbErr> {
})
}
async fn nodeinfo_2_1_impl(use_cache: bool) -> Result<Nodeinfo21, DbErr> {
if use_cache {
if let Some(nodeinfo) = CACHE.lock().ok().and_then(|cache| cache.to_owned()) {
/// Returns NodeInfo (version 2.1) of the local server.
pub async fn nodeinfo_2_1() -> Result<Nodeinfo21, DbErr> {
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<Nodeinfo21, DbErr> {
nodeinfo_2_1_impl(true).await
}
/// Returns NodeInfo (version 2.0) of the local server.
pub async fn nodeinfo_2_0() -> Result<Nodeinfo20, DbErr> {
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<serde_json::Value, Error> {
pub async fn nodeinfo_2_0_as_json() -> Result<serde_json::Value, Error> {
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(())
}

View file

@ -1,28 +1,10 @@
use std::sync::{Mutex, MutexGuard, OnceLock, PoisonError};
use crate::misc::system_info;
use sysinfo::System;
pub type SysinfoPoisonError = PoisonError<MutexGuard<'static, System>>;
static SYSTEM_INFO: OnceLock<Mutex<System>> = 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> {
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(())
}

View file

@ -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;

View file

@ -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)
}

View file

@ -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<String>,
pub host: Option<String>,
}
#[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<Regex> =
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<String, u32>) -> HashMap<String, u32> {
let mut res = HashMap::<String, u32>::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<String, Error> {
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<Regex> =
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
);
}
}

View file

@ -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()
}

View file

@ -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<ImageSize, Error> {
.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<ImageSize, Error> {
#[cfg(test)]
mod unit_test {
use super::ImageSize;
use crate::database::cache;
use crate::cache;
use pretty_assertions::assert_eq;
#[tokio::test]

View file

@ -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<String, Error> {
#[derive(Debug, Deserialize)]
struct Response {
version: String,
}
static PACKAGE_JSON_CACHE: Cache<PackageJson> = Cache::new_with_ttl(Duration::hours(3));
async fn get_package_json() -> Result<PackageJson, Error> {
// 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<String, Error> {
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<String, Error> {
let version: Option<String> =
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);
}
}

View file

@ -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;

View file

@ -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)
}

View file

@ -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<Vec<u8>, Error> {
if let Some(icon) = cache::get_one::<Vec<u8>>(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<napi::bindgen_prelude::Buffer> {
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)
))),
}
}

View file

@ -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<bool, Error> {
let fetched_value = user::Entity::find_by_id(reader_user_id)
.select_only()
.select_column(user::Column::ReadCatLanguage)
.column(user::Column::ReadCatLanguage)
.into_tuple::<bool>()
.one(db_conn().await?)
.await?
@ -37,7 +36,7 @@ pub async fn should_nyaify(reader_user_id: &str) -> Result<bool, Error> {
cache::Category::CatLang,
reader_user_id,
&fetched_value,
10 * 60,
Duration::minutes(10),
)
.await?;

View file

@ -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<Mutex<System>> = 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<Cpu, SysinfoPoisonError> {
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<Cpu, SysinfoPoisonError> {
cpus => cpus[0].brand().to_owned(),
},
cores: system_info.cpus().len() as u16,
})
}
}
#[macros::export]
pub fn cpu_usage() -> Result<f32, SysinfoPoisonError> {
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<Memory, SysinfoPoisonError> {
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]

View file

@ -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")]

View file

@ -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;

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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::*;

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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<String>,
#[sea_orm(column_name = "webpublicUrl")]
pub webpublic_url: Option<String>,
#[sea_orm(column_name = "accessKey")]
#[sea_orm(column_name = "accessKey", unique)]
pub access_key: Option<String>,
#[sea_orm(column_name = "thumbnailAccessKey")]
#[sea_orm(column_name = "thumbnailAccessKey", unique)]
pub thumbnail_access_key: Option<String>,
#[sea_orm(column_name = "webpublicAccessKey")]
#[sea_orm(column_name = "webpublicAccessKey", unique)]
pub webpublic_access_key: Option<String>,
pub uri: Option<String>,
pub src: Option<String>,

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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<String>,

View file

@ -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,

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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;

View file

@ -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};

View file

@ -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::*;

View file

@ -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};

View file

@ -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<String>,
pub score: i32,
#[sea_orm(column_name = "fileIds")]

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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};

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