Compare commits

...

230 Commits

Author SHA1 Message Date
naskya 452728b352
Merge branch 'develop' into feat/schedule-create 2024-05-16 17:32:42 +09:00
naskya 774c8213f2
fix (backend): correct relation 2024-05-16 17:26:31 +09:00
naskya 41b9fbef96
locale (minor): update en-US.yml 2024-05-16 17:22:53 +09:00
naskya a9e927cef1
Merge branch 'develop' into feat/schedule-create 2024-05-16 17:21:34 +09:00
naskya 8cee026c72 Merge branch 'refactor/types' into 'develop'
refactor: types

Co-authored-by: Lhcfl <Lhcfl@outlook.com>

See merge request firefish/firefish!10762
2024-05-16 08:05:00 +00:00
naskya 0ee3db7788
chore (backend): sort dependencies in package.json 2024-05-16 17:00:33 +09:00
naskya ff63089128
ci: add firefish-js to backend deps 2024-05-16 16:59:04 +09:00
naskya 287dcece57
chore (client): remove debug prints 2024-05-16 16:47:35 +09:00
naskya 9298a6252d
Merge branch 'develop' into refactor/types 2024-05-16 16:45:03 +09:00
naskya 088dfd21e7
v20240516 2024-05-16 16:16:29 +09:00
naskya 03323e40fa
docs: update notice-for-admins.md 2024-05-16 15:09:40 +09:00
naskya c6e3506bd5
fix: remove unnecessary copy operation (close #10926) 2024-05-16 15:03:53 +09:00
naskya 128fc72778 Merge branch 'renovate/lock-file-maintenance' into 'develop'
chore(deps): lock file maintenance

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10853
2024-05-16 05:17:04 +00:00
CI 310059f6a0 chore(deps): lock file maintenance 2024-05-16 04:05:56 +00:00
naskya 7d4d1c1fbd
fix merge mistake 2024-05-16 08:45:50 +09:00
naskya dbd205972f Merge branch 'refactor/push-notification' into 'develop'
refactor: port push notification sender to backend-rs


See merge request firefish/firefish!10760
2024-05-15 22:19:58 +00:00
naskya 41b32c5535 refactor (backend): port push notification sender to backend-rs 2024-05-15 22:19:58 +00:00
naskya 56be2f034e Merge branch 'renovate/syn-2.x' into 'develop'
chore(deps): update rust crate syn to 2.0.63

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10851
2024-05-15 21:22:39 +00:00
naskya e15bcee86c Merge branch 'renovate/aws-sdk-2.x' into 'develop'
fix(deps): update dependency aws-sdk to v2.1621.0

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10852
2024-05-15 21:19:48 +00:00
naskya 43326cdf8d Merge branch 'renovate/serde-monorepo' into 'develop'
chore(deps): update rust crate serde to 1.0.202

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10850
2024-05-15 21:16:55 +00:00
CI 7d1947792d fix(deps): update dependency aws-sdk to v2.1621.0 2024-05-15 21:05:33 +00:00
CI d28fe77d9f chore(deps): update rust crate syn to 2.0.63 2024-05-15 21:05:04 +00:00
CI acc13e9b10 chore(deps): update rust crate serde to 1.0.202 2024-05-15 21:04:59 +00:00
naskya 4e31e11f81
docs: use permalink 2024-05-16 05:04:47 +09:00
naskya dddd2779c0
chore: update auto-generated files 2024-05-16 04:57:48 +09:00
naskya 832fc7cd1d
docs: update changelog 2024-05-16 04:56:26 +09:00
naskya a18ad132be
fix: remove $[center] MFM function 2024-05-16 04:51:51 +09:00
naskya 4b96063c23
chore: format 2024-05-16 04:22:41 +09:00
naskya 0de54e02f8
chore (backend): use literals and consts 2024-05-16 04:22:23 +09:00
naskya 101e50926b
chore: remove import assertion 2024-05-16 04:12:10 +09:00
naskya 9cf88f0df6
chore: remove import assertion 2024-05-16 03:49:31 +09:00
naskya efb6cc9132 Merge branch 'renovate/execa-9.x' into 'develop'
chore(deps): update dependency execa to v9.1.0

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10848
2024-05-15 18:36:18 +00:00
Hosted Weblate 58f3eb4924
Merge branch 'origin/develop' into Weblate 2024-05-15 18:29:30 +00:00
Gary O'Regan Kelly 5adc0e581d
locale: update translations (French)
Currently translated at 100.0% (1932 of 1932 strings)

Translation: Firefish/locales
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/fr/
2024-05-15 20:29:25 +02:00
naskya c0b760cda5 Merge branch 'develop' into 'renovate/execa-9.x'
# Conflicts:
#   package.json
2024-05-15 18:17:35 +00:00
naskya eb967564f9 Merge branch 'renovate/aws-sdk-2.x' into 'develop'
fix(deps): update dependency aws-sdk to v2.1620.0

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10849
2024-05-15 18:13:47 +00:00
naskya 0085105e72 Merge branch 'renovate/websocket-1.x' into 'develop'
fix(deps): update dependency websocket to v1.0.35

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10847
2024-05-15 18:09:16 +00:00
naskya 217b3ecf80 Merge branch 'renovate/is-svg-5.x' into 'develop'
fix(deps): update dependency is-svg to v5.0.1

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10846
2024-05-15 18:07:52 +00:00
naskya ffeeb3b444 Merge branch 'renovate/bull-4.x' into 'develop'
fix(deps): update dependency bull to v4.12.4

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10845
2024-05-15 18:06:29 +00:00
naskya 2f00947a24 Merge branch 'renovate/swc-monorepo' into 'develop'
chore(deps): update swc monorepo

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10844
2024-05-15 18:05:01 +00:00
naskya 5608129913 Merge branch 'renovate/syn-2.x-lockfile' into 'develop'
chore(deps): update rust crate syn to v2.0.63

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10843
2024-05-15 18:02:51 +00:00
naskya 8923e1f2a7 Merge branch 'renovate/serde-monorepo' into 'develop'
chore(deps): update rust crate serde to v1.0.202

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10842
2024-05-15 17:54:30 +00:00
naskya 8765e6ba54
ci: update renovate config 2024-05-16 02:42:59 +09:00
naskya 7c72738983 Merge branch 'renovate/pnpm-9.x' into 'develop'
chore(deps): update pnpm to v9.1.1

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10841
2024-05-15 17:40:54 +00:00
naskya ff446de7e8 Merge branch 'renovate/vue-tsc-2.x' into 'develop'
chore(deps): update dependency vue-tsc to v2.0.18

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10840
2024-05-15 17:36:25 +00:00
naskya 411d00a7af Merge branch 'renovate/vue-draggable-plus-0.x' into 'develop'
chore(deps): update dependency vue-draggable-plus to v0.4.1

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10839
2024-05-15 17:35:22 +00:00
CI 65a1fa870b fix(deps): update dependency aws-sdk to v2.1620.0 2024-05-15 17:29:54 +00:00
CI 1d25c78866 chore(deps): update dependency execa to v9.1.0 2024-05-15 17:29:35 +00:00
CI 6067eaef04 fix(deps): update dependency websocket to v1.0.35 2024-05-15 17:29:14 +00:00
CI 92299423a3 fix(deps): update dependency is-svg to v5.0.1 2024-05-15 17:28:54 +00:00
CI 65a8984c09 fix(deps): update dependency bull to v4.12.4 2024-05-15 17:28:34 +00:00
CI 99eb364778 chore(deps): update swc monorepo 2024-05-15 17:28:11 +00:00
CI 266c81df1e chore(deps): update rust crate syn to v2.0.63 2024-05-15 17:27:48 +00:00
CI 6a2e91efa1 chore(deps): update rust crate serde to v1.0.202 2024-05-15 17:27:42 +00:00
CI 17cbb9cd1e chore(deps): update pnpm to v9.1.1 2024-05-15 17:26:51 +00:00
CI d6ebb55556 chore(deps): update dependency vue-tsc to v2.0.18 2024-05-15 17:26:26 +00:00
CI 4dd1cff80b chore(deps): update dependency vue-draggable-plus to v0.4.1 2024-05-15 17:26:06 +00:00
naskya 752c6dc75b
ci: update renovate config 2024-05-16 02:24:06 +09:00
naskya cede0fdae2 Merge branch 'renovate/node-20.x' into 'develop'
chore(deps): update dependency @types/node to v20.12.12

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10837
2024-05-15 15:01:54 +00:00
naskya 35d706e45d Merge branch 'renovate/swiper-11.x' into 'develop'
chore(deps): update dependency swiper to v11.1.3

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10838
2024-05-15 15:01:26 +00:00
CI 9075050a67 chore(deps): update dependency swiper to v11.1.3 2024-05-15 10:05:12 +00:00
CI edc2a7d890 chore(deps): update dependency @types/node to v20.12.12 2024-05-15 10:04:53 +00:00
naskya 28e2a24585
chore (backend-rs): cleanup 2024-05-15 16:45:35 +09:00
naskya 2884b2fb42
chore (backend-rs): apply clippy fix 2024-05-15 16:36:26 +09:00
naskya d8e1ab63c0
refactor: port system information checker to backend-rs
network stat is removed because it might be inaccurate and/or
it should be monitored by other system tools, but it may be added back
later if it is wanted
2024-05-15 16:26:46 +09:00
Gary O'Regan Kelly c2d5859755
locale: update translations (French)
Currently translated at 100.0% (1932 of 1932 strings)

Translation: Firefish/locales
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/fr/
2024-05-14 20:01:54 +02:00
naskya 457bd22b7b
chore (deps): pin versions 2024-05-12 01:29:19 +09:00
naskya 6176c09509
ci: update renovate config 2024-05-12 01:20:17 +09:00
naskya e9068acddd Merge branch 'renovate/lock-file-maintenance' into 'develop'
chore(deps): lock file maintenance

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10834
2024-05-11 16:11:11 +00:00
CI 3ccacb7fce chore(deps): lock file maintenance 2024-05-11 16:07:50 +00:00
naskya 654ab006a6
ci: update config 2024-05-12 01:03:52 +09:00
naskya 1942d772db Merge branch 'renovate/node-20.x' into 'develop'
chore(deps): update node.js to v20

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10833
2024-05-11 15:59:23 +00:00
naskya bc08f0faa9 Merge branch 'renovate/semver-7.x' into 'develop'
fix(deps): update dependency semver to v7.6.2

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10829
2024-05-11 15:56:59 +00:00
naskya fa35d1f4dd
meta: remove node version info from backend-rs/package.json 2024-05-12 00:52:27 +09:00
naskya 4db42272e7 Merge branch 'renovate/eslint-plugin-file-progress-1.x' into 'develop'
chore(deps): update dependency eslint-plugin-file-progress to ^1.4.0

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10836
2024-05-11 15:51:49 +00:00
naskya cfa3263c46
Merge branch 'develop' into renovate/semver-7.x 2024-05-12 00:49:18 +09:00
naskya b09e418cf6 Merge branch 'renovate/msgpackr-1.x' into 'develop'
fix(deps): update dependency msgpackr to ^1.10.2

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10835
2024-05-11 15:47:12 +00:00
CI e5a5d715b6 chore(deps): update node.js to v20 2024-05-11 15:41:52 +00:00
CI 9ad61fe607 chore(deps): update dependency eslint-plugin-file-progress to ^1.4.0 2024-05-11 15:41:30 +00:00
CI d6983e92aa fix(deps): update dependency semver to v7.6.2 2024-05-11 15:40:50 +00:00
CI 07e2571c79 fix(deps): update dependency msgpackr to ^1.10.2 2024-05-11 15:40:29 +00:00
naskya 0a9289abe3 Merge branch 'renovate/sass-1.x' into 'develop'
chore(deps): update dependency sass to v1.77.1

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10814
2024-05-11 15:25:52 +00:00
naskya 9dda7f955a Merge branch 'renovate/type-fest-4.x' into 'develop'
chore(deps): update dependency type-fest to v4.18.2

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10831
2024-05-11 14:38:51 +00:00
naskya a14a4a5f9c Merge branch 'renovate/redocly-openapi-core-1.x' into 'develop'
fix(deps): update dependency @redocly/openapi-core to v1.12.2

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10826
2024-05-11 14:34:39 +00:00
naskya b3eb12ccff Merge branch 'renovate/eslint-sets-eslint-config-vue3-5.x' into 'develop'
chore(deps): update dependency @eslint-sets/eslint-config-vue3 to ^5.13.0

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10811
2024-05-11 14:34:16 +00:00
naskya 6cd511d473 Merge branch 'renovate/bull-4.x' into 'develop'
fix(deps): update dependency bull to v4.12.3

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10828
2024-05-11 14:30:51 +00:00
naskya a82ed86539 Merge branch 'renovate/prismjs-1.x' into 'develop'
chore(deps): update dependency @types/prismjs to ^1.26.4

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10825
2024-05-11 14:29:38 +00:00
naskya 30ce11f9fe Merge branch 'renovate/vue-tsc-2.x' into 'develop'
chore(deps): update dependency vue-tsc to v2.0.17

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10807
2024-05-11 14:29:22 +00:00
naskya afb2d30e65 Merge branch 'renovate/pg-8.x' into 'develop'
chore(deps): update dependency @types/pg to ^8.11.6

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10824
2024-05-11 14:27:40 +00:00
naskya fb3cd43102 Merge branch 'renovate/napi-rs-cli-2.x' into 'develop'
chore(deps): update dependency @napi-rs/cli to v2.18.3

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10823
2024-05-11 14:26:54 +00:00
CI 1cd58ce05d chore(deps): update dependency type-fest to v4.18.2 2024-05-11 14:08:58 +00:00
CI 55fdd8b5a2 chore(deps): update dependency sass to v1.77.1 2024-05-11 14:08:37 +00:00
CI 704d39f202 chore(deps): update dependency @eslint-sets/eslint-config-vue3 to ^5.13.0 2024-05-11 14:07:55 +00:00
CI ef95673d50 fix(deps): update dependency bull to v4.12.3 2024-05-11 14:07:10 +00:00
CI eb443c8494 fix(deps): update dependency @redocly/openapi-core to v1.12.2 2024-05-11 14:06:30 +00:00
CI 52e3b49533 chore(deps): update dependency vue-tsc to v2.0.17 2024-05-11 14:06:07 +00:00
CI 11ded9491e chore(deps): update dependency @types/prismjs to ^1.26.4 2024-05-11 14:05:47 +00:00
CI d3b899ccc3 chore(deps): update dependency @types/pg to ^8.11.6 2024-05-11 14:05:27 +00:00
CI 6d6c0fbca0 chore(deps): update dependency @napi-rs/cli to v2.18.3 2024-05-11 14:05:03 +00:00
naskya 2257721fe3 Merge branch 'renovate/execa-9.x' into 'develop'
chore(deps): update dependency execa to v9

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10822
2024-05-11 13:10:38 +00:00
naskya cd836daa9b Merge branch 'renovate/otpauth-9.x' into 'develop'
fix(deps): update dependency otpauth to ^9.2.4

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10809
2024-05-11 13:09:51 +00:00
naskya bf5a2c6ebb Merge branch 'renovate/eslint-monorepo' into 'develop'
chore(deps): update dependency eslint to ^9.2.0

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10812
2024-05-11 13:09:13 +00:00
naskya f0632d2a6b Merge branch 'renovate/cropperjs-2.x' into 'develop'
chore(deps): update dependency cropperjs to v2.0.0-beta.5

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10805
2024-05-11 13:06:28 +00:00
naskya d192a7c81a
ci: fix config 2024-05-11 21:35:56 +09:00
naskya 847cc47fc4
chore (deps): update cargo dependencies 2024-05-11 21:27:24 +09:00
naskya 59862f16b0
chore (deps): update bull-board 2024-05-11 21:21:50 +09:00
naskya 0e5e96c99a Merge branch 'renovate/vite-5.x' into 'develop'
chore(deps): update dependency vite to v5.2.11

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10806
2024-05-11 12:18:46 +00:00
naskya dbc24a0b8f
Merge branch 'develop' into renovate/vite-5.x 2024-05-11 21:16:34 +09:00
naskya 1eb26263c1 Merge branch 'renovate/ajv-8.x' into 'develop'
fix(deps): update dependency ajv to v8.13.0

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10819
2024-05-11 12:14:56 +00:00
naskya 639a838736 Merge branch 'renovate/systeminformation-5.x' into 'develop'
fix(deps): update dependency systeminformation to v5.22.8

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10810
2024-05-11 12:14:00 +00:00
naskya 811be1022a Merge branch 'renovate/tesseract.js-5.x' into 'develop'
fix(deps): update dependency tesseract.js to ^5.1.0

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10821
2024-05-11 12:12:27 +00:00
naskya 7f277878a6 Merge branch 'renovate/ws-8.x' into 'develop'
chore(deps): update dependency ws to v8.17.0

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10815
2024-05-11 12:11:49 +00:00
naskya 119cbe3e4f Merge branch 'renovate/bull-board-ui-5.x' into 'develop'
fix(deps): update dependency @bull-board/ui to v5.17.1

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10818
2024-05-11 12:10:23 +00:00
naskya 2e15165117 Merge branch 'renovate/rollup-4.x' into 'develop'
chore(deps): update dependency rollup to v4.17.2

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10813
2024-05-11 12:09:49 +00:00
naskya 1b526c651e Merge branch 'renovate/aws-sdk-2.x' into 'develop'
fix(deps): update dependency aws-sdk to v2.1618.0

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10820
2024-05-11 11:57:14 +00:00
naskya afe06edd16
ci: disable scans for now 2024-05-11 20:49:18 +09:00
naskya 3bdf4f9f9c
ci: GitLab CI's cache is slow 2024-05-11 20:35:01 +09:00
naskya 567ba873e3 Merge branch 'renovate/vue-monorepo' into 'develop'
chore(deps): update vue monorepo to v3.4.27

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10808
2024-05-11 11:19:43 +00:00
naskya 6eef39158e
ci: ignore unrelated files in sast 2024-05-11 19:55:12 +09:00
naskya f81739b8d1
container: cargo fetch using root Cargo.toml 2024-05-11 19:30:00 +09:00
naskya ff7fffc711
container: update Dockerfile to use cargo cache on deps updates 2024-05-11 19:19:28 +09:00
naskya daade3865b Merge branch 'renovate/nodemailer-6.x' into 'develop'
chore(deps): update dependency @types/nodemailer to v6.4.15

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10804
2024-05-11 09:48:00 +00:00
naskya 7b24210bd8
ci: add variables 2024-05-11 18:47:01 +09:00
naskya d77da088f8
ci: tweak config 2024-05-11 18:38:02 +09:00
naskya d6541a3ebb Merge branch 'renovate/jsrsasign-10.x' into 'develop'
chore(deps): update dependency @types/jsrsasign to v10.5.14

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10803
2024-05-11 08:48:59 +00:00
naskya 4fbf211e96
ci: add missing file copy 2024-05-11 10:35:00 +09:00
naskya 132615958b
ci: revise conditions 2024-05-11 10:32:10 +09:00
naskya 27c4b4c812
ci: fix typo 2024-05-11 10:28:39 +09:00
naskya 99f5063f4c
ci: reduce cargo builds 2024-05-11 10:27:09 +09:00
CI ef706fff9d chore(deps): update dependency execa to v9 2024-05-11 00:12:40 +00:00
CI 84528680df fix(deps): update dependency tesseract.js to ^5.1.0 2024-05-11 00:12:21 +00:00
CI 11b5f5cc17 fix(deps): update dependency aws-sdk to v2.1618.0 2024-05-11 00:11:48 +00:00
CI da752a158c fix(deps): update dependency ajv to v8.13.0 2024-05-11 00:11:28 +00:00
CI 693c9edb10 fix(deps): update dependency @bull-board/ui to v5.17.1 2024-05-11 00:11:09 +00:00
CI 4110842357 chore(deps): update dependency ws to v8.17.0 2024-05-11 00:10:10 +00:00
CI 8656bdb185 chore(deps): update dependency rollup to v4.17.2 2024-05-11 00:09:25 +00:00
CI af4426653e chore(deps): update dependency eslint to ^9.2.0 2024-05-11 00:09:02 +00:00
CI 3750ca426b fix(deps): update dependency systeminformation to v5.22.8 2024-05-11 00:08:21 +00:00
CI 3a8ca2a2d7 fix(deps): update dependency otpauth to ^9.2.4 2024-05-11 00:08:02 +00:00
CI ace081f163 chore(deps): update vue monorepo to v3.4.27 2024-05-11 00:07:41 +00:00
CI 184b0e4019 chore(deps): update dependency vite to v5.2.11 2024-05-11 00:06:56 +00:00
CI bec62cffc6 chore(deps): update dependency cropperjs to v2.0.0-beta.5 2024-05-11 00:06:36 +00:00
CI d5493f8e5d chore(deps): update dependency @types/nodemailer to v6.4.15 2024-05-11 00:06:17 +00:00
CI 1cb64b7fa8 chore(deps): update dependency @types/jsrsasign to v10.5.14 2024-05-11 00:05:58 +00:00
naskya 8f59f26aa0
ci: disable nodejs-scan
it doesn't work very well with this repository :(
2024-05-11 09:03:31 +09:00
naskya 6f6333f094
ci: edit sast config 2024-05-11 08:50:13 +09:00
naskya 96cbc6799c
ci: add container scanning 2024-05-11 08:41:33 +09:00
naskya d4f1e06535
ci: add sast-ruleset.toml 2024-05-11 07:59:37 +09:00
naskya f9e2bd2448
ci: enable Static Application Security Testing 2024-05-11 07:26:53 +09:00
naskya b07dc87af6
container: reorder build operations to use cargo build cache on deps updates 2024-05-11 05:39:09 +09:00
naskya aa266d91e0
chore (backend-rs): impl From<Acct> for String 2024-05-11 04:54:30 +09:00
naskya 8f8d62aa58
chore (backend): organize imports 2024-05-11 04:52:59 +09:00
naskya d1b33ad76f
chore (backend-rs): move acct to another directory 2024-05-11 04:31:59 +09:00
naskya eeb09028bd
docs: fix indent 2024-05-11 04:23:20 +09:00
naskya ded0de27c5 Merge branch 'renovate/swc-monorepo' into 'develop'
chore(deps): update dependency @swc/core to v1.5.5

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10802
2024-05-10 12:40:32 +00:00
naskya ac57e4c019
ci: update config 2024-05-10 21:39:13 +09:00
CI 291990d320 chore(deps): update dependency @swc/core to v1.5.5 2024-05-10 12:09:21 +00:00
naskya 2ca7bd65aa
ci: update config 2024-05-10 21:05:34 +09:00
naskya fb4e449139
ci: update renovate config 2024-05-10 21:00:29 +09:00
naskya 084c7f1c84 Merge branch 'renovate/biomejs-biome-1.x' into 'develop'
chore(deps): update dependency @biomejs/biome to v1.7.3

Co-authored-by: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>

See merge request firefish/firefish!10800
2024-05-10 07:52:04 +00:00
naskya 421030c38f
ci: fix rules 2024-05-10 16:34:58 +09:00
naskya f933525856
chore: fix dependencies 2024-05-10 16:25:22 +09:00
CI 9c8e5eabb4 chore(deps): update dependency @biomejs/biome to v1.7.3 2024-05-10 00:32:26 +00:00
naskya 4d3072929e
chore (backend-rs): update doctest comment 2024-05-10 06:59:05 +09:00
naskya 612ce48f44
chore (backend-rs): impl FromStr and Display for Acct 2024-05-10 06:55:51 +09:00
naskya 95fd20a46f
feat (macro-rs): add ts_only_warn macro 2024-05-10 06:54:26 +09:00
naskya 3886c5624b
ci: don't run cargo test with napi feature flag 2024-05-10 06:52:55 +09:00
naskya fc7de024c6
chore: let pnpm detect dependencies 2024-05-10 02:24:25 +09:00
naskya bd88c3399f
chore: update pnpm major version 2024-05-10 01:55:27 +09:00
Hosted Weblate 5b8a164b8d
Merge branch 'origin/develop' into Weblate 2024-05-09 18:21:47 +02:00
jolupa b3cc8cdb3c
locale: update translations (Catalan)
Currently translated at 100.0% (1932 of 1932 strings)

Translation: Firefish/locales
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/ca/
2024-05-09 18:21:46 +02:00
naskya 8373623136
locale: add the Esperanto language 2024-05-10 01:19:13 +09:00
Hosted Weblate d04f85d4bd
Merge branch 'origin/develop' into Weblate 2024-05-09 18:07:17 +02:00
naskya 33853a3a9b
locale: update translations (Japanese)
Currently translated at 100.0% (1932 of 1932 strings)

Translation: Firefish/locales
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/ja/
2024-05-09 18:07:12 +02:00
naskya 26a58c92df
ci: use cargo nextest 2024-05-09 23:19:35 +09:00
naskya 4a81106cf5
chore (backend): remove generate-native-user-token 2024-05-09 21:49:56 +09:00
naskya bdc5d02d27
fix (client): missing import 2024-05-09 19:13:43 +09:00
naskya 075d326d7b Merge branch 'fix/renote-time' into 'develop'
fix: incorrect renote time

Co-authored-by: Lhcfl <Lhcfl@outlook.com>

See merge request firefish/firefish!10798
2024-05-09 09:52:07 +00:00
Lhcfl 0a7f16c11f fix: renote time 2024-05-09 15:49:00 +08:00
naskya 3af8f86924
chore: lint 2024-05-09 02:06:10 +09:00
naskya 276cabbbe3
ci: fix clippy task 2024-05-09 01:15:09 +09:00
naskya af14bee31f
docs: update changelog 2024-05-09 00:41:49 +09:00
naskya b3d1be457b Merge branch 'fix/MkTime' into 'develop'
refactor MkTime: replace the awful ?: chain with if-else; fix: force update ticker when props.time changed

Co-authored-by: Lhcfl <Lhcfl@outlook.com>

See merge request firefish/firefish!10797
2024-05-08 12:11:31 +00:00
Hosted Weblate 347851d6bb
Merge branch 'origin/develop' into Weblate 2024-05-08 10:21:46 +02:00
jolupa abec71074b
locale: update translations (Catalan)
Currently translated at 100.0% (1930 of 1930 strings)

Translation: Firefish/locales
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/ca/
2024-05-08 10:21:45 +02:00
Lhcfl 272e30be0c refactor: replace the awful ?: chain with if-else; fix: force update ticker when props.time changed
related: ed6f866a4f

Co-authored-by: kakkokari-gtyih <kakkokari-gtyih@users.noreply.github.com>
2024-05-08 10:52:32 +08:00
naskya 971f196627
ci: yet another fix 2024-05-08 08:27:54 +09:00
naskya 8cc0e40d35
ci: remove more unneeded paths 2024-05-08 07:16:32 +09:00
naskya beeea86253
ci: remove unneeded steps from clippy check 2024-05-08 06:54:43 +09:00
naskya 084a4bc63a
ci: add pull_policy 2024-05-08 06:46:41 +09:00
naskya cda31d3dc7
Revert "refactor (backend): port publishNotesStream to backend-rs"
This reverts commit 5382dc5da8.

It turns out this sends an inccorect time info to the stream
since JavaScript's Date object doesn't have timezone info

I'll revisit this in the future
2024-05-08 06:08:26 +09:00
naskya 907578e8f8
ci: fix config error 2024-05-08 05:28:41 +09:00
naskya 2923ea86de
ci: update workflow rules 2024-05-08 05:26:59 +09:00
naskya 226c990385
ci: use buildah caches 2024-05-08 05:26:36 +09:00
naskya 769f52c8ee Merge branch 'fix/reactive' into 'develop'
fix: use reactive MkTime

Co-authored-by: Lhcfl <Lhcfl@outlook.com>

See merge request firefish/firefish!10796
2024-05-07 19:59:12 +00:00
naskya 8a00d82f36
ci: add firefish-js 2024-05-08 04:49:13 +09:00
naskya 34ed877f57
ci: don't build the backend on client-only changes 2024-05-08 04:41:20 +09:00
Lhcfl f5074f35cc fix: use reactive MkTime 2024-05-08 03:00:07 +08:00
naskya a847dd55ad
ci: fix cargo clippy task 2024-05-08 03:58:21 +09:00
naskya 5382dc5da8
refactor (backend): port publishNotesStream to backend-rs 2024-05-08 02:15:07 +09:00
naskya 989e93f2a0
fix: migrate back from happy-dom to JSDOM (closes #10924 #10914 #10842)
this reverts commit 4565867b8b.
2024-05-08 01:52:15 +09:00
naskya df81cb6a85 Merge branch 'feat/collepse-reply-timeline' into 'develop'
feat: collepse renotes and replies in timeline

Co-authored-by: Lhcfl <Lhcfl@outlook.com>

Closes #10908

See merge request firefish/firefish!10788
2024-05-07 16:20:45 +00:00
Lhcfl 31168cc7b2 fix: use reacive MkSubNoteContent 2024-05-07 23:42:40 +08:00
Lhcfl 42886f054d fix: use reactive previewableCount 2024-05-07 23:31:45 +08:00
Lhcfl 1d0ea11eea fix: use note capture in MkNoteSimple 2024-05-07 23:23:19 +08:00
Lhcfl 24602c4745 update locales 2024-05-07 22:49:09 +08:00
Lhcfl 33923a59fa fix: use reactive MkNoteHeader 2024-05-07 22:37:09 +08:00
Lhcfl 8067ed4084 Merge branch 'develop' of https://firefish.dev/firefish/firefish into feat/collepse-reply-timeline 2024-05-07 22:34:45 +08:00
Lhcfl 46d0679845 little patch 2024-05-03 00:56:10 +08:00
Lhcfl 160e7f26a6 feat: collepse renotes and replies 2024-05-03 00:22:25 +08:00
Lhcfl 9138c3726a dev: use reactiveState in foldNotification 2024-05-02 01:07:57 +08:00
Lhcfl 425b333474 set collapseReplyInTimeline default to false 2024-05-02 00:57:00 +08:00
Lhcfl d1c76b3882 feat: allow collepse replied posts in timeline 2024-05-02 00:53:52 +08:00
naskya 53dfec57a8
refactor (backend): remove misc/langmap.ts 2024-04-27 09:15:59 +09:00
naskya 1b143ebfaa
Merge branch 'develop' into refactor/types 2024-04-27 09:09:17 +09:00
Lhcfl 8b73a1a6b3 revert changes of vite.config.ts 2024-04-26 10:37:02 +08:00
Lhcfl 41f9a0bda9 fix: Unrecognized language causes errors 2024-04-26 10:34:12 +08:00
Lhcfl 99f30ba01a change firefish-js to esm 2024-04-26 10:22:51 +08:00
Lhcfl fec1a800b6 Merge branch 'develop' of https://firefish.dev/firefish/firefish into refactor/types 2024-04-25 21:13:55 +08:00
Lhcfl 267670af96 move schema & langmap from backend to firefish-js 2024-04-24 02:00:34 +08:00
Lhcfl ea60895bf8 Merge branch 'develop' of https://firefish.dev/firefish/firefish into refactor/types 2024-04-24 00:13:07 +08:00
Lhcfl 5994a1d615 fix types 2024-04-24 00:02:28 +08:00
Lhcfl 9a42745926 fix soft mute languages 2024-04-23 22:41:18 +08:00
Lhcfl c0afa4a2f7 fix types 2024-04-23 22:40:57 +08:00
Lhcfl 62f5c84ca6 fix types 2024-04-23 21:30:55 +08:00
Lhcfl 850201ff71 Merge branch 'develop' of https://firefish.dev/firefish/firefish into refactor/types 2024-04-23 18:37:50 +08:00
Lhcfl 6f324a3dcd refactor: move ColdDeviceStorage into a module 2024-04-23 16:56:39 +08:00
Lhcfl 93bee484bb fix types of pizzax.ts 2024-04-23 10:44:06 +08:00
Lhcfl 07d39cb5ac Merge branch 'develop' of https://firefish.dev/firefish/firefish into refactor/types 2024-04-23 10:28:11 +08:00
Lhcfl 1d3b67eafb fix types of pizzax 2024-04-22 11:01:46 +08:00
196 changed files with 13999 additions and 11783 deletions

View File

@ -3,29 +3,31 @@ image: docker.io/rust:slim-bookworm
services:
- name: docker.io/groonga/pgroonga:latest-alpine-12-slim
alias: postgres
pull_policy: if-not-present
- name: docker.io/redis:7-alpine
alias: redis
pull_policy: if-not-present
workflow:
rules:
- if: $CI_PROJECT_PATH == 'firefish/firefish'
when: always
- if: $CI_MERGE_REQUEST_PROJECT_PATH == 'firefish/firefish'
- if: $CI_PROJECT_PATH == 'firefish/firefish' || $CI_MERGE_REQUEST_PROJECT_PATH == 'firefish/firefish'
changes:
paths:
- packages/**/*
- locales/**/*
- scripts/**/*
- package.json
- Cargo.toml
- Cargo.lock
- Dockerfile
- .dockerignore
when: always
- when: never
cache:
paths:
- node_modules
# - /usr/local/cargo/registry/index
# - /usr/local/cargo/registry/cache
- target/debug/deps
- target/debug/build
stages:
- dependency
- test
- build
- dependency
variables:
POSTGRES_DB: 'firefish_db'
@ -36,6 +38,8 @@ variables:
CARGO_PROFILE_DEV_OPT_LEVEL: '0'
CARGO_PROFILE_DEV_LTO: 'off'
CARGO_PROFILE_DEV_DEBUG: 'none'
CARGO_TERM_COLOR: 'always'
GIT_CLEAN_FLAGS: -ffdx -e node_modules/ -e built/ -e target/ -e packages/backend-rs/built/
default:
before_script:
@ -50,29 +54,114 @@ default:
- export PGPASSWORD="${POSTGRES_PASSWORD}"
- psql --host postgres --user "${POSTGRES_USER}" --dbname "${POSTGRES_DB}" --command 'CREATE EXTENSION pgroonga'
build_test:
test:build:
stage: test
rules:
- if: $CI_PIPELINE_SOURCE == 'push' || $CI_PIPELINE_SOURCE == 'merge_request_event'
- if: $TEST == 'false'
when: never
- if: $CI_COMMIT_BRANCH == 'develop' || $CI_PIPELINE_SOURCE == 'merge_request_event'
changes:
paths:
- packages/**/*
- packages/backend-rs/**/*
- packages/macro-rs/**/*
- scripts/**/*
- locales/**/*
- package.json
- pnpm-lock.yaml
- Cargo.toml
- Cargo.lock
when: always
needs:
- job: cargo:clippy
optional: true
- job: cargo:test
optional: true
script:
- pnpm install --frozen-lockfile
- pnpm run build:debug
- pnpm run migrate
container_image_build:
test:build:backend_ts_only:
stage: test
rules:
- if: $TEST == 'false'
when: never
- if: $CI_COMMIT_BRANCH == 'develop' || $CI_PIPELINE_SOURCE == 'merge_request_event'
changes:
paths:
- packages/backend-rs/**/*
- packages/macro-rs/**/*
- scripts/**/*
- package.json
- Cargo.toml
- Cargo.lock
when: never
- if: $CI_COMMIT_BRANCH == 'develop' || $CI_PIPELINE_SOURCE == 'merge_request_event'
changes:
paths:
- packages/backend/**/*
- packages/firefish-js/**/*
- packages/megalodon/**/*
when: always
before_script:
- apt-get update && apt-get -y upgrade
- 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 nodejs postgresql-client
- corepack enable
- corepack prepare pnpm@latest --activate
- mkdir -p packages/backend-rs/built
- cp packages/backend-rs/index.js packages/backend-rs/built/index.js
- cp packages/backend-rs/index.d.ts packages/backend-rs/built/index.d.ts
- cp .config/ci.yml .config/default.yml
- export PGPASSWORD="${POSTGRES_PASSWORD}"
- psql --host postgres --user "${POSTGRES_USER}" --dbname "${POSTGRES_DB}" --command 'CREATE EXTENSION pgroonga'
script:
- pnpm install --frozen-lockfile
- pnpm --filter 'backend' --filter 'firefish-js' --filter 'megalodon' run build:debug
- pnpm run migrate
test:build:client_only:
stage: test
rules:
- if: $TEST == 'false'
when: never
- if: $CI_COMMIT_BRANCH == 'develop' || $CI_PIPELINE_SOURCE == 'merge_request_event'
changes:
paths:
- packages/backend-rs/**/*
- packages/macro-rs/**/*
- scripts/**/*
- package.json
- Cargo.toml
- Cargo.lock
when: never
- if: $CI_COMMIT_BRANCH == 'develop' || $CI_PIPELINE_SOURCE == 'merge_request_event'
changes:
paths:
- packages/client/**/*
- packages/firefish-js/**/*
- packages/sw/**/*
- locales/**/*
when: always
services: []
before_script:
- apt-get update && apt-get -y upgrade
- 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
- corepack prepare pnpm@latest --activate
- cp .config/ci.yml .config/default.yml
script:
- pnpm install --frozen-lockfile
- pnpm --filter 'firefish-js' --filter 'client' --filter 'sw' run build:debug
build:container:
stage: build
image: docker.io/debian:bookworm-slim
services: []
rules:
- if: $BUILD == 'false'
when: never
- if: $CI_COMMIT_BRANCH == 'develop'
changes:
paths:
@ -80,54 +169,87 @@ container_image_build:
- locales/**/*
- scripts/copy-assets.mjs
- package.json
- pnpm-lock.yaml
- Cargo.toml
- Cargo.lock
- Dockerfile
- .dockerignore
when: always
needs:
- job: test:build
optional: true
- job: test:build:backend_ts_only
optional: true
- job: test:build:client_only
optional: true
before_script:
- apt-get update && apt-get -y upgrade
- apt-get install -y --no-install-recommends buildah ca-certificates fuse-overlayfs
- buildah login --username "${CI_REGISTRY_USER}" --password "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY}"
- export IMAGE_TAG="${CI_REGISTRY}/${CI_PROJECT_PATH}/develop:not-for-production"
- export IMAGE_CACHE="${CI_REGISTRY}/${CI_PROJECT_PATH}/develop/cache"
script:
- buildah build --isolation chroot --device /dev/fuse:rw --security-opt seccomp=unconfined --security-opt apparmor=unconfined --cap-add all --tag "${IMAGE_TAG}" --platform linux/amd64 .
- |-
buildah build \
--isolation chroot \
--device /dev/fuse:rw \
--security-opt seccomp=unconfined \
--security-opt apparmor=unconfined \
--cap-add all \
--platform linux/amd64 \
--layers \
--cache-to "${IMAGE_CACHE}" \
--cache-from "${IMAGE_CACHE}" \
--tag "${IMAGE_TAG}" \
.
- buildah inspect "${IMAGE_TAG}"
- buildah push "${IMAGE_TAG}"
cargo_unit_test:
cargo:test:
stage: test
rules:
- if: $CI_PIPELINE_SOURCE == 'merge_request_event' || $CI_COMMIT_BRANCH == 'develop'
- if: $TEST == 'false'
when: never
- if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == 'main'
when: never
- if: $CI_COMMIT_BRANCH == 'develop' || $CI_PIPELINE_SOURCE == 'merge_request_event'
changes:
paths:
- packages/backend-rs/**/*
- packages/macro-rs/**/*
- Cargo.toml
- Cargo.lock
- if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == 'main'
when: never
when: always
script:
- cargo check --features napi
- curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C /usr/local/cargo/bin
- pnpm install --frozen-lockfile
- mkdir packages/backend-rs/built
- mkdir -p packages/backend-rs/built
- cp packages/backend-rs/index.js packages/backend-rs/built/index.js
- cp packages/backend-rs/index.d.ts packages/backend-rs/built/index.d.ts
- pnpm --filter='!backend-rs' run build:debug
- cargo test
- cargo test --doc
- cargo nextest run
cargo_clippy:
cargo:clippy:
stage: test
rules:
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
- if: $TEST == 'false'
when: never
- if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == 'main'
when: never
- if: $CI_COMMIT_BRANCH == 'develop' || $CI_PIPELINE_SOURCE == 'merge_request_event'
changes:
paths:
- packages/backend-rs/**/*
- packages/macro-rs/**/*
- Cargo.toml
- Cargo.lock
- if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == 'main'
when: never
when: always
services: []
before_script:
- apt-get update && apt-get -y upgrade
- 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:
- cargo clippy -- -D warnings

759
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -5,8 +5,8 @@ resolver = "2"
[workspace.dependencies]
macro_rs = { path = "packages/macro-rs" }
napi = { version = "2.16.4", default-features = false }
napi-derive = "2.16.3"
napi = { version = "2.16.6", default-features = false }
napi-derive = "2.16.4"
napi-build = "2.1.3"
argon2 = "0.5.3"
@ -23,24 +23,26 @@ nom-exif = "1.2.0"
once_cell = "1.19.0"
openssl = "0.10.64"
pretty_assertions = "1.4.0"
proc-macro2 = "1.0.81"
proc-macro2 = "1.0.82"
quote = "1.0.36"
rand = "0.8.5"
redis = "0.25.3"
regex = "1.10.4"
rmp-serde = "1.2.0"
rmp-serde = "1.3.0"
sea-orm = "0.12.15"
serde = "1.0.198"
serde_json = "1.0.116"
serde = "1.0.202"
serde_json = "1.0.117"
serde_yaml = "0.9.34"
strum = "0.26.2"
syn = "2.0.60"
thiserror = "1.0.59"
syn = "2.0.63"
sysinfo = "0.30.12"
thiserror = "1.0.60"
tokio = "1.37.0"
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
url = "2.5.0"
urlencoding = "2.1.3"
web-push = { git = "https://github.com/pimeys/rust-web-push", rev = "40febe4085e3cef9cdfd539c315e3e945aba0656" }
[profile.release]
lto = true

View File

@ -7,7 +7,12 @@ RUN apk update && apk add --no-cache build-base linux-headers curl ca-certificat
RUN curl --proto '=https' --tlsv1.2 --silent --show-error --fail https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"
# Copy only the cargo dependency-related files first, to cache efficiently
# Copy only backend-rs dependency-related files first, to cache efficiently
COPY package.json pnpm-workspace.yaml ./
COPY packages/backend-rs/package.json packages/backend-rs/package.json
COPY packages/backend-rs/npm/linux-x64-musl/package.json packages/backend-rs/npm/linux-x64-musl/package.json
COPY packages/backend-rs/npm/linux-arm64-musl/package.json packages/backend-rs/npm/linux-arm64-musl/package.json
COPY Cargo.toml Cargo.toml
COPY Cargo.lock Cargo.lock
COPY packages/backend-rs/Cargo.toml packages/backend-rs/Cargo.toml
@ -15,22 +20,9 @@ COPY packages/backend-rs/src/lib.rs packages/backend-rs/src/
COPY packages/macro-rs/Cargo.toml packages/macro-rs/Cargo.toml
COPY packages/macro-rs/src/lib.rs packages/macro-rs/src/
# Install cargo dependencies
RUN cargo fetch --locked --manifest-path /firefish/packages/backend-rs/Cargo.toml
# Copy only the dependency-related files first, to cache efficiently
COPY package.json pnpm*.yaml ./
COPY packages/backend/package.json packages/backend/package.json
COPY packages/client/package.json packages/client/package.json
COPY packages/sw/package.json packages/sw/package.json
COPY packages/firefish-js/package.json packages/firefish-js/package.json
COPY packages/megalodon/package.json packages/megalodon/package.json
COPY packages/backend-rs/package.json packages/backend-rs/package.json
COPY packages/backend-rs/npm/linux-x64-musl/package.json packages/backend-rs/npm/linux-x64-musl/package.json
COPY packages/backend-rs/npm/linux-arm64-musl/package.json packages/backend-rs/npm/linux-arm64-musl/package.json
# Configure pnpm, and install dev mode dependencies for compilation
RUN corepack enable && corepack prepare pnpm@latest --activate && pnpm install --frozen-lockfile
# Configure pnpm, and install backend-rs dependencies
RUN corepack enable && corepack prepare pnpm@latest --activate && pnpm --filter backend-rs install
RUN cargo fetch --locked --manifest-path Cargo.toml
# Copy in the rest of the rust files
COPY packages/backend-rs packages/backend-rs/
@ -42,10 +34,22 @@ RUN NODE_ENV='production' pnpm run --filter backend-rs build
# Copy/Overwrite index.js to mitigate the bug in napi-rs codegen
COPY packages/backend-rs/index.js packages/backend-rs/built/index.js
# Copy in the rest of the files to compile
# Copy only the dependency-related files first, to cache efficiently
COPY packages/backend/package.json packages/backend/package.json
COPY packages/client/package.json packages/client/package.json
COPY packages/sw/package.json packages/sw/package.json
COPY packages/firefish-js/package.json packages/firefish-js/package.json
COPY packages/megalodon/package.json packages/megalodon/package.json
COPY pnpm-lock.yaml ./
# Install dev mode dependencies for compilation
RUN pnpm install --frozen-lockfile
# Copy in the rest of the files to build
COPY . ./
RUN NODE_ENV='production' pnpm run --filter firefish-js build
RUN NODE_ENV='production' pnpm run --recursive --parallel --filter '!backend-rs' --filter '!firefish-js' build && pnpm run build:assets
# Build other workspaces
RUN NODE_ENV='production' pnpm run --recursive --filter '!backend-rs' build && pnpm run build:assets
# Trim down the dependencies to only those for production
RUN find . -path '*/node_modules/*' -delete && pnpm install --prod --frozen-lockfile

View File

@ -2,8 +2,10 @@
Breaking changes are indicated by the :warning: icon.
## Unreleased
## v20240516
- :warning: `server-info` (an endpoint to get server hardware information) now requires credentials.
- :warning: `net` (server's default network interface) has been removed from `admin/server-info`.
- Adding `lang` to the response of `i` and the request parameter of `i/update`.
## v20240504

View File

@ -5,6 +5,13 @@ Critical security updates are indicated by the :warning: icon.
- Server administrators should check [notice-for-admins.md](./notice-for-admins.md) as well.
- Third-party client/bot developers may want to check [api-change.md](./api-change.md) as well.
## [v20240516](https://firefish.dev/firefish/firefish/-/merge_requests/10854/commits)
- Improve timeline UX (you can restore the original appearance by settings)
- Remove `$[center]` MFM function
- This function was suddenly added last year (https://firefish.dev/firefish/firefish/-/commit/1a971efa689323d54eebb4d3646e102fb4d1d95a), but according to the [MFM spec](https://github.com/misskey-dev/mfm.js/blob/6aaf68089023c6adebe44123eebbc4dcd75955e0/docs/syntax.md#fn), `$[something]` must be an inline element (while `center` is a block element), so such a syntax is not expected by MFM renderers. Please use `<center></center>` instead.
- Fix bugs
## [v20240504](https://firefish.dev/firefish/firefish/-/merge_requests/10790/commits)
- Fix bugs

View File

@ -2,6 +2,12 @@
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](./upgrade.md).
## v20240516
### For all users
Firefish is now compatible with [Node v22](https://nodejs.org/en/blog/announcements/v22-release-announce). The pre-built OCI container image will still be using the latest LTS version (v20.13.1 as of now).
## v20240430
### For all users
@ -17,11 +23,13 @@ You can control the verbosity of the server log by adding `maxLogLevel` in `.con
- Not only Firefish but also Node.js has recently fixed a few security issues:
- https://nodejs.org/en/blog/vulnerability/april-2024-security-releases
- https://nodejs.org/en/blog/vulnerability/april-2024-security-releases-2
So, it is highly recommended that you upgrade your Node.js version as well. The new versions are
- Node v18.20.2 (v18.x LTS)
- Node v20.12.2 (v20.x LTS)
- Node v21.7.3 (v21.x)
- You can check your Node.js version by this command:
You can check your Node.js version by this command:
```sh
node --version
```

View File

@ -2301,3 +2301,9 @@ getQrCode: Mostrar el codi QR
copyRemoteFollowUrl: Còpia la adreça URL del seguidor remot
foldNotification: Agrupar les notificacions similars
slashQuote: Cita encadenada
i18nServerInfo: Els nous clients els trobares en {language} per defecte.
i18nServerChange: Fes servir {language} en comptes.
i18nServerSet: Fes servir {language} per els nous clients.
mergeThreadInTimeline: Fusiona diferents publicacions en un mateix fil a les línies
de temps
mergeRenotesInTimeline: Agrupa diferents impulsos d'una mateixa publicació

View File

@ -1588,7 +1588,7 @@ _ago:
yearsAgo: "{n}y ago"
_later:
future: "Future"
justNow: "Immediate"
justNow: "Right now"
secondsAgo: "{n}s later"
minutesAgo: "{n}m later"
hoursAgo: "{n}h later"
@ -2256,3 +2256,5 @@ slashQuote: "Chain quote"
foldNotification: "Group similar notifications"
scheduledPost: "Scheduled post"
scheduledDate: "Scheduled date"
mergeThreadInTimeline: "Merge multiple posts in the same thread in timelines"
mergeRenotesInTimeline: "Group multiple boosts of the same post"

1
locales/eo.yml Normal file
View File

@ -0,0 +1 @@
_lang_: "Esperanto"

View File

@ -1142,8 +1142,8 @@ _wordMute:
mutedNotes: "Publications masquées"
muteLangsDescription2: Utiliser les codes de langue (i.e en, fr, ja, zh).
lang: Langue
langDescription: Cacher du fil de publication les publications qui correspondent
à ces langues.
langDescription: Cachez les publications qui correspondent à la langue définie dans
le fil d'actualité.
muteLangs: Langues filtrées
muteLangsDescription: Séparer avec des espaces ou des retours à la ligne pour une
condition OU (OR).
@ -1260,7 +1260,7 @@ _tutorial:
step2_2: "En fournissant quelques informations sur qui vous êtes, il sera plus facile
pour les autres de savoir s'ils veulent voir vos publcations ou s'abonner à vous."
step3_1: "Maintenant il est temps de vous abonner à des gens!"
step3_2: "Vos fils d'actualités Principal et Social sont basés sur les personnes
step3_2: "Vos fils d'actualité Principal et Social sont basés sur les personnes
que vous êtes abonné, alors essayez de vous abonner à quelques comptes pour commencer.\n
Cliquez sur le cercle « plus » en haut à droite d'un profil pour vous abonner."
step4_1: "On y va."
@ -2332,3 +2332,9 @@ inputAccountId: Veuillez saisir votre compte (par exemple, @firefish@info.firefi
remoteFollow: Abonnement à distance
copyRemoteFollowUrl: Copier l'URL d'abonnement à distance
slashQuote: Citation enchaînée
i18nServerInfo: Les nouveaux clients seront en {language} par défaut.
i18nServerChange: Utilisez {language} à la place.
i18nServerSet: Utilisez {language} pour les nouveaux clients.
mergeThreadInTimeline: Fusionner plusieurs publications dans le même fil dans les
fils d'actualité
mergeRenotesInTimeline: Regrouper plusieurs boosts du même publication

View File

@ -2071,3 +2071,5 @@ getQrCode: QRコードを表示
copyRemoteFollowUrl: リモートからフォローするURLをコピー
foldNotification: 同じ種類の通知をまとめて表示する
slashQuote: 繋げて引用
mergeRenotesInTimeline: タイムラインで同じ投稿のブーストをまとめる
mergeThreadInTimeline: タイムラインで同じスレッドの投稿をまとめる

View File

@ -2083,3 +2083,5 @@ slashQuote: "斜杠引用"
foldNotification: "将通知按同类型分组"
scheduledPost: "定时发送"
scheduledDate: "发送日期"
mergeThreadInTimeline: "将时间线内的连续回复合并成一串"
mergeRenotesInTimeline: "合并同一个帖子的转发"

View File

@ -1,17 +1,17 @@
{
"name": "firefish",
"version": "20240504",
"version": "20240516",
"repository": {
"type": "git",
"url": "https://firefish.dev/firefish/firefish.git"
},
"packageManager": "pnpm@8.15.7",
"packageManager": "pnpm@9.1.1",
"private": true,
"scripts": {
"rebuild": "pnpm run clean && pnpm run build",
"build": "pnpm node ./scripts/build.mjs && pnpm run build:assets",
"build": "pnpm --recursive --color run build && pnpm node ./scripts/copy-index.mjs && pnpm run build:assets",
"build:assets": "pnpm node ./scripts/copy-assets.mjs",
"build:debug": "pnpm run clean && pnpm node ./scripts/dev-build.mjs && pnpm run build:assets",
"build:debug": "pnpm run clean && pnpm --recursive --color run build:debug && pnpm node ./scripts/copy-index-dev.mjs && pnpm run build:assets",
"start": "pnpm --filter backend run start",
"start:container": "pnpm run build:assets && pnpm run migrate && pnpm run start",
"start:test": "pnpm --filter backend run start:test",
@ -41,14 +41,14 @@
"js-yaml": "4.1.0"
},
"devDependencies": {
"@biomejs/biome": "1.7.1",
"@biomejs/cli-darwin-arm64": "^1.7.1",
"@biomejs/cli-darwin-x64": "^1.7.1",
"@biomejs/cli-linux-arm64": "^1.7.1",
"@biomejs/cli-linux-x64": "^1.7.1",
"@types/node": "20.12.7",
"execa": "8.0.1",
"pnpm": "8.15.7",
"@biomejs/biome": "1.7.3",
"@biomejs/cli-darwin-arm64": "1.7.3",
"@biomejs/cli-darwin-x64": "1.7.3",
"@biomejs/cli-linux-arm64": "1.7.3",
"@biomejs/cli-linux-x64": "1.7.3",
"@types/node": "20.12.12",
"execa": "9.1.0",
"pnpm": "9.1.1",
"typescript": "5.4.5"
}
}

View File

@ -39,12 +39,14 @@ serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
serde_yaml = { workspace = true }
strum = { workspace = true, features = ["derive"] }
sysinfo = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["full"] }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
url = { workspace = true }
urlencoding = { workspace = true }
web-push = { workspace = true }
[dev-dependencies]
pretty_assertions = { workspace = true }

View File

@ -41,7 +41,6 @@ export interface ServerConfig {
proxySmtp?: string
proxyBypassHosts?: Array<string>
allowedPrivateNetworks?: Array<string>
/** `NapiValue` is not implemented for `u64` */
maxFileSize?: number
accessLog?: string
clusterLimits?: WorkerConfigInternal
@ -212,6 +211,8 @@ export interface Acct {
}
export function stringToAcct(acct: string): Acct
export function acctToString(acct: Acct): string
export function showServerInfo(): void
export function initializeRustLogger(): void
export function addNoteToAntenna(antennaId: string, note: Note): void
/**
* Checks if a server is blocked.
@ -235,7 +236,6 @@ export function isSilencedServer(host: string): Promise<boolean>
* `host` - punycoded instance host
*/
export function isAllowedServer(host: string): Promise<boolean>
/** TODO: handle name collisions better */
export interface NoteLikeForCheckWordMute {
fileIds: Array<string>
userId: string | null
@ -260,7 +260,6 @@ export interface ImageSize {
height: number
}
export function getImageSizeFromUrl(url: string): Promise<ImageSize>
/** TODO: handle name collisions better */
export interface NoteLikeForGetNoteSummary {
fileIds: Array<string>
text: string | null
@ -268,6 +267,28 @@ export interface NoteLikeForGetNoteSummary {
hasPoll: boolean
}
export function getNoteSummary(note: NoteLikeForGetNoteSummary): string
export interface Cpu {
model: string
cores: number
}
export interface Memory {
/** Total memory amount in bytes */
total: number
/** Used memory amount in bytes */
used: number
/** Available (for (re)use) memory amount in bytes */
available: number
}
export interface Storage {
/** Total storage space in bytes */
total: number
/** Used storage space in bytes */
used: number
}
export function cpuInfo(): Cpu
export function cpuUsage(): number
export function memoryUsage(): Memory
export function storageUsage(): Storage | null
export function isSafeUrl(url: string): boolean
export function latestVersion(): Promise<string>
export function toMastodonId(firefishId: string): string | null
@ -1156,7 +1177,6 @@ export interface Webhook {
latestSentAt: Date | null
latestStatus: number | null
}
export function initializeRustLogger(): void
export function fetchNodeinfo(host: string): Promise<Nodeinfo>
export function nodeinfo_2_1(): Promise<any>
export function nodeinfo_2_0(): Promise<any>
@ -1259,6 +1279,15 @@ export interface Users {
}
export function watchNote(watcherId: string, noteAuthorId: string, noteId: string): Promise<void>
export function unwatchNote(watcherId: string, noteId: string): Promise<void>
export enum PushNotificationKind {
Generic = 'generic',
Chat = 'chat',
ReadAllChats = 'readAllChats',
ReadAllChatsInTheRoom = 'readAllChatsInTheRoom',
ReadNotifications = 'readNotifications',
ReadAllNotifications = 'readAllNotifications'
}
export function sendPushNotification(receiverUserId: string, kind: PushNotificationKind, content: any): Promise<void>
export function publishToChannelStream(channelId: string, userId: string): void
export enum ChatEvent {
Message = 'message',
@ -1304,4 +1333,6 @@ export function getTimestamp(id: string): number
export function genId(): string
/** Generate an ID using a specific datetime */
export function genIdAt(date: Date): string
export function secureRndstr(length?: number | undefined | null): string
/** Generate random string based on [thread_rng] and [Alphanumeric]. */
export function generateSecureRandomString(length: number): string
export function generateUserToken(): string

View File

@ -310,7 +310,7 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}
const { SECOND, MINUTE, HOUR, DAY, USER_ONLINE_THRESHOLD, USER_ACTIVE_THRESHOLD, FILE_TYPE_BROWSERSAFE, loadEnv, loadConfig, stringToAcct, acctToString, addNoteToAntenna, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getImageSizeFromUrl, getNoteSummary, isSafeUrl, latestVersion, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, AntennaSrcEnum, DriveFileUsageHintEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, initializeRustLogger, fetchNodeinfo, nodeinfo_2_1, nodeinfo_2_0, Protocol, Inbound, Outbound, watchNote, unwatchNote, publishToChannelStream, ChatEvent, publishToChatStream, ChatIndexEvent, publishToChatIndexStream, publishToBroadcastStream, publishToGroupChatStream, publishToModerationStream, getTimestamp, genId, genIdAt, secureRndstr } = nativeBinding
const { SECOND, MINUTE, HOUR, DAY, USER_ONLINE_THRESHOLD, USER_ACTIVE_THRESHOLD, FILE_TYPE_BROWSERSAFE, loadEnv, loadConfig, stringToAcct, acctToString, showServerInfo, initializeRustLogger, addNoteToAntenna, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getImageSizeFromUrl, getNoteSummary, cpuInfo, cpuUsage, memoryUsage, storageUsage, isSafeUrl, latestVersion, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, AntennaSrcEnum, DriveFileUsageHintEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, fetchNodeinfo, nodeinfo_2_1, nodeinfo_2_0, Protocol, Inbound, Outbound, watchNote, unwatchNote, PushNotificationKind, sendPushNotification, publishToChannelStream, ChatEvent, publishToChatStream, ChatIndexEvent, publishToChatIndexStream, publishToBroadcastStream, publishToGroupChatStream, publishToModerationStream, getTimestamp, genId, genIdAt, generateSecureRandomString, generateUserToken } = nativeBinding
module.exports.SECOND = SECOND
module.exports.MINUTE = MINUTE
@ -323,6 +323,8 @@ module.exports.loadEnv = loadEnv
module.exports.loadConfig = loadConfig
module.exports.stringToAcct = stringToAcct
module.exports.acctToString = acctToString
module.exports.showServerInfo = showServerInfo
module.exports.initializeRustLogger = initializeRustLogger
module.exports.addNoteToAntenna = addNoteToAntenna
module.exports.isBlockedServer = isBlockedServer
module.exports.isSilencedServer = isSilencedServer
@ -339,6 +341,10 @@ module.exports.safeForSql = safeForSql
module.exports.formatMilliseconds = formatMilliseconds
module.exports.getImageSizeFromUrl = getImageSizeFromUrl
module.exports.getNoteSummary = getNoteSummary
module.exports.cpuInfo = cpuInfo
module.exports.cpuUsage = cpuUsage
module.exports.memoryUsage = memoryUsage
module.exports.storageUsage = storageUsage
module.exports.isSafeUrl = isSafeUrl
module.exports.latestVersion = latestVersion
module.exports.toMastodonId = toMastodonId
@ -364,7 +370,6 @@ module.exports.RelayStatusEnum = RelayStatusEnum
module.exports.UserEmojimodpermEnum = UserEmojimodpermEnum
module.exports.UserProfileFfvisibilityEnum = UserProfileFfvisibilityEnum
module.exports.UserProfileMutingnotificationtypesEnum = UserProfileMutingnotificationtypesEnum
module.exports.initializeRustLogger = initializeRustLogger
module.exports.fetchNodeinfo = fetchNodeinfo
module.exports.nodeinfo_2_1 = nodeinfo_2_1
module.exports.nodeinfo_2_0 = nodeinfo_2_0
@ -373,6 +378,8 @@ module.exports.Inbound = Inbound
module.exports.Outbound = Outbound
module.exports.watchNote = watchNote
module.exports.unwatchNote = unwatchNote
module.exports.PushNotificationKind = PushNotificationKind
module.exports.sendPushNotification = sendPushNotification
module.exports.publishToChannelStream = publishToChannelStream
module.exports.ChatEvent = ChatEvent
module.exports.publishToChatStream = publishToChatStream
@ -384,4 +391,5 @@ module.exports.publishToModerationStream = publishToModerationStream
module.exports.getTimestamp = getTimestamp
module.exports.genId = genId
module.exports.genIdAt = genIdAt
module.exports.secureRndstr = secureRndstr
module.exports.generateSecureRandomString = generateSecureRandomString
module.exports.generateUserToken = generateUserToken

View File

@ -22,10 +22,7 @@
}
},
"devDependencies": {
"@napi-rs/cli": "2.18.1"
},
"engines": {
"node": ">= 10"
"@napi-rs/cli": "2.18.3"
},
"scripts": {
"artifacts": "napi artifacts",

View File

@ -22,7 +22,7 @@ struct ServerConfig {
pub proxy_bypass_hosts: Option<Vec<String>>,
pub allowed_private_networks: Option<Vec<String>>,
/// `NapiValue` is not implemented for `u64`
// TODO: i64 -> u64 (NapiValue is not implemented for u64)
pub max_file_size: Option<i64>,
pub access_log: Option<String>,
pub cluster_limits: Option<WorkerConfigInternal>,
@ -298,7 +298,7 @@ fn read_manifest() -> Manifest {
}
#[crate::export]
fn load_config() -> Config {
pub fn load_config() -> Config {
let server_config = read_config_file();
let version = read_meta().version;
let manifest = read_manifest();

View File

@ -49,7 +49,7 @@ fn wildcard(category: Category) -> String {
/// ## Example
///
/// ```
/// use backend_rs::database::cache;
/// # use backend_rs::database::cache;
/// let key = "apple";
/// let data = "I want to cache this string".to_string();
///
@ -85,8 +85,7 @@ pub fn set<V: for<'a> Deserialize<'a> + Serialize>(
/// ## Example
///
/// ```
/// use backend_rs::database::cache;
///
/// # use backend_rs::database::cache;
/// let key = "banana";
/// let data = "I want to cache this string".to_string();
///
@ -121,20 +120,19 @@ pub fn get<V: for<'a> Deserialize<'a> + Serialize>(key: &str) -> Result<Option<V
/// ## Example
///
/// ```
/// use backend_rs::database::cache::{set, get, delete};
///
/// # use backend_rs::database::cache;
/// let key = "chocolate";
/// let value = "I want to cache this string".to_string();
///
/// // set cache
/// set(key, &value, 10).unwrap();
/// cache::set(key, &value, 10).unwrap();
///
/// // delete the cache
/// delete("foo").unwrap();
/// delete("nonexistent").unwrap(); // this is okay
/// cache::delete("foo").unwrap();
/// cache::delete("nonexistent").unwrap(); // this is okay
///
/// // the cache is gone
/// let cached_value = get::<String>("foo").unwrap();
/// let cached_value = cache::get::<String>("foo").unwrap();
/// assert!(cached_value.is_none());
/// ```
pub fn delete(key: &str) -> Result<(), Error> {

View File

@ -1,8 +1,9 @@
use crate::config::CONFIG;
use once_cell::sync::OnceCell;
use sea_orm::{ConnectOptions, Database, DbConn, DbErr};
use tracing::log::LevelFilter;
static DB_CONN: once_cell::sync::OnceCell<DbConn> = once_cell::sync::OnceCell::new();
static DB_CONN: OnceCell<DbConn> = OnceCell::new();
async fn init_database() -> Result<&'static DbConn, DbErr> {
let database_uri = format!(

View File

@ -1,7 +1,8 @@
use crate::config::CONFIG;
use once_cell::sync::OnceCell;
use redis::{Client, Connection, RedisError};
static REDIS_CLIENT: once_cell::sync::OnceCell<Client> = once_cell::sync::OnceCell::new();
static REDIS_CLIENT: OnceCell<Client> = OnceCell::new();
fn init_redis() -> Result<Client, RedisError> {
let redis_url = {
@ -26,7 +27,7 @@ fn init_redis() -> Result<Client, RedisError> {
params.concat()
};
tracing::info!("Initializing Redis connection");
tracing::info!("Initializing Redis client");
Client::open(redis_url)
}
@ -38,8 +39,8 @@ pub fn redis_conn() -> Result<Connection, RedisError> {
}
}
#[inline]
/// prefix redis key
#[inline]
pub fn key(key: impl ToString) -> String {
format!("{}:{}", CONFIG.redis_key_prefix, key.to_string())
}

View File

@ -0,0 +1,105 @@
use std::fmt;
use std::str::FromStr;
#[derive(Debug, PartialEq)]
#[crate::export(object)]
pub struct Acct {
pub username: String,
pub host: Option<String>,
}
impl FromStr for Acct {
type Err = ();
/// This never throw errors. Feel free to `.unwrap()` the result.
fn from_str(value: &str) -> Result<Self, Self::Err> {
let split: Vec<&str> = if let Some(stripped) = value.strip_prefix('@') {
stripped
} else {
value
}
.split('@')
.collect();
Ok(Self {
username: split[0].to_string(),
host: if split.len() == 1 {
None
} else {
Some(split[1].to_string())
},
})
}
}
impl fmt::Display for Acct {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let result = match &self.host {
Some(host) => format!("{}@{}", self.username, host),
None => self.username.clone(),
};
write!(f, "{result}")
}
}
impl From<Acct> for String {
fn from(value: Acct) -> Self {
value.to_string()
}
}
#[crate::ts_only_warn("Use `acct.parse().unwrap()` or `Acct::from_str(acct).unwrap()` instead.")]
#[crate::export]
pub fn string_to_acct(acct: &str) -> Acct {
Acct::from_str(acct).unwrap()
}
#[crate::ts_only_warn("Use `acct.to_string()` instead.")]
#[crate::export]
pub fn acct_to_string(acct: &Acct) -> String {
acct.to_string()
}
#[cfg(test)]
mod unit_test {
use super::Acct;
use pretty_assertions::assert_eq;
use std::str::FromStr;
#[test]
fn test_acct_to_string() {
let remote_acct = Acct {
username: "firefish".to_string(),
host: Some("example.com".to_string()),
};
let local_acct = Acct {
username: "MisakaMikoto".to_string(),
host: None,
};
assert_eq!(remote_acct.to_string(), "firefish@example.com");
assert_ne!(remote_acct.to_string(), "mastodon@example.com");
assert_eq!(local_acct.to_string(), "MisakaMikoto");
assert_ne!(local_acct.to_string(), "ShiraiKuroko");
}
#[test]
fn test_string_to_acct() {
let remote_acct = Acct {
username: "firefish".to_string(),
host: Some("example.com".to_string()),
};
let local_acct = Acct {
username: "MisakaMikoto".to_string(),
host: None,
};
assert_eq!(
Acct::from_str("@firefish@example.com").unwrap(),
remote_acct
);
assert_eq!(Acct::from_str("firefish@example.com").unwrap(), remote_acct);
assert_eq!(Acct::from_str("@MisakaMikoto").unwrap(), local_acct);
assert_eq!(Acct::from_str("MisakaMikoto").unwrap(), local_acct);
}
}

View File

@ -0,0 +1 @@
pub mod acct;

View File

@ -0,0 +1,39 @@
use std::sync::{Mutex, MutexGuard, OnceLock, PoisonError};
use sysinfo::System;
pub type SystemMutexError = PoisonError<MutexGuard<'static, System>>;
// TODO: handle this in a more proper way when we move the entry point to backend-rs
pub fn system() -> Result<MutexGuard<'static, System>, SystemMutexError> {
pub static SYSTEM: OnceLock<Mutex<System>> = OnceLock::new();
SYSTEM.get_or_init(|| Mutex::new(System::new_all())).lock()
}
#[crate::export]
pub fn show_server_info() -> Result<(), SystemMutexError> {
let system_info = system()?;
tracing::info!(
"Hostname: {}",
System::host_name().unwrap_or("unknown".to_string())
);
tracing::info!(
"OS: {}",
System::long_os_version().unwrap_or("unknown".to_string())
);
tracing::info!(
"Kernel: {}",
System::kernel_version().unwrap_or("unknown".to_string())
);
tracing::info!(
"CPU architecture: {}",
System::cpu_arch().unwrap_or("unknown".to_string())
);
tracing::info!("CPU threads: {}", system_info.cpus().len());
tracing::info!("Total memory: {} MiB", system_info.total_memory() / 1048576);
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

@ -0,0 +1,2 @@
pub mod hardware_stats;
pub mod log;

View File

@ -1,7 +1,9 @@
pub use macro_rs::export;
pub use macro_rs::{export, ts_only_warn};
pub mod config;
pub mod database;
pub mod federation;
pub mod init;
pub mod misc;
pub mod model;
pub mod service;

View File

@ -1,74 +0,0 @@
#[derive(Debug, PartialEq)]
#[crate::export(object)]
pub struct Acct {
pub username: String,
pub host: Option<String>,
}
#[crate::export]
pub fn string_to_acct(acct: &str) -> Acct {
let split: Vec<&str> = if let Some(stripped) = acct.strip_prefix('@') {
stripped
} else {
acct
}
.split('@')
.collect();
Acct {
username: split[0].to_string(),
host: if split.len() == 1 {
None
} else {
Some(split[1].to_string())
},
}
}
#[crate::export]
pub fn acct_to_string(acct: &Acct) -> String {
match &acct.host {
Some(host) => format!("{}@{}", acct.username, host),
None => acct.username.clone(),
}
}
#[cfg(test)]
mod unit_test {
use super::{acct_to_string, string_to_acct, Acct};
use pretty_assertions::assert_eq;
#[test]
fn test_acct_to_string() {
let remote_acct = Acct {
username: "firefish".to_string(),
host: Some("example.com".to_string()),
};
let local_acct = Acct {
username: "MisakaMikoto".to_string(),
host: None,
};
assert_eq!(acct_to_string(&remote_acct), "firefish@example.com");
assert_ne!(acct_to_string(&remote_acct), "mastodon@example.com");
assert_eq!(acct_to_string(&local_acct), "MisakaMikoto");
assert_ne!(acct_to_string(&local_acct), "ShiraiKuroko");
}
#[test]
fn test_string_to_acct() {
let remote_acct = Acct {
username: "firefish".to_string(),
host: Some("example.com".to_string()),
};
let local_acct = Acct {
username: "MisakaMikoto".to_string(),
host: None,
};
assert_eq!(string_to_acct("@firefish@example.com"), remote_acct);
assert_eq!(string_to_acct("firefish@example.com"), remote_acct);
assert_eq!(string_to_acct("@MisakaMikoto"), local_acct);
assert_eq!(string_to_acct("MisakaMikoto"), local_acct);
}
}

View File

@ -4,7 +4,7 @@ use once_cell::sync::Lazy;
use regex::Regex;
use sea_orm::{prelude::*, QuerySelect};
/// TODO: handle name collisions better
// TODO: handle name collisions in a better way
#[crate::export(object, js_name = "NoteLikeForCheckWordMute")]
pub struct NoteLike {
pub file_ids: Vec<String>,

View File

@ -1,4 +1,8 @@
/// TODO: handle name collisions better
use serde::{Deserialize, Serialize};
// TODO: handle name collisions in a better way
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, js_name = "NoteLikeForGetNoteSummary")]
pub struct NoteLike {
pub file_ids: Vec<String>,

View File

@ -0,0 +1,90 @@
use crate::init::hardware_stats::{system, SystemMutexError};
use sysinfo::{Disks, MemoryRefreshKind};
// TODO: i64 -> u64 (we can't export u64 to Node.js)
#[crate::export(object)]
pub struct Cpu {
pub model: String,
// TODO: u16 -> usize (we can't export usize to Node.js)
pub cores: u16,
}
#[crate::export(object)]
pub struct Memory {
/// Total memory amount in bytes
pub total: i64,
/// Used memory amount in bytes
pub used: i64,
/// Available (for (re)use) memory amount in bytes
pub available: i64,
}
#[crate::export(object)]
pub struct Storage {
/// Total storage space in bytes
pub total: i64,
/// Used storage space in bytes
pub used: i64,
}
#[crate::export]
pub fn cpu_info() -> Result<Cpu, SystemMutexError> {
let system_info = system()?;
Ok(Cpu {
model: match system_info.cpus() {
[] => {
tracing::debug!("failed to get CPU info");
"unknown".to_string()
}
cpus => cpus[0].brand().to_string(),
},
cores: system_info.cpus().len() as u16,
})
}
#[crate::export]
pub fn cpu_usage() -> Result<f32, SystemMutexError> {
let mut system_info = system()?;
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))
}
#[crate::export]
pub fn memory_usage() -> Result<Memory, SystemMutexError> {
let mut system_info = system()?;
system_info.refresh_memory_specifics(MemoryRefreshKind::new().with_ram());
Ok(Memory {
total: system_info.total_memory() as i64,
used: system_info.used_memory() as i64,
available: system_info.available_memory() as i64,
})
}
#[crate::export]
pub fn storage_usage() -> Option<Storage> {
// Get the first disk that is actualy used.
let disks = Disks::new_with_refreshed_list();
let disk = disks
.iter()
.find(|disk| disk.available_space() > 0 && disk.total_space() > disk.available_space());
if let Some(disk) = disk {
let total = disk.total_space() as i64;
let available = disk.available_space() as i64;
return Some(Storage {
total,
used: total - available,
});
}
tracing::debug!("failed to get stats");
None
}

View File

@ -1,4 +1,3 @@
pub mod acct;
pub mod add_note_to_antenna;
pub mod check_server_block;
pub mod check_word_mute;
@ -8,6 +7,7 @@ pub mod escape_sql;
pub mod format_milliseconds;
pub mod get_image_size;
pub mod get_note_summary;
pub mod hardware_stats;
pub mod is_safe_url;
pub mod latest_version;
pub mod mastodon_id;

View File

@ -1,4 +1,4 @@
pub mod log;
pub mod nodeinfo;
pub mod note;
pub mod push_notification;
pub mod stream;

View File

@ -0,0 +1,232 @@
use crate::database::db_conn;
use crate::misc::get_note_summary::{get_note_summary, NoteLike};
use crate::misc::meta::fetch_meta;
use crate::model::entity::sw_subscription;
use crate::util::http_client;
use once_cell::sync::OnceCell;
use sea_orm::{prelude::*, DbErr};
use web_push::{
ContentEncoding, IsahcWebPushClient, SubscriptionInfo, SubscriptionKeys, VapidSignatureBuilder,
WebPushClient, WebPushError, WebPushMessageBuilder,
};
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Database error: {0}")]
DbErr(#[from] DbErr),
#[error("Web Push error: {0}")]
WebPushErr(#[from] WebPushError),
#[error("Failed to (de)serialize an object: {0}")]
SerializeErr(#[from] serde_json::Error),
#[error("Invalid content: {0}")]
InvalidContentErr(String),
#[error("HTTP client aquisition error: {0}")]
HttpClientErr(#[from] http_client::Error),
}
static CLIENT: OnceCell<IsahcWebPushClient> = OnceCell::new();
fn get_client() -> Result<IsahcWebPushClient, Error> {
Ok(CLIENT
.get_or_try_init(|| http_client::client().map(IsahcWebPushClient::from))
.cloned()?)
}
#[derive(strum::Display, PartialEq)]
#[crate::export(string_enum = "camelCase")]
pub enum PushNotificationKind {
#[strum(serialize = "notification")]
Generic,
#[strum(serialize = "unreadMessagingMessage")]
Chat,
#[strum(serialize = "readAllMessagingMessages")]
ReadAllChats,
#[strum(serialize = "readAllMessagingMessagesOfARoom")]
ReadAllChatsInTheRoom,
#[strum(serialize = "readNotifications")]
ReadNotifications,
#[strum(serialize = "readAllNotifications")]
ReadAllNotifications,
}
fn compact_content(
kind: &PushNotificationKind,
mut content: serde_json::Value,
) -> Result<serde_json::Value, Error> {
if kind != &PushNotificationKind::Generic {
return Ok(content);
}
if !content.is_object() {
return Err(Error::InvalidContentErr("not a JSON object".to_string()));
}
let object = content.as_object_mut().unwrap();
if !object.contains_key("note") {
return Ok(content);
}
let mut note = if object.contains_key("type") && object.get("type").unwrap() == "renote" {
object
.get("note")
.unwrap()
.get("renote")
.ok_or(Error::InvalidContentErr(
"renote object is missing".to_string(),
))?
} else {
object.get("note").unwrap()
}
.clone();
if !note.is_object() {
return Err(Error::InvalidContentErr(
"(re)note is not an object".to_string(),
));
}
let note_like: NoteLike = serde_json::from_value(note.clone())?;
let text = get_note_summary(note_like);
let note_object = note.as_object_mut().unwrap();
note_object.remove("reply");
note_object.remove("renote");
note_object.remove("user");
note_object.insert("text".to_string(), text.into());
object.insert("note".to_string(), note);
Ok(serde_json::from_value(Json::Object(object.clone()))?)
}
async fn handle_web_push_failure(
db: &DatabaseConnection,
err: WebPushError,
subscription_id: &str,
error_message: &str,
) -> Result<(), DbErr> {
match err {
WebPushError::BadRequest(_)
| WebPushError::ServerError(_)
| WebPushError::InvalidUri
| WebPushError::EndpointNotValid
| WebPushError::EndpointNotFound
| WebPushError::TlsError
| WebPushError::SslError
| WebPushError::InvalidPackageName
| WebPushError::MissingCryptoKeys
| WebPushError::InvalidCryptoKeys
| WebPushError::InvalidResponse => {
sw_subscription::Entity::delete_by_id(subscription_id)
.exec(db)
.await?;
tracing::info!("{}; {} was unsubscribed", error_message, subscription_id);
tracing::debug!("reason: {:#?}", err);
}
_ => {
tracing::warn!("{}; subscription id: {}", error_message, subscription_id);
tracing::info!("reason: {:#?}", err);
}
};
Ok(())
}
#[crate::export]
pub async fn send_push_notification(
receiver_user_id: &str,
kind: PushNotificationKind,
content: &serde_json::Value,
) -> Result<(), Error> {
let meta = fetch_meta(true).await?;
if !meta.enable_service_worker || meta.sw_public_key.is_none() || meta.sw_private_key.is_none()
{
return Ok(());
}
let db = db_conn().await?;
let signature_builder = VapidSignatureBuilder::from_base64_no_sub(
meta.sw_private_key.unwrap().as_str(),
web_push::URL_SAFE_NO_PAD,
)?;
let subscriptions = sw_subscription::Entity::find()
.filter(sw_subscription::Column::UserId.eq(receiver_user_id))
.all(db)
.await?;
let payload = format!(
"{{\"type\":\"{}\",\"userId\":\"{}\",\"dateTime\":{},\"body\":{}}}",
kind,
receiver_user_id,
chrono::Utc::now().timestamp_millis(),
serde_json::to_string(&compact_content(&kind, content.clone())?)?
);
tracing::trace!("payload: {:#?}", payload);
for subscription in subscriptions.iter() {
if !subscription.send_read_message
&& [
PushNotificationKind::ReadAllChats,
PushNotificationKind::ReadAllChatsInTheRoom,
PushNotificationKind::ReadAllNotifications,
PushNotificationKind::ReadNotifications,
]
.contains(&kind)
{
continue;
}
let subscription_info = SubscriptionInfo {
endpoint: subscription.endpoint.to_owned(),
keys: SubscriptionKeys {
// convert standard base64 into base64url
// https://en.wikipedia.org/wiki/Base64#Variants_summary_table
p256dh: subscription
.publickey
.replace('+', "-")
.replace('/', "_")
.to_owned(),
auth: subscription
.auth
.replace('+', "-")
.replace('/', "_")
.to_owned(),
},
};
let signature = signature_builder
.clone()
.add_sub_info(&subscription_info)
.build();
if let Err(err) = signature {
handle_web_push_failure(db, err, &subscription.id, "failed to build a signature")
.await?;
continue;
}
let mut message_builder = WebPushMessageBuilder::new(&subscription_info);
message_builder.set_ttl(1000);
message_builder.set_payload(ContentEncoding::Aes128Gcm, payload.as_bytes());
message_builder.set_vapid_signature(signature.unwrap());
let message = message_builder.build();
if let Err(err) = message {
handle_web_push_failure(db, err, &subscription.id, "failed to build a payload").await?;
continue;
}
if let Err(err) = get_client()?.send(message.unwrap()).await {
handle_web_push_failure(db, err, &subscription.id, "failed to send").await?;
continue;
}
tracing::debug!("success; subscription id: {}", subscription.id);
}
Ok(())
}

View File

@ -1,7 +1,8 @@
use rand::{distributions::Alphanumeric, thread_rng, Rng};
/// Generate random string based on [thread_rng] and [Alphanumeric].
pub fn gen_string(length: u16) -> String {
#[crate::export]
pub fn generate_secure_random_string(length: u16) -> String {
thread_rng()
.sample_iter(Alphanumeric)
.take(length.into())
@ -9,9 +10,9 @@ pub fn gen_string(length: u16) -> String {
.collect()
}
#[crate::export(js_name = "secureRndstr")]
pub fn native_random_str(length: Option<u16>) -> String {
gen_string(length.unwrap_or(32))
#[crate::export]
pub fn generate_user_token() -> String {
generate_secure_random_string(16)
}
#[cfg(test)]
@ -19,14 +20,17 @@ mod unit_test {
use pretty_assertions::{assert_eq, assert_ne};
use std::thread;
use super::gen_string;
use super::generate_secure_random_string;
#[test]
fn can_generate_unique_strings() {
assert_eq!(gen_string(16).len(), 16);
assert_ne!(gen_string(16), gen_string(16));
let s1 = thread::spawn(|| gen_string(16));
let s2 = thread::spawn(|| gen_string(16));
assert_eq!(generate_secure_random_string(16).len(), 16);
assert_ne!(
generate_secure_random_string(16),
generate_secure_random_string(16)
);
let s1 = thread::spawn(|| generate_secure_random_string(16));
let s2 = thread::spawn(|| generate_secure_random_string(16));
assert_ne!(s1.join().unwrap(), s2.join().unwrap());
}
}

View File

@ -22,54 +22,55 @@
"@swc/core-android-arm64": "1.3.11"
},
"dependencies": {
"@bull-board/api": "5.16.0",
"@bull-board/koa": "5.16.0",
"@bull-board/ui": "5.16.0",
"@discordapp/twemoji": "^15.0.3",
"@bull-board/api": "5.17.1",
"@bull-board/koa": "5.17.1",
"@bull-board/ui": "5.17.1",
"@discordapp/twemoji": "15.0.3",
"@koa/cors": "5.0.0",
"@koa/multer": "3.0.2",
"@koa/router": "12.0.1",
"@ladjs/koa-views": "9.0.0",
"@peertube/http-signature": "1.7.0",
"@redocly/openapi-core": "1.12.0",
"@redocly/openapi-core": "1.12.2",
"@sinonjs/fake-timers": "11.2.2",
"adm-zip": "0.5.10",
"ajv": "8.12.0",
"ajv": "8.13.0",
"archiver": "7.0.1",
"aws-sdk": "2.1608.0",
"axios": "^1.6.8",
"aws-sdk": "2.1621.0",
"axios": "1.6.8",
"backend-rs": "workspace:*",
"blurhash": "2.0.5",
"bull": "4.12.2",
"bull": "4.12.4",
"cacheable-lookup": "TheEssem/cacheable-lookup",
"cbor-x": "^1.5.9",
"cbor-x": "1.5.9",
"chalk": "5.3.0",
"chalk-template": "1.1.0",
"cli-highlight": "2.1.11",
"color-convert": "2.0.1",
"content-disposition": "0.5.4",
"date-fns": "3.6.0",
"decompress": "^4.2.1",
"decompress": "4.2.1",
"deep-email-validator": "0.1.21",
"deepl-node": "1.13.0",
"escape-regexp": "0.0.1",
"feed": "4.2.2",
"file-type": "19.0.0",
"firefish-js": "workspace:*",
"fluent-ffmpeg": "2.1.2",
"form-data": "^4.0.0",
"form-data": "4.0.0",
"got": "14.2.1",
"gunzip-maybe": "^1.4.2",
"happy-dom": "^14.7.1",
"gunzip-maybe": "1.4.2",
"hpagent": "1.2.0",
"ioredis": "5.4.1",
"ip-cidr": "4.0.0",
"is-svg": "5.0.0",
"is-svg": "5.0.1",
"jsdom": "24.0.0",
"json5": "2.2.3",
"jsonld": "8.3.2",
"jsrsasign": "11.1.0",
"katex": "0.16.10",
"koa": "2.15.3",
"koa-body": "^6.0.1",
"koa-body": "6.0.1",
"koa-bodyparser": "4.4.1",
"koa-favicon": "2.1.0",
"koa-json-body": "5.3.0",
@ -81,14 +82,13 @@
"megalodon": "workspace:*",
"mfm-js": "0.24.0",
"mime-types": "2.1.35",
"msgpackr": "^1.10.1",
"msgpackr": "1.10.2",
"multer": "1.4.5-lts.1",
"nested-property": "4.0.0",
"node-fetch": "3.3.2",
"nodemailer": "6.9.13",
"opencc-js": "^1.0.5",
"os-utils": "0.0.14",
"otpauth": "^9.2.3",
"opencc-js": "1.0.5",
"otpauth": "9.2.4",
"parse5": "7.1.2",
"pg": "8.11.5",
"private-ip": "3.0.2",
@ -106,33 +106,32 @@
"rndstr": "1.0.0",
"rss-parser": "3.13.0",
"sanitize-html": "2.13.0",
"semver": "7.6.0",
"semver": "7.6.2",
"sharp": "0.33.3",
"stringz": "2.1.0",
"summaly": "2.7.0",
"syslog-pro": "1.0.0",
"systeminformation": "5.22.7",
"tar-stream": "^3.1.7",
"tesseract.js": "^5.0.5",
"tar-stream": "3.1.7",
"tesseract.js": "5.1.0",
"tinycolor2": "1.6.0",
"tmp": "0.2.3",
"typeorm": "0.3.20",
"ulid": "2.3.0",
"uuid": "9.0.1",
"web-push": "3.6.7",
"websocket": "1.0.34",
"websocket": "1.0.35",
"xev": "3.0.2"
},
"devDependencies": {
"@swc/cli": "0.3.12",
"@swc/core": "1.5.0",
"@types/adm-zip": "^0.5.5",
"@types/color-convert": "^2.0.3",
"@types/content-disposition": "^0.5.8",
"@swc/core": "1.5.7",
"@types/adm-zip": "0.5.5",
"@types/color-convert": "2.0.3",
"@types/content-disposition": "0.5.8",
"@types/escape-regexp": "0.0.3",
"@types/fluent-ffmpeg": "2.1.24",
"@types/jsdom": "21.1.6",
"@types/jsonld": "1.5.13",
"@types/jsrsasign": "10.5.13",
"@types/jsrsasign": "10.5.14",
"@types/katex": "0.16.7",
"@types/koa": "2.15.0",
"@types/koa-bodyparser": "4.3.12",
@ -145,13 +144,13 @@
"@types/koa__multer": "2.0.7",
"@types/koa__router": "12.0.4",
"@types/mocha": "10.0.6",
"@types/node": "20.12.7",
"@types/node": "20.12.12",
"@types/node-fetch": "2.6.11",
"@types/nodemailer": "6.4.14",
"@types/nodemailer": "6.4.15",
"@types/oauth": "0.9.4",
"@types/opencc-js": "^1.0.3",
"@types/pg": "^8.11.5",
"@types/probe-image-size": "^7.2.4",
"@types/opencc-js": "1.0.3",
"@types/pg": "8.11.6",
"@types/probe-image-size": "7.2.4",
"@types/pug": "2.0.10",
"@types/punycode": "2.1.4",
"@types/qrcode": "1.5.5",
@ -162,7 +161,7 @@
"@types/sanitize-html": "2.11.0",
"@types/semver": "7.5.8",
"@types/sinonjs__fake-timers": "8.1.5",
"@types/syslog-pro": "^1.0.3",
"@types/syslog-pro": "1.0.3",
"@types/tinycolor2": "1.4.6",
"@types/tmp": "0.2.6",
"@types/uuid": "9.0.8",
@ -170,17 +169,17 @@
"@types/websocket": "1.0.10",
"@types/ws": "8.5.10",
"cross-env": "7.0.3",
"eslint": "^9.1.1",
"eslint": "9.2.0",
"mocha": "10.4.0",
"pug": "3.0.2",
"strict-event-emitter-types": "2.0.0",
"swc-loader": "^0.2.6",
"swc-loader": "0.2.6",
"ts-loader": "9.5.1",
"ts-node": "10.9.2",
"tsconfig-paths": "4.2.0",
"type-fest": "4.17.0",
"type-fest": "4.18.2",
"typescript": "5.4.5",
"webpack": "^5.91.0",
"ws": "8.16.0"
"webpack": "5.91.0",
"ws": "8.17.0"
}
}

View File

@ -1,33 +0,0 @@
declare module "os-utils" {
type FreeCommandCallback = (usedmem: number) => void;
type HarddriveCallback = (total: number, free: number, used: number) => void;
type GetProcessesCallback = (result: string) => void;
type CPUCallback = (perc: number) => void;
export function platform(): NodeJS.Platform;
export function cpuCount(): number;
export function sysUptime(): number;
export function processUptime(): number;
export function freemem(): number;
export function totalmem(): number;
export function freememPercentage(): number;
export function freeCommand(callback: FreeCommandCallback): void;
export function harddrive(callback: HarddriveCallback): void;
export function getProcesses(callback: GetProcessesCallback): void;
export function getProcesses(
nProcess: number,
callback: GetProcessesCallback,
): void;
export function allLoadavg(): string;
export function loadavg(_time?: number): number;
export function cpuFree(callback: CPUCallback): void;
export function cpuUsage(callback: CPUCallback): void;
}

View File

@ -8,11 +8,14 @@ import chalkTemplate from "chalk-template";
import semver from "semver";
import Logger from "@/services/logger.js";
import type { Config } from "backend-rs";
import { initializeRustLogger } from "backend-rs";
import { fetchMeta, removeOldAttestationChallenges } from "backend-rs";
import {
fetchMeta,
initializeRustLogger,
removeOldAttestationChallenges,
showServerInfo,
type Config,
} from "backend-rs";
import { config, envOption } from "@/config.js";
import { showMachineInfo } from "@/misc/show-machine-info.js";
import { db, initDb } from "@/db/postgre.js";
import { inspect } from "node:util";
@ -90,12 +93,12 @@ function greet() {
export async function masterMain() {
// initialize app
try {
initializeRustLogger();
greet();
showEnvironment();
await showMachineInfo(bootLogger);
showServerInfo();
showNodejsVersion();
await connectDb();
initializeRustLogger();
} catch (e) {
bootLogger.error(
`Fatal error occurred during initialization:\n${inspect(e)}`,

View File

@ -1,15 +1,8 @@
import si from "systeminformation";
import Xev from "xev";
import * as osUtils from "os-utils";
import { fetchMeta } from "backend-rs";
import { fetchMeta, cpuUsage, memoryUsage } from "backend-rs";
const ev = new Xev();
const interval = 2000;
const roundCpu = (num: number) => Math.round(num * 1000) / 1000;
const round = (num: number) => Math.round(num * 10) / 10;
/**
* Report server stats regularly
*/
@ -24,26 +17,9 @@ export default async function () {
if (!meta.enableServerMachineStats) return;
async function tick() {
const cpu = await cpuUsage();
const memStats = await mem();
const netStats = await net();
const fsStats = await fs();
const stats = {
cpu: roundCpu(cpu),
mem: {
used: round(memStats.used - memStats.buffers - memStats.cached),
active: round(memStats.active),
total: round(memStats.total),
},
net: {
rx: round(Math.max(0, netStats.rx_sec)),
tx: round(Math.max(0, netStats.tx_sec)),
},
fs: {
r: round(Math.max(0, fsStats.rIO_sec ?? 0)),
w: round(Math.max(0, fsStats.wIO_sec ?? 0)),
},
cpu: cpuUsage(),
mem: memoryUsage(),
};
ev.emit("serverStats", stats);
log.unshift(stats);
@ -52,33 +28,5 @@ export default async function () {
tick();
setInterval(tick, interval);
}
// CPU STAT
function cpuUsage(): Promise<number> {
return new Promise((res, rej) => {
osUtils.cpuUsage((cpuUsage) => {
res(cpuUsage);
});
});
}
// MEMORY STAT
async function mem() {
const data = await si.mem();
return data;
}
// NETWORK STAT
async function net() {
const iface = await si.networkInterfaceDefault();
const data = await si.networkStats(iface);
return data[0];
}
// FS STAT
async function fs() {
const data = await si.disksIO().catch(() => ({ rIO_sec: 0, wIO_sec: 0 }));
return data || { rIO_sec: 0, wIO_sec: 0 };
setInterval(tick, 3000);
}

View File

@ -1,21 +1,17 @@
import { type HTMLElement, Window } from "happy-dom";
import { JSDOM } from "jsdom";
import type * as mfm from "mfm-js";
import katex from "katex";
import { config } from "@/config.js";
import { intersperse } from "@/prelude/array.js";
import type { IMentionedRemoteUsers } from "@/models/entities/note.js";
function toMathMl(code: string, displayMode: boolean): HTMLElement | null {
const { window } = new Window();
const document = window.document;
document.body.innerHTML = katex.renderToString(code, {
function toMathMl(code: string, displayMode: boolean): MathMLElement | null {
const rendered = katex.renderToString(code, {
throwOnError: false,
output: "mathml",
displayMode,
});
return document.querySelector("math");
return JSDOM.fragment(rendered).querySelector("math");
}
export function toHtml(
@ -26,7 +22,7 @@ export function toHtml(
return null;
}
const { window } = new Window();
const { window } = new JSDOM("");
const doc = window.document;

View File

@ -1,234 +1,7 @@
import {
packedUserLiteSchema,
packedUserDetailedNotMeOnlySchema,
packedMeDetailedOnlySchema,
packedUserDetailedNotMeSchema,
packedMeDetailedSchema,
packedUserDetailedSchema,
packedUserSchema,
} from "@/models/schema/user.js";
import { packedNoteSchema } from "@/models/schema/note.js";
import { packedUserListSchema } from "@/models/schema/user-list.js";
import { packedAppSchema } from "@/models/schema/app.js";
import { packedMessagingMessageSchema } from "@/models/schema/messaging-message.js";
import { packedNotificationSchema } from "@/models/schema/notification.js";
import { packedDriveFileSchema } from "@/models/schema/drive-file.js";
import { packedDriveFolderSchema } from "@/models/schema/drive-folder.js";
import { packedFollowingSchema } from "@/models/schema/following.js";
import { packedMutingSchema } from "@/models/schema/muting.js";
import { packedRenoteMutingSchema } from "@/models/schema/renote-muting.js";
import { packedReplyMutingSchema } from "@/models/schema/reply-muting.js";
import { packedBlockingSchema } from "@/models/schema/blocking.js";
import { packedNoteReactionSchema } from "@/models/schema/note-reaction.js";
import { packedHashtagSchema } from "@/models/schema/hashtag.js";
import { packedPageSchema } from "@/models/schema/page.js";
import { packedUserGroupSchema } from "@/models/schema/user-group.js";
import { packedNoteFavoriteSchema } from "@/models/schema/note-favorite.js";
import { packedChannelSchema } from "@/models/schema/channel.js";
import { packedAntennaSchema } from "@/models/schema/antenna.js";
import { packedClipSchema } from "@/models/schema/clip.js";
import { packedFederationInstanceSchema } from "@/models/schema/federation-instance.js";
import { packedQueueCountSchema } from "@/models/schema/queue.js";
import { packedGalleryPostSchema } from "@/models/schema/gallery-post.js";
import { packedEmojiSchema } from "@/models/schema/emoji.js";
import { packedNoteEdit } from "@/models/schema/note-edit.js";
import { packedNoteFileSchema } from "@/models/schema/note-file.js";
import { packedAbuseUserReportSchema } from "@/models/schema/abuse-user-report.js";
// TODO: use firefish-js
import { Schema as _Schema } from "firefish-js";
export const refs = {
AbuseUserReport: packedAbuseUserReportSchema,
UserLite: packedUserLiteSchema,
UserDetailedNotMeOnly: packedUserDetailedNotMeOnlySchema,
MeDetailedOnly: packedMeDetailedOnlySchema,
UserDetailedNotMe: packedUserDetailedNotMeSchema,
MeDetailed: packedMeDetailedSchema,
UserDetailed: packedUserDetailedSchema,
User: packedUserSchema,
UserList: packedUserListSchema,
UserGroup: packedUserGroupSchema,
App: packedAppSchema,
MessagingMessage: packedMessagingMessageSchema,
Note: packedNoteSchema,
NoteFile: packedNoteFileSchema,
NoteEdit: packedNoteEdit,
NoteReaction: packedNoteReactionSchema,
NoteFavorite: packedNoteFavoriteSchema,
Notification: packedNotificationSchema,
DriveFile: packedDriveFileSchema,
DriveFolder: packedDriveFolderSchema,
Following: packedFollowingSchema,
Muting: packedMutingSchema,
RenoteMuting: packedRenoteMutingSchema,
ReplyMuting: packedReplyMutingSchema,
Blocking: packedBlockingSchema,
Hashtag: packedHashtagSchema,
Page: packedPageSchema,
Channel: packedChannelSchema,
QueueCount: packedQueueCountSchema,
Antenna: packedAntennaSchema,
Clip: packedClipSchema,
FederationInstance: packedFederationInstanceSchema,
GalleryPost: packedGalleryPostSchema,
Emoji: packedEmojiSchema,
};
export type Packed<x extends keyof typeof refs> = SchemaType<(typeof refs)[x]>;
type TypeStringef =
| "null"
| "boolean"
| "integer"
| "number"
| "string"
| "array"
| "object"
| "any";
type StringDefToType<T extends TypeStringef> = T extends "null"
? null
: T extends "boolean"
? boolean
: T extends "integer"
? number
: T extends "number"
? number
: T extends "string"
? string | Date
: T extends "array"
? ReadonlyArray<any>
: T extends "object"
? Record<string, any>
: any;
// https://swagger.io/specification/?sbsearch=optional#schema-object
type OfSchema = {
readonly anyOf?: ReadonlyArray<Schema>;
readonly oneOf?: ReadonlyArray<Schema>;
readonly allOf?: ReadonlyArray<Schema>;
};
export interface Schema extends OfSchema {
readonly type?: TypeStringef;
readonly nullable?: boolean;
readonly optional?: boolean;
readonly items?: Schema;
readonly properties?: Obj;
readonly required?: ReadonlyArray<
Extract<keyof NonNullable<this["properties"]>, string>
>;
readonly description?: string;
readonly example?: any;
readonly format?: string;
readonly ref?: keyof typeof refs;
readonly enum?: ReadonlyArray<string>;
readonly default?:
| (this["type"] extends TypeStringef ? StringDefToType<this["type"]> : any)
| null;
readonly maxLength?: number;
readonly minLength?: number;
readonly maximum?: number;
readonly minimum?: number;
readonly pattern?: string;
}
type RequiredPropertyNames<s extends Obj> = {
[K in keyof s]: // K is not optional
s[K]["optional"] extends false
? K
: // K has default value
s[K]["default"] extends
| null
| string
| number
| boolean
| Record<string, unknown>
? K
: never;
}[keyof s];
export type Obj = Record<string, Schema>;
// https://github.com/misskey-dev/misskey/issues/8535
// To avoid excessive stack depth error,
// deceive TypeScript with UnionToIntersection (or more precisely, `infer` expression within it).
export type ObjType<
s extends Obj,
RequiredProps extends keyof s,
> = UnionToIntersection<
{
-readonly [R in RequiredPropertyNames<s>]-?: SchemaType<s[R]>;
} & {
-readonly [R in RequiredProps]-?: SchemaType<s[R]>;
} & {
-readonly [P in keyof s]?: SchemaType<s[P]>;
}
>;
type NullOrUndefined<p extends Schema, T> =
| (p["nullable"] extends true ? null : never)
| (p["optional"] extends true ? undefined : never)
| T;
// https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection
// Get intersection from union
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I,
) => void
? I
: never;
// https://github.com/misskey-dev/misskey/pull/8144#discussion_r785287552
// To get union, we use `Foo extends any ? Hoge<Foo> : never`
type UnionSchemaType<
a extends readonly any[],
X extends Schema = a[number],
> = X extends any ? SchemaType<X> : never;
type ArrayUnion<T> = T extends any ? Array<T> : never;
export type SchemaTypeDef<p extends Schema> = p["type"] extends "null"
? null
: p["type"] extends "integer"
? number
: p["type"] extends "number"
? number
: p["type"] extends "string"
? p["enum"] extends readonly string[]
? p["enum"][number]
: p["format"] extends "date-time"
? string
: // Dateにする
string
: p["type"] extends "boolean"
? boolean
: p["type"] extends "object"
? p["ref"] extends keyof typeof refs
? Packed<p["ref"]>
: p["properties"] extends NonNullable<Obj>
? ObjType<p["properties"], NonNullable<p["required"]>[number]>
: p["anyOf"] extends ReadonlyArray<Schema>
? UnionSchemaType<p["anyOf"]> &
Partial<UnionToIntersection<UnionSchemaType<p["anyOf"]>>>
: p["allOf"] extends ReadonlyArray<Schema>
? UnionToIntersection<UnionSchemaType<p["allOf"]>>
: any
: p["type"] extends "array"
? p["items"] extends OfSchema
? p["items"]["anyOf"] extends ReadonlyArray<Schema>
? UnionSchemaType<NonNullable<p["items"]["anyOf"]>>[]
: p["items"]["oneOf"] extends ReadonlyArray<Schema>
? ArrayUnion<
UnionSchemaType<NonNullable<p["items"]["oneOf"]>>
>
: p["items"]["allOf"] extends ReadonlyArray<Schema>
? UnionToIntersection<
UnionSchemaType<NonNullable<p["items"]["allOf"]>>
>[]
: never
: p["items"] extends NonNullable<Schema>
? SchemaTypeDef<p["items"]>[]
: any[]
: p["oneOf"] extends ReadonlyArray<Schema>
? UnionSchemaType<p["oneOf"]>
: any;
export type SchemaType<p extends Schema> = NullOrUndefined<p, SchemaTypeDef<p>>;
export const refs = _Schema.refs;
export type Packed<T extends keyof typeof refs> = _Schema.Packed<T>;
export type Schema = _Schema.Schema;
export type SchemaType<P extends _Schema.Schema> = _Schema.SchemaType<P>;

View File

@ -1,17 +0,0 @@
import * as os from "node:os";
import sysUtils from "systeminformation";
import type Logger from "@/services/logger.js";
export async function showMachineInfo(parentLogger: Logger) {
const logger = parentLogger.createSubLogger("machine");
logger.debug(`Hostname: ${os.hostname()}`);
logger.debug(`Platform: ${process.platform} Arch: ${process.arch}`);
const mem = await sysUtils.mem();
const totalmem = (mem.total / 1024 / 1024 / 1024).toFixed(1);
const availmem = (mem.available / 1024 / 1024 / 1024).toFixed(1);
logger.debug(
`CPU: ${
os.cpus().length
} core MEM: ${totalmem}GB (available: ${availmem}GB)`,
);
}

View File

@ -1,8 +1,7 @@
import { Brackets } from "typeorm";
import { isBlockedServer } from "backend-rs";
import { isBlockedServer, DAY } from "backend-rs";
import { Instances } from "@/models/index.js";
import type { Instance } from "@/models/entities/instance.js";
import { DAY } from "backend-rs";
// Threshold from last contact after which an instance will be considered
// "dead" and should no longer get activities delivered to it.

View File

@ -3,6 +3,7 @@ import {
JoinColumn,
Column,
ManyToOne,
OneToOne,
PrimaryColumn,
Index,
type Relation,
@ -31,7 +32,7 @@ export class ScheduledNoteCreation {
public scheduledAt: Date;
//#region Relations
@ManyToOne(() => Note, {
@OneToOne(() => Note, {
onDelete: "CASCADE",
})
@JoinColumn()

View File

@ -5,8 +5,12 @@ import perform from "@/remote/activitypub/perform.js";
import Logger from "@/services/logger.js";
import { registerOrFetchInstanceDoc } from "@/services/register-or-fetch-instance-doc.js";
import { Instances } from "@/models/index.js";
import { isAllowedServer, isBlockedServer } from "backend-rs";
import { toPuny, extractHost } from "backend-rs";
import {
extractHost,
isAllowedServer,
isBlockedServer,
toPuny,
} from "backend-rs";
import { getApId } from "@/remote/activitypub/type.js";
import { fetchInstanceMetadata } from "@/services/fetch-instance-metadata.js";
import type { InboxJobData } from "../types.js";

View File

@ -1,11 +1,15 @@
import { URL } from "url";
import { URL } from "node:url";
import httpSignature, { type IParsedSignature } from "@peertube/http-signature";
import { config } from "@/config.js";
import { fetchMeta, isAllowedServer, isBlockedServer } from "backend-rs";
import { toPuny } from "backend-rs";
import {
fetchMeta,
isAllowedServer,
isBlockedServer,
toPuny,
} from "backend-rs";
import DbResolver from "@/remote/activitypub/db-resolver.js";
import { getApId } from "@/remote/activitypub/type.js";
import type { IncomingMessage } from "http";
import type { IncomingMessage } from "node:http";
import type { CacheableRemoteUser } from "@/models/entities/user.js";
import type { UserPublickey } from "@/models/entities/user-publickey.js";
import { verify } from "node:crypto";

View File

@ -5,12 +5,11 @@ import type { IAnnounce } from "../../type.js";
import { getApId } from "../../type.js";
import { fetchNote, resolveNote } from "../../models/note.js";
import { apLogger } from "../../logger.js";
import { extractHost } from "backend-rs";
import { extractHost, isBlockedServer } from "backend-rs";
import { getApLock } from "@/misc/app-lock.js";
import { parseAudience } from "../../audience.js";
import { StatusError } from "@/misc/fetch.js";
import { Notes } from "@/models/index.js";
import { isBlockedServer } from "backend-rs";
import { inspect } from "node:util";
/**

View File

@ -16,7 +16,9 @@ import type { DriveFile } from "@/models/entities/drive-file.js";
import {
type ImageSize,
extractHost,
genId,
getImageSizeFromUrl,
isBlockedServer,
isSameOrigin,
toPuny,
} from "backend-rs";
@ -39,7 +41,6 @@ import {
getApType,
} from "../type.js";
import type { Emoji } from "@/models/entities/emoji.js";
import { genId, isBlockedServer } from "backend-rs";
import { getApLock } from "@/misc/app-lock.js";
import { createMessage } from "@/services/messages/create.js";
import { parseAudience } from "../audience.js";
@ -52,7 +53,7 @@ import { UserProfiles } from "@/models/index.js";
import { In } from "typeorm";
import { config } from "@/config.js";
import { truncate } from "@/misc/truncate.js";
import { langmap } from "@/misc/langmap.js";
import { langmap } from "firefish-js";
import { inspect } from "node:util";
export function validateNote(object: any, uri: string) {

View File

@ -16,10 +16,9 @@ import type { IRemoteUser, CacheableUser } from "@/models/entities/user.js";
import { User } from "@/models/entities/user.js";
import type { Emoji } from "@/models/entities/emoji.js";
import { UserNotePining } from "@/models/entities/user-note-pining.js";
import { genId } from "backend-rs";
import { genId, isSameOrigin, toPuny } from "backend-rs";
import { UserPublickey } from "@/models/entities/user-publickey.js";
import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js";
import { isSameOrigin, toPuny } from "backend-rs";
import { UserProfile } from "@/models/entities/user-profile.js";
import { toArray } from "@/prelude/array.js";
import { fetchInstanceMetadata } from "@/services/fetch-instance-metadata.js";

View File

@ -1,3 +0,0 @@
import { secureRndstr } from "backend-rs";
export default () => secureRndstr(16);

View File

@ -3,10 +3,11 @@ import {
publishToChatStream,
publishToGroupChatStream,
publishToChatIndexStream,
sendPushNotification,
ChatEvent,
ChatIndexEvent,
PushNotificationKind,
} from "backend-rs";
import { pushNotification } from "@/services/push-notification.js";
import type { User, IRemoteUser } from "@/models/entities/user.js";
import type { MessagingMessage } from "@/models/entities/messaging-message.js";
import { MessagingMessages, UserGroupJoinings, Users } from "@/models/index.js";
@ -62,20 +63,19 @@ export async function readUserMessagingMessage(
if (!(await Users.getHasUnreadMessagingMessage(userId))) {
// 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行
publishMainStream(userId, "readAllMessagingMessages");
pushNotification(userId, "readAllMessagingMessages", undefined);
sendPushNotification(userId, PushNotificationKind.ReadAllChats, {});
} else {
// そのユーザーとのメッセージで未読がなければイベント発行
const count = await MessagingMessages.count({
const hasUnread = await MessagingMessages.exists({
where: {
userId: otherpartyId,
recipientId: userId,
isRead: false,
},
take: 1,
});
if (!count) {
pushNotification(userId, "readAllMessagingMessagesOfARoom", {
if (!hasUnread) {
sendPushNotification(userId, PushNotificationKind.ReadAllChatsInTheRoom, {
userId: otherpartyId,
});
}
@ -137,10 +137,10 @@ export async function readGroupMessagingMessage(
if (!(await Users.getHasUnreadMessagingMessage(userId))) {
// 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行
publishMainStream(userId, "readAllMessagingMessages");
pushNotification(userId, "readAllMessagingMessages", undefined);
sendPushNotification(userId, PushNotificationKind.ReadAllChats, {});
} else {
// そのグループにおいて未読がなければイベント発行
const unreadExist = await MessagingMessages.createQueryBuilder("message")
const hasUnread = await MessagingMessages.createQueryBuilder("message")
.where("message.groupId = :groupId", { groupId: groupId })
.andWhere("message.userId != :userId", { userId: userId })
.andWhere("NOT (:userId = ANY(message.reads))", { userId: userId })
@ -150,8 +150,10 @@ export async function readGroupMessagingMessage(
.getOne()
.then((x) => x != null);
if (!unreadExist) {
pushNotification(userId, "readAllMessagingMessagesOfARoom", { groupId });
if (!hasUnread) {
sendPushNotification(userId, PushNotificationKind.ReadAllChatsInTheRoom, {
groupId,
});
}
}
}

View File

@ -1,6 +1,6 @@
import { In } from "typeorm";
import { publishMainStream } from "@/services/stream.js";
import { pushNotification } from "@/services/push-notification.js";
import { sendPushNotification, PushNotificationKind } from "backend-rs";
import type { User } from "@/models/entities/user.js";
import type { Notification } from "@/models/entities/notification.js";
import { Notifications, Users } from "@/models/index.js";
@ -47,7 +47,11 @@ export async function readNotificationByQuery(
function postReadAllNotifications(userId: User["id"]) {
publishMainStream(userId, "readAllNotifications");
return pushNotification(userId, "readAllNotifications", undefined);
return sendPushNotification(
userId,
PushNotificationKind.ReadAllNotifications,
{},
);
}
function postReadNotifications(
@ -55,5 +59,7 @@ function postReadNotifications(
notificationIds: Notification["id"][],
) {
publishMainStream(userId, "readNotifications", notificationIds);
return pushNotification(userId, "readNotifications", { notificationIds });
return sendPushNotification(userId, PushNotificationKind.ReadNotifications, {
notificationIds,
});
}

View File

@ -1,10 +1,9 @@
import { generateKeyPair } from "node:crypto";
import generateUserToken from "./generate-native-user-token.js";
import { User } from "@/models/entities/user.js";
import { Users, UsedUsernames } from "@/models/index.js";
import { UserProfile } from "@/models/entities/user-profile.js";
import { IsNull } from "typeorm";
import { genId, hashPassword, toPuny } from "backend-rs";
import { genId, generateUserToken, hashPassword, toPuny } from "backend-rs";
import { UserKeypair } from "@/models/entities/user-keypair.js";
import { UsedUsername } from "@/models/entities/used-username.js";
import { db } from "@/db/postgre.js";

View File

@ -1,8 +1,8 @@
import * as os from "node:os";
import si from "systeminformation";
import define from "@/server/api/define.js";
import { redisClient } from "@/db/redis.js";
import { db } from "@/db/postgre.js";
import { cpuInfo, memoryUsage, storageUsage } from "backend-rs";
export const meta = {
requireCredential: true,
@ -85,19 +85,6 @@ export const meta = {
},
},
},
net: {
type: "object",
optional: false,
nullable: false,
properties: {
interface: {
type: "string",
optional: false,
nullable: false,
example: "eth0",
},
},
},
},
},
} as const;
@ -109,13 +96,10 @@ export const paramDef = {
} as const;
export default define(meta, paramDef, async () => {
const memStats = await si.mem();
const fsStats = await si.fsSize();
const netInterface = await si.networkInterfaceDefault();
const redisServerInfo = await redisClient.info("Server");
const m = redisServerInfo.match(new RegExp("^redis_version:(.*)", "m"));
const m = redisServerInfo.match(/^redis_version:(.*)/m);
const redis_version = m?.[1];
const storage = storageUsage();
return {
machine: os.hostname(),
@ -125,19 +109,13 @@ export default define(meta, paramDef, async () => {
.query("SHOW server_version")
.then((x) => x[0].server_version),
redis: redis_version,
cpu: {
model: os.cpus()[0].model,
cores: os.cpus().length,
},
cpu: cpuInfo(),
mem: {
total: memStats.total,
total: memoryUsage().total,
},
fs: {
total: fsStats[0].size,
used: fsStats[0].used,
},
net: {
interface: netInterface,
total: storage?.total ?? 0,
used: storage?.used ?? 0,
},
};
});

View File

@ -1,6 +1,6 @@
import define from "@/server/api/define.js";
import { Apps } from "@/models/index.js";
import { genId, secureRndstr } from "backend-rs";
import { genId, generateSecureRandomString } from "backend-rs";
import { unique } from "@/prelude/array.js";
export const meta = {
@ -40,7 +40,7 @@ export default define(meta, paramDef, async (ps, user) => {
includeSecret: true,
});
// Generate secret
const secret = secureRndstr(32);
const secret = generateSecureRandomString(32);
// for backward compatibility
const permission = unique(

View File

@ -2,7 +2,7 @@ import * as crypto from "node:crypto";
import define from "@/server/api/define.js";
import { ApiError } from "@/server/api/error.js";
import { AuthSessions, AccessTokens, Apps } from "@/models/index.js";
import { genId, secureRndstr } from "backend-rs";
import { genId, generateSecureRandomString } from "backend-rs";
export const meta = {
tags: ["auth"],
@ -37,10 +37,10 @@ export default define(meta, paramDef, async (ps, user) => {
}
// Generate access token
const accessToken = secureRndstr(32);
const accessToken = generateSecureRandomString(32);
// Fetch exist access token
const exist = await AccessTokens.exist({
const exist = await AccessTokens.exists({
where: {
appId: session.appId,
userId: user.id,

View File

@ -1,4 +1,4 @@
import { readdir } from "fs/promises";
import { readdir } from "node:fs/promises";
import define from "@/server/api/define.js";
export const meta = {

View File

@ -2,8 +2,7 @@ import define from "@/server/api/define.js";
import { createImportPostsJob } from "@/queue/index.js";
import { ApiError } from "@/server/api/error.js";
import { DriveFiles } from "@/models/index.js";
import { DAY } from "backend-rs";
import { fetchMeta } from "backend-rs";
import { fetchMeta, DAY } from "backend-rs";
export const meta = {
secure: true,

View File

@ -4,11 +4,10 @@ import { resolveUser } from "@/remote/resolve-user.js";
import acceptAllFollowRequests from "@/services/following/requests/accept-all.js";
import { publishToFollowers } from "@/services/i/update.js";
import { publishMainStream } from "@/services/stream.js";
import { DAY } from "backend-rs";
import { stringToAcct, DAY } from "backend-rs";
import { apiLogger } from "@/server/api/logger.js";
import define from "@/server/api/define.js";
import { ApiError } from "@/server/api/error.js";
import { stringToAcct } from "backend-rs";
import { inspect } from "node:util";
export const meta = {

View File

@ -1,6 +1,6 @@
import type { User } from "@/models/entities/user.js";
import { resolveUser } from "@/remote/resolve-user.js";
import { DAY } from "backend-rs";
import { stringToAcct, DAY } from "backend-rs";
import DeliverManager from "@/remote/activitypub/deliver-manager.js";
import { renderActivity } from "@/remote/activitypub/renderer/index.js";
import define from "@/server/api/define.js";
@ -12,7 +12,6 @@ import { getUser } from "@/server/api/common/getters.js";
import { Followings, Users } from "@/models/index.js";
import { config } from "@/config.js";
import { publishMainStream } from "@/services/stream.js";
import { stringToAcct } from "backend-rs";
import { inspect } from "node:util";
export const meta = {

View File

@ -3,10 +3,9 @@ import {
publishMainStream,
publishUserEvent,
} from "@/services/stream.js";
import generateUserToken from "@/server/api/common/generate-native-user-token.js";
import define from "@/server/api/define.js";
import { Users, UserProfiles } from "@/models/index.js";
import { verifyPassword } from "backend-rs";
import { generateUserToken, verifyPassword } from "backend-rs";
export const meta = {
requireCredential: true,

View File

@ -1,6 +1,6 @@
import define from "@/server/api/define.js";
import { AccessTokens } from "@/models/index.js";
import { genId, secureRndstr } from "backend-rs";
import { genId, generateSecureRandomString } from "backend-rs";
export const meta = {
tags: ["auth"],
@ -43,7 +43,7 @@ export const paramDef = {
export default define(meta, paramDef, async (ps, user) => {
// Generate access token
const accessToken = secureRndstr(32);
const accessToken = generateSecureRandomString(32);
const now = new Date();

View File

@ -18,8 +18,8 @@ import { ApiError } from "@/server/api/error.js";
import define from "@/server/api/define.js";
import { HOUR, genId } from "backend-rs";
import { getNote } from "@/server/api/common/getters.js";
import { langmap } from "@/misc/langmap.js";
import { createScheduledCreateNoteJob } from "@/queue";
import { langmap } from "firefish-js";
import { createScheduledCreateNoteJob } from "@/queue/index.js";
export const meta = {
tags: ["notes"],

View File

@ -18,7 +18,7 @@ import { config } from "@/config.js";
import { noteVisibilities } from "@/types.js";
import { ApiError } from "@/server/api/error.js";
import define from "@/server/api/define.js";
import { HOUR } from "backend-rs";
import { genId, HOUR } from "backend-rs";
import { getNote } from "@/server/api/common/getters.js";
import { Poll } from "@/models/entities/poll.js";
import * as mfm from "mfm-js";
@ -26,7 +26,6 @@ import { concat } from "@/prelude/array.js";
import { extractHashtags } from "@/misc/extract-hashtags.js";
import { extractCustomEmojisFromMfm } from "@/misc/extract-custom-emojis-from-mfm.js";
import { extractMentionedUsers } from "@/services/note/create.js";
import { genId } from "backend-rs";
import { publishNoteStream } from "@/services/stream.js";
import DeliverManager from "@/remote/activitypub/deliver-manager.js";
import { renderActivity } from "@/remote/activitypub/renderer/index.js";
@ -34,7 +33,7 @@ import renderNote from "@/remote/activitypub/renderer/note.js";
import renderUpdate from "@/remote/activitypub/renderer/update.js";
import { deliverToRelays } from "@/services/relay.js";
// import { deliverQuestionUpdate } from "@/services/note/polls/update.js";
import { langmap } from "@/misc/langmap.js";
import { langmap } from "firefish-js";
export const meta = {
tags: ["notes"],

View File

@ -1,7 +1,7 @@
import { ApiError } from "@/server/api/error.js";
import { getNote } from "@/server/api/common/getters.js";
import { translate } from "@/misc/translate.js";
import type { PostLanguage } from "@/misc/langmap.js";
import type { PostLanguage } from "firefish-js";
import define from "@/server/api/define.js";
export const meta = {

View File

@ -1,5 +1,5 @@
import { publishMainStream } from "@/services/stream.js";
import { pushNotification } from "@/services/push-notification.js";
import { sendPushNotification, PushNotificationKind } from "backend-rs";
import { Notifications } from "@/models/index.js";
import define from "@/server/api/define.js";
@ -17,7 +17,7 @@ export const paramDef = {
required: [],
} as const;
export default define(meta, paramDef, async (ps, user) => {
export default define(meta, paramDef, async (_, user) => {
// Update documents
await Notifications.update(
{
@ -31,5 +31,5 @@ export default define(meta, paramDef, async (ps, user) => {
// 全ての通知を読みましたよというイベントを発行
publishMainStream(user.id, "readAllNotifications");
pushNotification(user.id, "readAllNotifications", undefined);
sendPushNotification(user.id, PushNotificationKind.ReadAllNotifications, {});
});

View File

@ -1,9 +1,8 @@
import { Pages, DriveFiles } from "@/models/index.js";
import { genId } from "backend-rs";
import { genId, HOUR } from "backend-rs";
import { Page } from "@/models/entities/page.js";
import define from "@/server/api/define.js";
import { ApiError } from "@/server/api/error.js";
import { HOUR } from "backend-rs";
export const meta = {
tags: ["pages"],

View File

@ -1,7 +1,6 @@
import { IsNull } from "typeorm";
import { Users } from "@/models/index.js";
import { fetchMeta } from "backend-rs";
import { stringToAcct } from "backend-rs";
import { fetchMeta, stringToAcct } from "backend-rs";
import type { User } from "@/models/entities/user.js";
import define from "@/server/api/define.js";

View File

@ -1,10 +1,9 @@
import * as os from "node:os";
import si from "systeminformation";
import define from "@/server/api/define.js";
import { fetchMeta } from "backend-rs";
import { fetchMeta, cpuInfo, memoryUsage, storageUsage } from "backend-rs";
export const meta = {
requireCredential: false,
requireCredential: true,
requireCredentialPrivateMode: true,
allowGet: true,
cacheSec: 30,
@ -18,19 +17,8 @@ export const paramDef = {
} as const;
export default define(meta, paramDef, async () => {
const memStats = await si.mem();
const fsStats = await si.fsSize();
let fsIndex = 0;
// Get the first index of fs sizes that are actualy used.
for (const [i, stat] of fsStats.entries()) {
if (stat.rw === true && stat.used > 0) {
fsIndex = i;
break;
}
}
const instanceMeta = await fetchMeta(true);
if (!instanceMeta.enableServerMachineStats) {
return {
machine: "Not specified",
@ -47,18 +35,19 @@ export default define(meta, paramDef, async () => {
},
};
}
const memory = memoryUsage();
const storage = storageUsage();
return {
machine: os.hostname(),
cpu: {
model: os.cpus()[0].model,
cores: os.cpus().length,
},
cpu: cpuInfo(),
mem: {
total: memStats.total,
total: memory.total,
},
fs: {
total: fsStats[fsIndex].size,
used: fsStats[fsIndex].used,
total: storage?.total ?? 0,
used: storage?.used ?? 0,
},
};
});

View File

@ -1,5 +1,4 @@
import { fetchMeta } from "backend-rs";
import { genId } from "backend-rs";
import { fetchMeta, genId } from "backend-rs";
import { SwSubscriptions } from "@/models/index.js";
import define from "@/server/api/define.js";

View File

@ -1,6 +1,6 @@
import Router from "@koa/router";
import type Router from "@koa/router";
import { getClient } from "../ApiMastodonCompatibleService.js";
import { ParsedUrlQuery } from "querystring";
import type { ParsedUrlQuery } from "node:querystring";
import {
convertAccount,
convertConversation,

View File

@ -16,10 +16,9 @@ import { IsNull } from "typeorm";
import { config, envOption } from "@/config.js";
import Logger from "@/services/logger.js";
import { Users } from "@/models/index.js";
import { fetchMeta } from "backend-rs";
import { fetchMeta, stringToAcct } from "backend-rs";
import { genIdenticon } from "@/misc/gen-identicon.js";
import { createTemp } from "@/misc/create-temp.js";
import { stringToAcct } from "backend-rs";
import megalodon, { type MegalodonInterface } from "megalodon";
import activityPub from "./activitypub.js";
import nodeinfo from "./nodeinfo.js";

View File

@ -149,7 +149,9 @@ router.get<{ Params: { path: string } }>("/emoji/:path(.*)", async (ctx) => {
return;
}
let url = new URL(`${config.mediaProxy || config.url + "/proxy"}/emoji.webp`);
const url = new URL(
`${config.mediaProxy || `${config.url}/proxy`}/emoji.webp`,
);
// || emoji.originalUrl してるのは後方互換性のため
url.searchParams.append("url", emoji.publicUrl || emoji.originalUrl);
url.searchParams.append("emoji", "1");
@ -370,9 +372,8 @@ const getFeed = async (
};
// As the /@user[.json|.rss|.atom]/sub endpoint is complicated, we will use a regex to switch between them.
const reUser = new RegExp(
"^/@(?<user>[^/]+?)(?:.(?<feed>json|rss|atom)(?:\\?[^/]*)?)?(?:/(?<sub>[^/]+))?$",
);
const reUser =
/^\/@(?<user>[^\/]+?)(?:.(?<feed>json|rss|atom)(?:\?[^\/]*)?)?(?:\/(?<sub>[^\/]+))?$/;
router.get(reUser, async (ctx, next) => {
const groups = reUser.exec(ctx.originalUrl)?.groups;
if (!groups) {

View File

@ -1,73 +0,0 @@
{
"short_name": "Firefish",
"name": "Firefish",
"description": "An open source, decentralized social media platform that's free forever!",
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#1f1d2e",
"theme_color": "#31748f",
"orientation": "natural",
"icons": [
{
"src": "/static-assets/icons/192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/static-assets/icons/512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/static-assets/icons/maskable.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/static-assets/icons/monochrome.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "monochrome"
}
],
"share_target": {
"action": "/share/",
"params": {
"title": "title",
"text": "text",
"url": "url"
}
},
"screenshots": [
{
"src": "/static-assets/screenshots/1.webp",
"sizes": "1080x2340",
"type": "image/webp",
"platform": "narrow",
"label": "Profile page"
},
{
"src": "/static-assets/screenshots/2.webp",
"sizes": "1080x2340",
"type": "image/webp",
"platform": "narrow",
"label": "Posts"
}
],
"shortcuts": [
{
"name": "Notifications",
"short_name": "Notifs",
"url": "/my/notifications"
},
{
"name": "Chats",
"url": "/my/messaging"
}
],
"categories": ["social"]
}

View File

@ -1,27 +1,97 @@
import type Koa from "koa";
import { fetchMeta } from "backend-rs";
import { config } from "@/config.js";
import manifest from "./manifest.json" assert { type: "json" };
const manifest = {
short_name: "Firefish",
name: "Firefish",
description:
"An open source, decentralized social media platform that's free forever!",
start_url: "/",
scope: "/",
display: "standalone",
background_color: "#1f1d2e",
theme_color: "#31748f",
orientation: "natural",
icons: [
{
src: "/static-assets/icons/192.png",
sizes: "192x192",
type: "image/png",
purpose: "any",
},
{
src: "/static-assets/icons/512.png",
sizes: "512x512",
type: "image/png",
purpose: "any",
},
{
src: "/static-assets/icons/maskable.png",
sizes: "512x512",
type: "image/png",
purpose: "maskable",
},
{
src: "/static-assets/icons/monochrome.png",
sizes: "512x512",
type: "image/png",
purpose: "monochrome",
},
],
share_target: {
action: "/share/",
params: {
title: "title",
text: "text",
url: "url",
},
},
screenshots: [
{
src: "/static-assets/screenshots/1.webp",
sizes: "1080x2340",
type: "image/webp",
platform: "narrow",
label: "Profile page",
},
{
src: "/static-assets/screenshots/2.webp",
sizes: "1080x2340",
type: "image/webp",
platform: "narrow",
label: "Posts",
},
],
shortcuts: [
{
name: "Notifications",
short_name: "Notifs",
url: "/my/notifications",
},
{
name: "Chats",
url: "/my/messaging",
},
],
categories: ["social"],
};
export const manifestHandler = async (ctx: Koa.Context) => {
// TODO
//const res = structuredClone(manifest);
const res = JSON.parse(JSON.stringify(manifest));
const instance = await fetchMeta(true);
const instance = await fetchMeta(false);
res.short_name = instance.name || "Firefish";
res.name = instance.name || "Firefish";
if (instance.themeColor) res.theme_color = instance.themeColor;
for (const icon of res.icons) {
manifest.short_name = instance.name || "Firefish";
manifest.name = instance.name || "Firefish";
if (instance.themeColor) manifest.theme_color = instance.themeColor;
for (const icon of manifest.icons) {
icon.src = `${icon.src}?v=${config.version.replace(/[^0-9]/g, "")}`;
}
for (const screenshot of res.screenshots) {
for (const screenshot of manifest.screenshots) {
screenshot.src = `${screenshot.src}?v=${config.version.replace(
/[^0-9]/g,
"",
)}`;
}
ctx.set("Cache-Control", "max-age=300");
ctx.body = res;
ctx.body = manifest;
};

View File

@ -1,5 +1,4 @@
import { publishMainStream } from "@/services/stream.js";
import { pushNotification } from "@/services/push-notification.js";
import {
Notifications,
Mutings,
@ -8,7 +7,12 @@ import {
Users,
Followings,
} from "@/models/index.js";
import { genId, isSilencedServer } from "backend-rs";
import {
genId,
isSilencedServer,
sendPushNotification,
PushNotificationKind,
} from "backend-rs";
import type { User } from "@/models/entities/user.js";
import type { Notification } from "@/models/entities/notification.js";
import { sendEmailNotification } from "./send-email-notification.js";
@ -81,7 +85,7 @@ export async function createNotification(
if (fresh == null) return; // 既に削除されているかもしれない
// We execute this before, because the server side "read" check doesnt work well with push notifications, the app and service worker will decide themself
// when it is best to show push notifications
pushNotification(notifieeId, "notification", packed);
sendPushNotification(notifieeId, PushNotificationKind.Generic, packed);
if (fresh.isRead) return;
//#region ただしミュートしているユーザーからの通知なら無視

View File

@ -1,10 +1,9 @@
import { v4 as uuid } from "uuid";
import generateNativeUserToken from "@/server/api/common/generate-native-user-token.js";
import { genRsaKeyPair } from "@/misc/gen-key-pair.js";
import { User } from "@/models/entities/user.js";
import { UserProfile } from "@/models/entities/user-profile.js";
import { IsNull } from "typeorm";
import { genId, hashPassword } from "backend-rs";
import { generateUserToken, genId, hashPassword } from "backend-rs";
import { UserKeypair } from "@/models/entities/user-keypair.js";
import { UsedUsername } from "@/models/entities/used-username.js";
import { db } from "@/db/postgre.js";
@ -16,7 +15,7 @@ export async function createSystemUser(username: string) {
const hash = hashPassword(password);
// Generate secret
const secret = generateNativeUserToken();
const secret = generateUserToken();
const keyPair = await genRsaKeyPair(4096);

View File

@ -1,8 +1,8 @@
import { URL } from "node:url";
import { Window } from "happy-dom";
import { type DOMWindow, JSDOM } from "jsdom";
import fetch from "node-fetch";
import tinycolor from "tinycolor2";
import { getJson, getAgentByUrl } from "@/misc/fetch.js";
import { getJson, getHtml, getAgentByUrl } from "@/misc/fetch.js";
import {
type Instance,
MAX_LENGTH_INSTANCE,
@ -112,15 +112,13 @@ export async function fetchInstanceMetadata(
}
}
async function fetchDom(instance: Instance): Promise<Window["document"]> {
async function fetchDom(instance: Instance): Promise<DOMWindow["document"]> {
logger.info(`Fetching HTML of ${instance.host} ...`);
const window = new Window({
url: `https://${instance.host}`,
});
const doc = window.document;
const html = await getHtml(`https://${instance.host}`);
const { window } = new JSDOM(html);
return doc;
return window.document;
}
async function fetchManifest(
@ -137,7 +135,7 @@ async function fetchManifest(
async function fetchFaviconUrl(
instance: Instance,
doc: Window["document"] | null,
doc: DOMWindow["document"] | null,
): Promise<string | null> {
const url = `https://${instance.host}`;
@ -169,7 +167,7 @@ async function fetchFaviconUrl(
async function fetchIconUrl(
instance: Instance,
doc: Window["document"] | null,
doc: DOMWindow["document"] | null,
manifest: Record<string, any> | null,
): Promise<string | null> {
if (manifest?.icons && manifest.icons.length > 0 && manifest.icons[0].src) {
@ -219,9 +217,9 @@ async function getThemeColor(
async function getSiteName(
info: Nodeinfo | null,
doc: Window["document"] | null,
doc: DOMWindow["document"] | null,
manifest: Record<string, any> | null,
): Promise<string | undefined | null> {
): Promise<string | null> {
if (info?.metadata) {
if (info.metadata.nodeName || info.metadata.name) {
return info.metadata.nodeName || info.metadata.name;
@ -247,7 +245,7 @@ async function getSiteName(
async function getDescription(
info: Nodeinfo | null,
doc: Window["document"] | null,
doc: DOMWindow["document"] | null,
manifest: Record<string, any> | null,
): Promise<string | null> {
if (info?.metadata) {

View File

@ -1,12 +1,11 @@
import { Window } from "happy-dom";
import type { HTMLAnchorElement, HTMLLinkElement } from "happy-dom";
import { JSDOM } from "jsdom";
import { config } from "@/config.js";
import { getHtml } from "@/misc/fetch.js";
async function getRelMeLinks(url: string): Promise<string[]> {
try {
const dom = new Window({
url: url,
});
const html = await getHtml(url);
const dom = new JSDOM(html);
const allLinks = [...dom.window.document.querySelectorAll("a, link")];
const relMeLinks = allLinks
.filter((a) => {

View File

@ -9,16 +9,17 @@ import {
} from "@/models/index.js";
import {
genId,
sendPushNotification,
publishToChatStream,
publishToGroupChatStream,
publishToChatIndexStream,
toPuny,
ChatEvent,
ChatIndexEvent,
PushNotificationKind,
} from "backend-rs";
import type { MessagingMessage } from "@/models/entities/messaging-message.js";
import { publishMainStream } from "@/services/stream.js";
import { pushNotification } from "@/services/push-notification.js";
import { Not } from "typeorm";
import type { Note } from "@/models/entities/note.js";
import renderNote from "@/remote/activitypub/renderer/note.js";
@ -118,7 +119,11 @@ export async function createMessage(
//#endregion
publishMainStream(recipientUser.id, "unreadMessagingMessage", messageObj);
pushNotification(recipientUser.id, "unreadMessagingMessage", messageObj);
sendPushNotification(
recipientUser.id,
PushNotificationKind.Chat,
messageObj,
);
} else if (recipientGroup) {
const joinings = await UserGroupJoinings.findBy({
userGroupId: recipientGroup.id,
@ -127,7 +132,11 @@ export async function createMessage(
for (const joining of joinings) {
if (freshMessage.reads.includes(joining.userId)) return; // 既読
publishMainStream(joining.userId, "unreadMessagingMessage", messageObj);
pushNotification(joining.userId, "unreadMessagingMessage", messageObj);
sendPushNotification(
joining.userId,
PushNotificationKind.Chat,
messageObj,
);
}
}
}, 2000);

View File

@ -63,7 +63,7 @@ import { db } from "@/db/postgre.js";
import { getActiveWebhooks } from "@/misc/webhook-cache.js";
import { redisClient } from "@/db/redis.js";
import { Mutex } from "redis-semaphore";
import { langmap } from "@/misc/langmap.js";
import { langmap } from "firefish-js";
import Logger from "@/services/logger.js";
import { inspect } from "node:util";
import { toRustObject } from "@/prelude/undefined-to-null.js";

View File

@ -1,115 +0,0 @@
import push from "web-push";
import { config } from "@/config.js";
import { SwSubscriptions } from "@/models/index.js";
import { fetchMeta, getNoteSummary } from "backend-rs";
import type { Packed } from "@/misc/schema.js";
// Defined also packages/sw/types.ts#L14-L21
type pushNotificationsTypes = {
notification: Packed<"Notification">;
unreadMessagingMessage: Packed<"MessagingMessage">;
readNotifications: { notificationIds: string[] };
readAllNotifications: undefined;
readAllMessagingMessages: undefined;
readAllMessagingMessagesOfARoom: { userId: string } | { groupId: string };
};
// プッシュメッセージサーバーには文字数制限があるため、内容を削減します
function truncateNotification(notification: Packed<"Notification">): any {
if (notification.note != null) {
return {
...notification,
note: {
...notification.note,
// replace the text with summary
text: getNoteSummary(
notification.type === "renote" && notification.note.renote != null
? notification.note.renote
: notification.note,
),
cw: undefined,
reply: undefined,
renote: undefined,
user: undefined as any, // 通知を受け取ったユーザーである場合が多いのでこれも捨てる
},
};
}
return notification;
}
export async function pushNotification<T extends keyof pushNotificationsTypes>(
userId: string,
type: T,
body: pushNotificationsTypes[T],
) {
const meta = await fetchMeta(true);
if (
!meta.enableServiceWorker ||
meta.swPublicKey == null ||
meta.swPrivateKey == null
)
return;
// アプリケーションの連絡先と、サーバーサイドの鍵ペアの情報を登録
push.setVapidDetails(config.url, meta.swPublicKey, meta.swPrivateKey);
// Fetch
const subscriptions = await SwSubscriptions.findBy({
userId: userId,
});
for (const subscription of subscriptions) {
if (
[
"readNotifications",
"readAllNotifications",
"readAllMessagingMessages",
"readAllMessagingMessagesOfARoom",
].includes(type) &&
!subscription.sendReadMessage
)
continue;
const pushSubscription = {
endpoint: subscription.endpoint,
keys: {
auth: subscription.auth,
p256dh: subscription.publickey,
},
};
push
.sendNotification(
pushSubscription,
JSON.stringify({
type,
body:
type === "notification"
? truncateNotification(body as Packed<"Notification">)
: body,
userId,
dateTime: Date.now(),
}),
{
proxy: config.proxy,
},
)
.catch((err: any) => {
//swLogger.info(err.statusCode);
//swLogger.info(err.headers);
//swLogger.info(err.body);
if (err.statusCode === 410) {
SwSubscriptions.delete({
userId: userId,
endpoint: subscription.endpoint,
auth: subscription.auth,
publickey: subscription.publickey,
});
}
});
}
}

View File

@ -3,8 +3,7 @@ import {
MAX_LENGTH_INSTANCE,
} from "@/models/entities/instance.js";
import { Instances } from "@/models/index.js";
import { genId } from "backend-rs";
import { toPuny } from "backend-rs";
import { genId, toPuny } from "backend-rs";
import { Cache } from "@/misc/cache.js";
import Logger from "@/services/logger.js";

View File

@ -12,54 +12,54 @@
"format": "pnpm biome format * --write"
},
"devDependencies": {
"@eslint-sets/eslint-config-vue3": "^5.12.0",
"@eslint-sets/eslint-config-vue3-ts": "^3.3.0",
"@phosphor-icons/web": "^2.1.1",
"@eslint-sets/eslint-config-vue3": "5.13.0",
"@eslint-sets/eslint-config-vue3-ts": "3.3.0",
"@phosphor-icons/web": "2.1.1",
"@rollup/plugin-alias": "5.1.0",
"@rollup/plugin-json": "6.1.0",
"@rollup/pluginutils": "^5.1.0",
"@rollup/pluginutils": "5.1.0",
"@syuilo/aiscript": "0.17.0",
"@types/autosize": "^4.0.3",
"@misskey-dev/browser-image-resizer": "2024.1.0",
"@types/autosize": "4.0.3",
"@types/glob": "8.1.0",
"@types/insert-text-at-cursor": "^0.3.2",
"@types/insert-text-at-cursor": "0.3.2",
"@types/katex": "0.16.7",
"@types/matter-js": "0.19.6",
"@types/prismjs": "^1.26.3",
"@types/prismjs": "1.26.4",
"@types/punycode": "2.1.4",
"@types/qrcode": "1.5.5",
"@types/seedrandom": "3.0.8",
"@types/textarea-caret": "^3.0.3",
"@types/textarea-caret": "3.0.3",
"@types/throttle-debounce": "5.0.2",
"@types/tinycolor2": "1.4.6",
"@types/uuid": "9.0.8",
"@vitejs/plugin-vue": "5.0.4",
"@vue/runtime-core": "3.4.25",
"@vue/runtime-core": "3.4.27",
"autobind-decorator": "2.4.0",
"autosize": "6.0.1",
"broadcast-channel": "7.0.0",
"browser-image-resizer": "github:misskey-dev/browser-image-resizer",
"chart.js": "4.4.2",
"chartjs-adapter-date-fns": "3.0.0",
"chartjs-chart-matrix": "^2.0.1",
"chartjs-chart-matrix": "2.0.1",
"chartjs-plugin-gradient": "0.6.1",
"chartjs-plugin-zoom": "2.0.1",
"city-timezones": "^1.2.1",
"city-timezones": "1.2.1",
"compare-versions": "6.1.0",
"cropperjs": "2.0.0-beta.4",
"cropperjs": "2.0.0-beta.5",
"date-fns": "3.6.0",
"emojilib": "^3.0.12",
"eslint-plugin-file-progress": "^1.3.0",
"emojilib": "3.0.12",
"eslint-plugin-file-progress": "1.4.0",
"eventemitter3": "5.0.1",
"fast-blurhash": "^1.1.2",
"fast-blurhash": "1.1.2",
"firefish-js": "workspace:*",
"focus-trap": "^7.5.4",
"focus-trap-vue": "^4.0.3",
"gsap": "^3.12.5",
"focus-trap": "7.5.4",
"focus-trap-vue": "4.0.3",
"gsap": "3.12.5",
"idb-keyval": "6.2.1",
"insert-text-at-cursor": "0.3.0",
"json5": "2.2.3",
"katex": "0.16.10",
"long": "^5.2.3",
"long": "5.2.3",
"libopenmpt-wasm": "github:TheEssem/libopenmpt-packaging#build",
"matter-js": "0.19.0",
"mfm-js": "0.24.0",
@ -69,28 +69,27 @@
"prismjs": "1.29.0",
"punycode": "2.3.1",
"qrcode": "1.5.3",
"qrcode-vue3": "^1.6.8",
"rollup": "4.16.4",
"qrcode-vue3": "1.6.8",
"rollup": "4.17.2",
"s-age": "1.1.2",
"sass": "1.75.0",
"sass": "1.77.1",
"seedrandom": "3.0.5",
"stringz": "2.1.0",
"swiper": "11.1.1",
"swiper": "11.1.3",
"syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0",
"three": "0.164.1",
"throttle-debounce": "5.0.0",
"tinycolor2": "1.6.0",
"tinyld": "^1.3.4",
"tinyld": "1.3.4",
"typescript": "5.4.5",
"unicode-emoji-json": "^0.6.0",
"unicode-emoji-json": "0.6.0",
"uuid": "9.0.1",
"vite": "5.2.10",
"vite-plugin-compression": "^0.5.1",
"vue": "3.4.25",
"vue-draggable-plus": "^0.4.0",
"vue-plyr": "^7.0.0",
"vite": "5.2.11",
"vite-plugin-compression": "0.5.1",
"vue": "3.4.27",
"vue-draggable-plus": "0.4.1",
"vue-plyr": "7.0.0",
"vue-prism-editor": "2.0.0-alpha.2",
"vue-tsc": "2.0.14"
"vue-tsc": "2.0.18"
}
}

View File

@ -7,6 +7,7 @@ import { alert, api, popup, popupMenu, waiting } from "@/os";
import icon from "@/scripts/icon";
import { del, get, set } from "@/scripts/idb-proxy";
import { reloadChannel, unisonReload } from "@/scripts/unison-reload";
import type { MenuButton, MenuUser } from "./types/menu";
// TODO: 他のタブと永続化されたstateを同期
@ -16,7 +17,7 @@ export async function signOut() {
waiting();
localStorage.removeItem("account");
await removeAccount(me.id);
await removeAccount(me!.id);
const accounts = await getAccounts();
@ -26,12 +27,9 @@ export async function signOut() {
const registration = await navigator.serviceWorker.ready;
const push = await registration.pushManager.getSubscription();
if (push) {
await fetch(`${apiUrl}/sw/unregister`, {
method: "POST",
body: JSON.stringify({
i: me.token,
endpoint: push.endpoint,
}),
await api("sw/unregister", {
endpoint: push.endpoint,
i: me!.token, // FIXME: This parameter seems to be removable but I didn't test it
});
}
}
@ -117,13 +115,13 @@ function showSuspendedDialog() {
export function updateAccount(accountData) {
for (const [key, value] of Object.entries(accountData)) {
me[key] = value;
me![key] = value;
}
localStorage.setItem("account", JSON.stringify(me));
}
export async function refreshAccount() {
const accountData = await fetchAccount(me.token);
const accountData = await fetchAccount(me!.token);
return updateAccount(accountData);
}
@ -189,7 +187,7 @@ export async function openAccountMenu(
async function switchAccount(account: entities.UserDetailed) {
const storedAccounts = await getAccounts();
const token = storedAccounts.find((x) => x.id === account.id).token;
const token = storedAccounts.find((x) => x.id === account.id)!.token;
switchAccountWithToken(token);
}
@ -198,15 +196,15 @@ export async function openAccountMenu(
}
const storedAccounts = await getAccounts().then((accounts) =>
accounts.filter((x) => x.id !== me.id),
accounts.filter((x) => x.id !== me!.id),
);
const accountsPromise = api("users/show", {
userIds: storedAccounts.map((x) => x.id),
});
function createItem(account: entities.UserDetailed) {
function createItem(account: entities.UserDetailed): MenuUser {
return {
type: "user",
type: "user" as const,
user: account,
active: opts.active != null ? opts.active === account.id : false,
action: () => {
@ -221,10 +219,14 @@ export async function openAccountMenu(
const accountItemPromises = storedAccounts.map(
(a) =>
new Promise((res) => {
new Promise<MenuUser>((res) => {
accountsPromise.then((accounts) => {
const account = accounts.find((x) => x.id === a.id);
if (account == null) return res(null);
if (account == null) {
// The user is deleted, remove it
removeAccount(a.id);
return res(null as unknown as MenuUser);
}
res(createItem(account));
});
}),
@ -233,74 +235,72 @@ export async function openAccountMenu(
if (opts.withExtraOperation) {
popupMenu(
[
...[
...(isMobile ?? false
? [
{
type: "parent",
icon: `${icon("ph-plus")}`,
text: i18n.ts.addAccount,
children: [
{
text: i18n.ts.existingAccount,
action: () => {
showSigninDialog();
},
...(isMobile ?? false
? [
{
type: "parent" as const,
icon: `${icon("ph-plus")}`,
text: i18n.ts.addAccount,
children: [
{
text: i18n.ts.existingAccount,
action: () => {
showSigninDialog();
},
{
text: i18n.ts.createAccount,
action: () => {
createAccount();
},
},
{
text: i18n.ts.createAccount,
action: () => {
createAccount();
},
],
},
]
: [
{
type: "link",
text: i18n.ts.profile,
to: `/@${me.username}`,
avatar: me,
},
null,
]),
...(opts.includeCurrentAccount ? [createItem(me)] : []),
...accountItemPromises,
...(isMobile ?? false
? [
null,
{
type: "link",
text: i18n.ts.profile,
to: `/@${me.username}`,
avatar: me,
},
]
: [
{
type: "parent",
icon: `${icon("ph-plus")}`,
text: i18n.ts.addAccount,
children: [
{
text: i18n.ts.existingAccount,
action: () => {
showSigninDialog();
},
},
],
},
]
: [
{
type: "link" as const,
text: i18n.ts.profile,
to: `/@${me!.username}`,
avatar: me!,
},
null,
]),
...(opts.includeCurrentAccount ? [createItem(me!)] : []),
...accountItemPromises,
...(isMobile ?? false
? [
null,
{
type: "link" as const,
text: i18n.ts.profile,
to: `/@${me!.username}`,
avatar: me!,
},
]
: [
{
type: "parent" as const,
icon: `${icon("ph-plus")}`,
text: i18n.ts.addAccount,
children: [
{
text: i18n.ts.existingAccount,
action: () => {
showSigninDialog();
},
{
text: i18n.ts.createAccount,
action: () => {
createAccount();
},
},
{
text: i18n.ts.createAccount,
action: () => {
createAccount();
},
],
},
]),
],
},
],
},
]),
],
ev.currentTarget ?? ev.target,
(ev.currentTarget ?? ev.target) as HTMLElement,
{
align: "left",
},
@ -308,10 +308,10 @@ export async function openAccountMenu(
} else {
popupMenu(
[
...(opts.includeCurrentAccount ? [createItem(me)] : []),
...(opts.includeCurrentAccount ? [createItem(me!)] : []),
...accountItemPromises,
],
ev.currentTarget ?? ev.target,
(ev.currentTarget ?? ev.target) as HTMLElement,
{
align: "left",
},

View File

@ -0,0 +1,121 @@
import { ref as vueRef } from "vue";
import type { UnwrapRef } from "vue";
// TODO: 他のタブと永続化されたstateを同期
const PREFIX = "miux:";
interface Plugin {
id: string;
name: string;
active: boolean;
configData: Record<string, unknown>;
token: string;
ast: unknown[];
}
import darkTheme from "@/themes/d-rosepine.json5";
/**
* Storage for configuration information that does not need to be constantly loaded into memory (non-reactive)
*/
import lightTheme from "@/themes/l-rosepinedawn.json5";
const ColdStoreDefault = {
lightTheme,
darkTheme,
syncDeviceDarkMode: true,
plugins: [] as Plugin[],
mediaVolume: 0.5,
vibrate: false,
sound_masterVolume: 0.3,
sound_note: { type: "none", volume: 0 },
sound_noteMy: { type: "syuilo/up", volume: 1 },
sound_notification: { type: "syuilo/pope2", volume: 1 },
sound_chat: { type: "syuilo/pope1", volume: 1 },
sound_chatBg: { type: "syuilo/waon", volume: 1 },
sound_antenna: { type: "syuilo/triple", volume: 1 },
sound_channel: { type: "syuilo/square-pico", volume: 1 },
};
const watchers: {
key: string;
callback: (value) => void;
}[] = [];
function get<T extends keyof typeof ColdStoreDefault>(
key: T,
): (typeof ColdStoreDefault)[T] {
// TODO: indexedDBにする
// ただしその際はnullチェックではなくキー存在チェックにしないとダメ
// (indexedDBはnullを保存できるため、ユーザーが意図してnullを格納した可能性がある)
const value = localStorage.getItem(PREFIX + key);
if (value == null) {
return ColdStoreDefault[key];
} else {
return JSON.parse(value);
}
}
function set<T extends keyof typeof ColdStoreDefault>(
key: T,
value: (typeof ColdStoreDefault)[T],
): void {
// 呼び出し側のバグ等で undefined が来ることがある
// undefined を文字列として localStorage に入れると参照する際の JSON.parse でコケて不具合の元になるため無視
if (value === undefined) {
console.error(`attempt to store undefined value for key '${key}'`);
return;
}
localStorage.setItem(PREFIX + key, JSON.stringify(value));
for (const watcher of watchers) {
if (watcher.key === key) watcher.callback(value);
}
}
function watch<T extends keyof typeof ColdStoreDefault>(
key: T,
callback: (value: (typeof ColdStoreDefault)[T]) => void,
) {
watchers.push({ key, callback });
}
// TODO: VueのcustomRef使うと良い感じになるかも
function ref<T extends keyof typeof ColdStoreDefault>(key: T) {
const v = get(key);
const r = vueRef(v);
// TODO: このままではwatcherがリークするので開放する方法を考える
watch(key, (v) => {
r.value = v as UnwrapRef<typeof v>;
});
return r;
}
/**
* getter/setterを作ります
* vue場で設定コントロールのmodelとして使う用
*/
function makeGetterSetter<K extends keyof typeof ColdStoreDefault>(key: K) {
// TODO: VueのcustomRef使うと良い感じになるかも
const valueRef = ref(key);
return {
get: () => {
return valueRef.value;
},
set: (value: (typeof ColdStoreDefault)[K]) => {
const val = value;
set(key, val);
},
};
}
export default {
default: ColdStoreDefault,
watchers,
get,
set,
watch,
ref,
makeGetterSetter,
};

View File

@ -1,5 +1,5 @@
<template>
<MkModal ref="modal" :z-priority="'middle'" @closed="$emit('closed')">
<MkModal ref="modal" :z-priority="'middle'" @closed="emit('closed')">
<div :class="$style.root">
<div :class="$style.title">
<MkSparkle v-if="isGoodNews">{{ title }}</MkSparkle>
@ -41,6 +41,10 @@ const props = defineProps<{
announcement: entities.Announcement;
}>();
const emit = defineEmits<{
closed: [];
}>();
const { id, text, title, imageUrl, isGoodNews } = props.announcement;
const modal = shallowRef<InstanceType<typeof MkModal>>();

View File

@ -182,7 +182,7 @@ export default {
const props = defineProps<{
type: string;
q: string | null;
textarea: HTMLTextAreaElement;
textarea: HTMLTextAreaElement | HTMLInputElement;
close: () => void;
x: number;
y: number;
@ -435,7 +435,7 @@ onUpdated(() => {
onMounted(() => {
setPosition();
props.textarea.addEventListener("keydown", onKeydown);
(props.textarea as HTMLTextAreaElement).addEventListener("keydown", onKeydown);
document.body.addEventListener("mousedown", onMousedown);
nextTick(() => {
@ -453,7 +453,7 @@ onMounted(() => {
});
onBeforeUnmount(() => {
props.textarea.removeEventListener("keydown", onKeydown);
(props.textarea as HTMLTextAreaElement).removeEventListener("keydown", onKeydown);
document.body.removeEventListener("mousedown", onMousedown);
});
</script>

View File

@ -1,5 +1,5 @@
<template>
<MkModal ref="modal" :z-priority="'middle'" @closed="$emit('closed')">
<MkModal ref="modal" :z-priority="'middle'" @closed="emit('closed')">
<div :class="$style.root">
<p :class="$style.title">
{{ i18n.ts.youHaveUnreadAnnouncements }}
@ -21,6 +21,10 @@ import MkModal from "@/components/MkModal.vue";
import MkButton from "@/components/MkButton.vue";
import { i18n } from "@/i18n";
const emit = defineEmits<{
closed: [];
}>();
const modal = shallowRef<InstanceType<typeof MkModal>>();
const checkAnnouncements = () => {
modal.value!.close();

View File

@ -40,7 +40,7 @@
</template>
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import { computed, onMounted, ref } from "vue";
import type { entities } from "firefish-js";
import PhotoSwipeLightbox from "photoswipe/lightbox";
import PhotoSwipe from "photoswipe";
@ -207,9 +207,9 @@ const isModule = (file: entities.DriveFile): boolean => {
);
};
const previewableCount = props.mediaList.filter((media) =>
previewable(media),
).length;
const previewableCount = computed(
() => props.mediaList.filter((media) => previewable(media)).length,
);
</script>
<style lang="scss" scoped>

View File

@ -1,7 +1,7 @@
<template>
<div
v-if="!muted.muted"
v-show="!isDeleted"
v-show="!isDeleted && renotes?.length !== 0"
:id="appearNote.historyId || appearNote.id"
ref="el"
v-hotkey="keymap"
@ -10,13 +10,20 @@
:aria-label="accessibleLabel"
class="tkcbzcuz note-container"
:tabindex="!isDeleted ? '-1' : undefined"
:class="{ renote: isRenote }"
:class="{ renote: isRenote || (renotesSliced && renotesSliced.length > 0) }"
>
<MkNoteSub
v-if="appearNote.reply && !detailedView && !collapsedReply"
v-if="appearNote.reply && !detailedView && !collapsedReply && !parents"
:note="appearNote.reply"
class="reply-to"
/>
<MkNoteSub
v-for="n of parents"
v-else-if="!detailedView && !collapsedReply && parents"
:key="n.id"
:note="n"
class="reply-to"
/>
<div
v-if="!detailedView"
class="note-context"
@ -86,6 +93,75 @@
:custom-emojis="note.emojis"
/>
</div>
<div v-if="isRenote || (renotesSliced && renotesSliced.length > 0)" class="renote">
<i :class="icon('ph-rocket-launch')"></i>
<I18n
v-if="renotesSliced == null"
:src="i18n.ts.renotedBy"
tag="span"
>
<template #user>
<MkAvatar class="avatar" :user="note.user" />
<MkA
v-user-preview="note.userId"
class="name"
:to="userPage(note.user)"
@click.stop
>
<MkUserName :user="note.user" />
</MkA>
</template>
</I18n>
<I18n
v-else
:src="i18n.ts.renotedBy"
tag="span"
>
<template #user>
<template
v-for="(renote, index) in renotesSliced"
>
<MkAvatar
class="avatar"
:user="renote.user"
/>
<MkA
v-user-preview="renote.userId"
class="name"
:to="userPage(renote.user)"
@click.stop
>
<MkUserName :user="renote.user" />
</MkA>
{{
index !== renotesSliced.length - 1
? ", "
: renotesSliced.length < renotes!.length
? "..."
: ""
}}
</template>
</template>
</I18n>
<div class="info">
<button
ref="renoteTime"
class="_button time"
@click.stop="showRenoteMenu()"
>
<i
v-if="isMyNote"
:class="icon('ph-dots-three-outline dropdownIcon')"
></i>
<MkTime
v-if="(renotesSliced && renotesSliced.length > 0)"
:time="renotesSliced[0].createdAt"
/>
<MkTime v-else :time="note.createdAt" />
</button>
<MkVisibility :note="note" />
</div>
</div>
</div>
<article
class="article"
@ -287,7 +363,7 @@
</template>
<script lang="ts" setup>
import { computed, inject, onMounted, ref } from "vue";
import { computed, inject, onMounted, ref, watch } from "vue";
import type { Ref } from "vue";
import type { entities } from "firefish-js";
import MkSubNoteContent from "./MkSubNoteContent.vue";
@ -318,17 +394,13 @@ import { notePage } from "@/filters/note";
import { deepClone } from "@/scripts/clone";
import { getNoteSummary } from "@/scripts/get-note-summary";
import icon from "@/scripts/icon";
import type { NoteTranslation } from "@/types/note";
const router = useRouter();
type NoteType = entities.Note & {
_featuredId_?: string;
_prId_?: string;
};
import type { NoteTranslation, NoteType } from "@/types/note";
import { isDeleted as _isDeleted, isRenote as _isRenote } from "@/scripts/note";
const props = defineProps<{
note: NoteType;
parents?: NoteType[];
renotes?: entities.Note[];
pinned?: boolean;
detailedView?: boolean;
collapsedReply?: boolean;
@ -337,37 +409,20 @@ const props = defineProps<{
isLongJudger?: (note: entities.Note) => boolean;
}>();
// #region Constants
const router = useRouter();
const inChannel = inject("inChannel", null);
const note = ref(deepClone(props.note));
const softMuteReasonI18nSrc = (what?: string) => {
if (what === "note") return i18n.ts.userSaysSomethingReason;
if (what === "reply") return i18n.ts.userSaysSomethingReasonReply;
if (what === "renote") return i18n.ts.userSaysSomethingReasonRenote;
if (what === "quote") return i18n.ts.userSaysSomethingReasonQuote;
// I don't think here is reachable, but just in case
return i18n.ts.userSaysSomething;
const keymap = {
r: () => reply(true),
"e|a|plus": () => react(true),
q: () => renoteButton.value!.renote(true),
"up|k": focusBefore,
"down|j": focusAfter,
esc: blur,
"m|o": () => menu(true),
// FIXME: What's this?
// s: () => showContent.value !== showContent.value,
};
// plugin
if (noteViewInterruptors.length > 0) {
onMounted(async () => {
let result = deepClone(note.value);
for (const interruptor of noteViewInterruptors) {
result = await interruptor.handler(result);
}
note.value = result;
});
}
const isRenote =
note.value.renote != null &&
note.value.text == null &&
note.value.fileIds.length === 0 &&
note.value.poll == null;
const el = ref<HTMLElement | null>(null);
const footerEl = ref<HTMLElement>();
const menuButton = ref<HTMLElement>();
@ -375,42 +430,179 @@ const starButton = ref<InstanceType<typeof XStarButton>>();
const renoteButton = ref<InstanceType<typeof XRenoteButton> | null>(null);
const renoteTime = ref<HTMLElement>();
const reactButton = ref<HTMLElement | null>(null);
const appearNote = computed(() =>
isRenote ? (note.value.renote as NoteType) : note.value,
);
const isMyRenote = isSignedIn(me) && me.id === note.value.userId;
// const showContent = ref(false);
const isDeleted = ref(false);
const muted = ref(
getWordSoftMute(
note.value,
me?.id,
defaultStore.state.mutedWords,
defaultStore.state.mutedLangs,
),
);
const translation = ref<NoteTranslation | null>(null);
const translating = ref(false);
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
const enableEmojiReactions = defaultStore.reactiveState.enableEmojiReactions;
const expandOnNoteClick = defaultStore.reactiveState.expandOnNoteClick;
const lang = localStorage.getItem("lang");
const translateLang = localStorage.getItem("translateLang");
const targetLang = (translateLang || lang || navigator.language)?.slice(0, 2);
const currentClipPage = inject<Ref<entities.Clip> | null>(
"currentClipPage",
null,
);
// #endregion
const isForeignLanguage: boolean =
defaultStore.state.detectPostLanguage &&
appearNote.value.text != null &&
(() => {
const postLang = detectLanguage(appearNote.value.text);
return postLang !== "" && postLang !== targetLang;
})();
// #region Variables bound to Notes
let capture: ReturnType<typeof useNoteCapture> | undefined;
const note = ref(deepClone(props.note));
const postIsExpanded = ref(false);
const translation = ref<NoteTranslation | null>(null);
const translating = ref(false);
const isDeleted = ref(false);
const renotes = ref(props.renotes?.filter((rn) => !_isDeleted(rn.id)));
// #endregion
// #region computed
const renotesSliced = computed(() => renotes.value?.slice(0, 5));
const isRenote = computed(() => _isRenote(note.value));
const appearNote = computed(() =>
isRenote.value ? (note.value.renote as NoteType) : note.value,
);
const isMyNote = computed(
() => isSignedIn(me) && me.id === note.value.userId && props.renotes == null,
);
const muted = computed(() =>
getWordSoftMute(
note.value,
me?.id,
defaultStore.reactiveState.mutedWords.value,
defaultStore.reactiveState.mutedLangs.value,
),
);
const isForeignLanguage = computed(
() =>
defaultStore.state.detectPostLanguage &&
appearNote.value.text != null &&
(() => {
const postLang = detectLanguage(appearNote.value.text);
return postLang !== "" && postLang !== targetLang;
})(),
);
const reactionCount = computed(() =>
Object.values(appearNote.value.reactions).reduce(
(partialSum, val) => partialSum + val,
0,
),
);
const accessibleLabel = computed(() => {
let label = `${appearNote.value.user.username}; `;
if (appearNote.value.renote) {
label += `${i18n.ts.renoted} ${appearNote.value.renote.user.username}; `;
if (appearNote.value.renote.cw) {
label += `${i18n.ts.cw}: ${appearNote.value.renote.cw}; `;
if (postIsExpanded.value) {
label += `${appearNote.value.renote.text}; `;
}
} else {
label += `${appearNote.value.renote.text}; `;
}
} else {
if (appearNote.value.cw) {
label += `${i18n.ts.cw}: ${appearNote.value.cw}; `;
if (postIsExpanded.value) {
label += `${appearNote.value.text}; `;
}
} else {
label += `${appearNote.value.text}; `;
}
}
const date = new Date(appearNote.value.createdAt);
label += `${date.toLocaleTimeString()}`;
return label;
});
// #endregion
async function pluginInit(newNote: NoteType) {
// plugin
if (noteViewInterruptors.length > 0) {
let result = deepClone(newNote);
for (const interruptor of noteViewInterruptors) {
result = await interruptor.handler(result);
}
note.value = result;
}
}
function recalculateRenotes() {
renotes.value = props.renotes?.filter((rn) => !_isDeleted(rn.id));
}
async function init(newNote: NoteType, first = false) {
if (!first) {
// plugin
if (noteViewInterruptors.length > 0) {
await pluginInit(newNote);
} else {
note.value = deepClone(newNote);
}
}
translation.value = null;
translating.value = false;
postIsExpanded.value = false;
isDeleted.value = _isDeleted(note.value.id);
if (appearNote.value.historyId == null) {
capture?.close();
capture = useNoteCapture({
rootEl: el,
note: appearNote,
isDeletedRef: isDeleted,
});
if (isRenote.value === true) {
useNoteCapture({
rootEl: el,
note,
isDeletedRef: isDeleted,
});
}
if (props.renotes) {
const renoteDeletedTrigger = ref(false);
for (const renote of props.renotes) {
useNoteCapture({
rootEl: el,
note: ref(renote),
isDeletedRef: renoteDeletedTrigger,
});
}
watch(renoteDeletedTrigger, recalculateRenotes);
}
}
}
init(props.note, true);
onMounted(() => {
pluginInit(note.value);
});
watch(isDeleted, () => {
if (isDeleted.value === true) {
if (props.parents && props.parents.length > 0) {
let noteTakePlace: NoteType | null = null;
while (noteTakePlace == null || _isDeleted(noteTakePlace.id)) {
if (props.parents.length === 0) {
return;
}
noteTakePlace = props.parents[props.parents.length - 1];
props.parents.pop();
}
noteTakePlace.repliesCount -= 1;
init(noteTakePlace);
isDeleted.value = false;
}
}
});
watch(
() => props.note.id,
(o, n) => {
if (o !== n && _isDeleted(note.value.id) !== true) {
init(props.note);
}
},
);
watch(() => props.renotes?.length, recalculateRenotes);
async function translate_(noteId: string, targetLang: string) {
return await os.api("notes/translate", {
@ -439,24 +631,14 @@ async function translate() {
translating.value = false;
}
const keymap = {
r: () => reply(true),
"e|a|plus": () => react(true),
q: () => renoteButton.value!.renote(true),
"up|k": focusBefore,
"down|j": focusAfter,
esc: blur,
"m|o": () => menu(true),
// FIXME: What's this?
// s: () => showContent.value !== showContent.value,
};
function softMuteReasonI18nSrc(what?: string) {
if (what === "note") return i18n.ts.userSaysSomethingReason;
if (what === "reply") return i18n.ts.userSaysSomethingReasonReply;
if (what === "renote") return i18n.ts.userSaysSomethingReasonRenote;
if (what === "quote") return i18n.ts.userSaysSomethingReasonQuote;
if (appearNote.value.historyId == null) {
useNoteCapture({
rootEl: el,
note: appearNote,
isDeletedRef: isDeleted,
});
// I don't think here is reachable, but just in case
return i18n.ts.userSaysSomething;
}
function reply(_viaKeyboard = false): void {
@ -497,11 +679,6 @@ function undoReact(note: NoteType): void {
});
}
const currentClipPage = inject<Ref<entities.Clip> | null>(
"currentClipPage",
null,
);
function onContextmenu(ev: MouseEvent): void {
const isLink = (el: HTMLElement): boolean => {
if (el.tagName === "A") return true;
@ -590,7 +767,7 @@ function menu(viaKeyboard = false): void {
}
function showRenoteMenu(viaKeyboard = false): void {
if (!isMyRenote) return;
if (!isMyNote.value) return;
os.popupMenu(
[
{
@ -651,39 +828,10 @@ function readPromo() {
isDeleted.value = true;
}
const postIsExpanded = ref(false);
function setPostExpanded(val: boolean) {
postIsExpanded.value = val;
}
const accessibleLabel = computed(() => {
let label = `${appearNote.value.user.username}; `;
if (appearNote.value.renote) {
label += `${i18n.ts.renoted} ${appearNote.value.renote.user.username}; `;
if (appearNote.value.renote.cw) {
label += `${i18n.ts.cw}: ${appearNote.value.renote.cw}; `;
if (postIsExpanded.value) {
label += `${appearNote.value.renote.text}; `;
}
} else {
label += `${appearNote.value.renote.text}; `;
}
} else {
if (appearNote.value.cw) {
label += `${i18n.ts.cw}: ${appearNote.value.cw}; `;
if (postIsExpanded.value) {
label += `${appearNote.value.text}; `;
}
} else {
label += `${appearNote.value.text}; `;
}
}
const date = new Date(appearNote.value.createdAt);
label += `${date.toLocaleTimeString()}`;
return label;
});
defineExpose({
focus,
blur,
@ -757,6 +905,7 @@ defineExpose({
position: relative;
padding: 0 32px 0 32px;
display: flex;
flex-wrap: wrap;
z-index: 1;
&:first-child {
margin-top: 20px;
@ -809,6 +958,16 @@ defineExpose({
margin-right: 4px;
}
.avatar {
width: 1.2em;
height: 1.2em;
border-radius: 2em;
overflow: hidden;
margin-right: 0.4em;
background: var(--panelHighlight);
transform: translateY(-4px);
}
> span {
overflow: hidden;
flex-shrink: 1;

View File

@ -49,8 +49,6 @@
</template>
<script lang="ts" setup>
import { ref } from "vue";
import type { entities } from "firefish-js";
import { defaultStore } from "@/store";
import MkVisibility from "@/components/MkVisibility.vue";
@ -67,18 +65,16 @@ const props = defineProps<{
canOpenServerInfo?: boolean;
}>();
const note = ref(props.note);
const showTicker =
defaultStore.state.instanceTicker === "always" ||
(defaultStore.state.instanceTicker === "remote" && note.value.user.instance);
(defaultStore.state.instanceTicker === "remote" && props.note.user.instance);
function openServerInfo() {
if (!props.canOpenServerInfo || !defaultStore.state.openServerInfo) return;
const instanceInfoUrl =
note.value.user.host == null
props.note.user.host == null
? "/about"
: `/instance-info/${note.value.user.host}`;
: `/instance-info/${props.note.user.host}`;
pageWindow(instanceInfoUrl);
}
</script>

View File

@ -1,5 +1,10 @@
<template>
<div v-size="{ min: [350, 500] }" class="yohlumlk">
<div
v-show="!deleted"
ref="el"
v-size="{ min: [350, 500] }"
class="yohlumlk"
>
<MkAvatar class="avatar" :user="note.user" />
<div class="main">
<XNoteHeader class="header" :note="note" :mini="true" />
@ -12,13 +17,42 @@
<script lang="ts" setup>
import type { entities } from "firefish-js";
import { computed, ref, watch } from "vue";
import XNoteHeader from "@/components/MkNoteHeader.vue";
import MkSubNoteContent from "@/components/MkSubNoteContent.vue";
import { deepClone } from "@/scripts/clone";
import { useNoteCapture } from "@/scripts/use-note-capture";
import { isDeleted } from "@/scripts/note";
defineProps<{
const props = defineProps<{
note: entities.Note;
pinned?: boolean;
}>();
const rootEl = ref<HTMLElement | null>(null);
const note = ref(deepClone(props.note));
const deleted = computed(() => isDeleted(note.value.id));
let capture = useNoteCapture({
note,
rootEl,
});
function reload() {
note.value = deepClone(props.note);
capture.close();
capture = useNoteCapture({
note,
rootEl,
});
}
watch(
() => props.note.id,
(o, n) => {
if (o === n) return;
reload();
},
);
</script>
<style lang="scss" scoped>

View File

@ -3,6 +3,7 @@
ref="pagingComponent"
:pagination="pagination"
:disable-auto-load="disableAutoLoad"
:folder
>
<template #empty>
<div class="_fullinfo">
@ -15,7 +16,7 @@
</div>
</template>
<template #default="{ items: notes }">
<template #default="{ foldedItems: notes }">
<div ref="tlEl" class="giivymft" :class="{ noGap }">
<XList
ref="notes"
@ -28,6 +29,21 @@
class="notes"
>
<XNote
v-if="'folded' in note && note.folded === 'thread'"
:key="note.id"
class="qtqtichx"
:note="note.note"
:parents="note.parents"
/>
<XNote
v-else-if="'folded' in note && note.folded === 'renote'"
:key="note.key"
class="qtqtichx"
:note="note.note"
:renotes="note.renotesArr"
/>
<XNote
v-else
:key="note._featuredId_ || note._prId_ || note.id"
class="qtqtichx"
:note="note"
@ -51,14 +67,21 @@ import XList from "@/components/MkDateSeparatedList.vue";
import MkPagination from "@/components/MkPagination.vue";
import { i18n } from "@/i18n";
import { scroll } from "@/scripts/scroll";
import type { NoteFolded, NoteThread, NoteType } from "@/types/note";
const tlEl = ref<HTMLElement>();
defineProps<{
pagination: PagingOf<entities.Note>;
noGap?: boolean;
disableAutoLoad?: boolean;
}>();
withDefaults(
defineProps<{
pagination: PagingOf<entities.Note>;
noGap?: boolean;
disableAutoLoad?: boolean;
folder?: (ns: entities.Note[]) => (NoteType | NoteThread | NoteFolded)[];
}>(),
{
folder: (ns: entities.Note[]) => ns,
},
);
const pagingComponent = ref<MkPaginationType<
PagingKeyOf<entities.Note>

View File

@ -79,29 +79,35 @@ const stream = useStream();
const pagingComponent = ref<MkPaginationType<"i/notifications"> | null>(null);
const shouldFold = defaultStore.state.foldNotification;
const shouldFold = defaultStore.reactiveState.foldNotification;
const convertNotification = computed(() =>
shouldFold.value ? foldNotifications : (ns: entities.Notification[]) => ns,
);
const FETCH_LIMIT = 90;
const pagination = Object.assign(
{
endpoint: "i/notifications" as const,
params: computed(() => ({
includeTypes: props.includeTypes ?? undefined,
excludeTypes: props.includeTypes
? undefined
: me?.mutingNotificationTypes,
unreadOnly: props.unreadOnly,
})),
},
shouldFold
? {
limit: 50,
secondFetchLimit: FETCH_LIMIT,
}
: {
limit: 30,
},
const pagination = computed(() =>
Object.assign(
{
endpoint: "i/notifications" as const,
params: computed(() => ({
includeTypes: props.includeTypes ?? undefined,
excludeTypes: props.includeTypes
? undefined
: me?.mutingNotificationTypes,
unreadOnly: props.unreadOnly,
})),
},
shouldFold.value
? {
limit: 50,
secondFetchLimit: FETCH_LIMIT,
}
: {
limit: 30,
},
),
);
function isNoteNotification(
@ -138,14 +144,6 @@ const onNotification = (notification: entities.Notification) => {
let connection: StreamTypes.ChannelOf<"main"> | undefined;
function convertNotification(ns: entities.Notification[]) {
if (shouldFold) {
return foldNotifications(ns);
} else {
return ns;
}
}
onMounted(() => {
connection = stream.useChannel("main");
connection.on("notification", onNotification);

View File

@ -365,9 +365,9 @@ async function fetch(firstFetching?: boolean) {
}
// biome-ignore lint/style/noParameterAssign: assign it intentially
res = res.filter((item) => {
if (idMap.has(item)) return false;
idMap.set(item, true);
res = res.filter((it) => {
if (idMap.has(it.id)) return false;
idMap.set(it.id, true);
return true;
});
}
@ -435,8 +435,20 @@ const prepend = (...item: Item[]): void => {
}
};
const append = (...items: Item[]): void => {
appended.value.push(...items);
const append = (...it: Item[]): void => {
// If there are too many appended, merge them into arrItems
if (
appended.value.length >
(props.pagination.secondFetchLimit || SECOND_FETCH_LIMIT_DEFAULT)
) {
for (const item of appended.value) {
idMap.set(item.id, true);
}
arrItems.value.push(appended.value);
appended.value = [];
// We don't need to calculate here because it won't cause any changes in items
}
appended.value.push(...it);
calculateItems();
};
@ -486,6 +498,8 @@ if (props.pagination.params && isRef<Param>(props.pagination.params)) {
watch(props.pagination.params, reload, { deep: true });
}
watch(() => props.folder, calculateItems);
watch(
queue,
(a, b) => {

View File

@ -852,13 +852,17 @@ function setLanguage() {
actions.push(null);
}
if (language.value != null)
if (language.value != null && langmap[language.value] != null) {
actions.push({
text: langmap[language.value].nativeName,
danger: false,
active: true,
action: () => {},
});
} else {
// Unrecognized language, set to null
language.value = null;
}
const langs = Object.keys(langmap);

View File

@ -160,7 +160,7 @@ const hCaptchaResponse = ref(null);
const reCaptchaResponse = ref(null);
const emit = defineEmits<{
(ev: "login", v: any): void;
login: [v: { id: string; i: string }];
}>();
const props = defineProps({

View File

@ -30,7 +30,7 @@ withDefaults(
);
const emit = defineEmits<{
(ev: "done"): void;
(ev: "done", res: { id: string; i: string }): void;
(ev: "closed"): void;
(ev: "cancelled"): void;
}>();
@ -39,11 +39,11 @@ const dialog = ref<InstanceType<typeof XModalWindow>>();
function onClose() {
emit("cancelled");
dialog.value.close();
dialog.value!.close();
}
function onLogin(res) {
function onLogin(res: { id: string; i: string }) {
emit("done", res);
dialog.value.close();
dialog.value!.close();
}
</script>

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