82 Commits

Author SHA1 Message Date
8a9502d1f2 ssb: More correct/thorough channel status.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m50s
2025-05-31 17:55:49 -04:00
534438df63 ssb: The message_refs table had loads of erroneous entries. This fixes that and adds channel/hashtag refs.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-31 17:42:17 -04:00
45a4feec96 ssb: Fix a use of undefined.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m36s
2025-05-31 16:52:37 -04:00
aa7a32395e ssb: Expose last successful connection time for stored connections.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-31 16:33:48 -04:00
ab9f57f044 ssb: Support expanding profile images the same as other images. #122
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-31 16:11:13 -04:00
4040d6aa08 ssb: Hide 'Mark as read' when we're not in a channel with a read status. #122
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m52s
2025-05-31 15:23:07 -04:00
1c96f5c35e ssb: Slight tweaks to 'This message is not currently available.' 2025-05-31 15:20:55 -04:00
4d3e42812d ssb: Condense follows/blocks more, and support replies to them. #122 2025-05-31 15:17:07 -04:00
f7b3711d4f ssb: Bury placeholder message blob ids in a context menu. #122
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 34m4s
2025-05-31 11:44:50 -04:00
2408e076ff ssb: Indicate connection status with some colors in the connections list in the sidebar. #122
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-31 11:28:09 -04:00
6f71ffb477 ssb: Treat most plaintext votes as 👍. #122
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m12s
2025-05-31 10:52:07 -04:00
214433f36a update: CodeMirror.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m41s
2025-05-29 12:39:31 -04:00
309b22732e update: sqlite 3.50.0.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-29 12:37:14 -04:00
6fe7687b2a docs: Update the screenshots.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m44s
2025-05-28 20:49:48 -04:00
a8cbf757ff build: Let's start work on 0.0.32. nix => 0.0.31.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m47s
2025-05-28 18:12:15 -04:00
4a4bedfe2b docs: Fix the changelog version. Hmm.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m2s
2025-05-28 12:28:33 -04:00
051291f725 build: Let's build 0.0.31.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-28 12:27:37 -04:00
d2b338095f update: CodeMirror.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-28 12:26:42 -04:00
899827a8f2 ssb: Rename broadcasts to something not worse.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m41s
2025-05-27 21:09:26 -04:00
5fcbe3d6a9 docs: Prep for 0.0.31.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m31s
2025-05-27 12:44:33 -04:00
a0a40e6cb2 ssb: Implement blob file export from CLI. #121
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-27 12:30:24 -04:00
bb1190e3f8 intro: Ugg, CSS.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m8s
2025-05-22 12:00:33 -04:00
0a3baed1da intro: Wordsmith some more, and fiddle with style.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-22 11:49:58 -04:00
4931c489ed ssb: Dispatch blob wants immediately when added as the result of a web request.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m24s
2025-05-22 10:56:30 -04:00
996f9abaa2 ssb: Suppress the 'set your profile' banner if we're still loading abouts.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-22 10:44:41 -04:00
08c097e176 ssb: Fix a source of stale user information. 2025-05-22 10:26:59 -04:00
daa861a98b test: Test the intro flow. 2025-05-22 10:19:55 -04:00
a25d08fd76 intro: Skipped a step.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m35s
2025-05-22 07:04:57 -04:00
392d31cc53 format
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m10s
2025-05-21 22:16:52 -04:00
92926fa8df ssb: Chasing intermittent SQLITE_ERROR with multiple processes accessing the database. Switching to sqlite3_prepare_v2 for better errors, but maybe this is also the solution? 2025-05-21 22:05:33 -04:00
61ae9ae465 intro: Set an emoji, obviously.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m17s
2025-05-21 18:51:35 -04:00
89622697d5 intro: The default app is intro, and complete intro changes the default app to ssb.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-21 18:48:29 -04:00
17694f5646 update: CodeMirror.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-21 18:20:01 -04:00
5a1303149f intro: Add an intro app that aims to ease in completely new users. Needs some clean-up and to be hooked into the flow. #19
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-21 18:08:19 -04:00
8a0e190a86 ssb: Clean up outdated dynamic blob_wants_cache entries.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-21 17:44:00 -04:00
0d7dfd8c9e ssb: Get the 'encrypt to' input in line with everything else style-wise.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 34m14s
2025-05-18 07:55:10 -04:00
f979ff7050 ssb: Speculation on why I sometimes don't see names update.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m0s
2025-05-17 20:10:15 -04:00
e3fcdea362 ssb: Add entries to the blob wants cache when requesting a missing blob from the web. #120
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 34m7s
2025-05-17 12:46:47 -04:00
476fec2757 update: w3.css 5.02.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 37m55s
2025-05-14 22:19:53 -04:00
53c215399b ssb: Ugg, better safe than sorry.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m29s
2025-05-14 20:57:42 -04:00
2c330802da ssb: More account info fixes.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-14 20:56:13 -04:00
851d7046ea ssb: Fix failure to gather about information sometimes.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-14 20:39:06 -04:00
c0019d7246 format
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-14 20:33:02 -04:00
7688e4d3a8 ssb: I tried smarter data structures in my own code, but a better database index shows better performance for determining follows.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-14 20:13:14 -04:00
ef58749ce3 perf: Make -t following_perf respect the usual db path rules.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m33s
2025-05-12 12:41:13 -04:00
35941a7ddc perf: Add a quick test to measure ssb.following performance.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-12 12:32:41 -04:00
1f2664e5a8 ssb: Reduce redundant work in ssb.following.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m15s
2025-05-11 22:20:16 -04:00
35656a5c34 prettier 2025-05-11 21:54:53 -04:00
799f22e989 ssb: The updated fetch_abouts means that image JSON is now a string we need to handle in tf-user / tf-profile.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m16s
2025-05-11 21:42:48 -04:00
e226a37251 prettier
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m40s
2025-05-09 19:54:12 -04:00
8e3bc9d700 ssb: Now add back the about cache, and load times are getting good.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m43s
2025-05-09 13:25:05 -04:00
58c3e6c2ab build: Fix a -fanalyze issue in the tests.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m32s
2025-05-09 07:55:39 -04:00
0dc148bfea ssb: Don't forget the about info we know as we reload.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 4m53s
2025-05-09 07:35:52 -04:00
3eff1b08a9 ssb: Let about info load in its own time.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 4m45s
2025-05-09 07:27:35 -04:00
02d789471f ssb: Oops, filter on relevant accounts.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 4m33s
2025-05-08 12:58:57 -04:00
d367d47c4d ssb: Switch to a more brute force about-gathering approach. I think if I start with this and avoid querying all known accounts up-front, we will be plenty fast.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 5m12s
2025-05-08 12:39:26 -04:00
c93b8fc045 ssb: Show overall unread status on the hamburger menu.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 4m34s
2025-05-07 19:59:39 -04:00
eb9377e21d ssb: Add settings for broadcast and discovery.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 4m34s
2025-05-07 19:05:57 -04:00
a1764eee42 update: CodeMirror.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 5m16s
2025-05-07 18:37:23 -04:00
86ef74e20d test: More CLI tests, and make -u optional for publish, too.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 5m11s
2025-05-07 18:31:58 -04:00
4de53b9926 core: sqlite checkpoint tweaks. Switch the periodic checkpoint to passive mode. Truncate mode was painful on mobile. I'm seeing it succeed often enough that we don't grow indefinitely. Also fix the print.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-07 18:15:33 -04:00
99a195a3fd update: sqlite 3.49.2. 2025-05-07 18:00:30 -04:00
f1ced31f69 ssb: Faster loads by encouraging the right queries with CTEs in fetch_about.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-07 17:52:02 -04:00
b3cedf2baa ssb: Begin to add some CLI tests.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m19s
2025-05-06 12:49:57 -04:00
3bf19fabda ssb: Don't require -u for the private command. #119
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m44s
2025-05-05 21:51:38 -04:00
cf81ebe8ad ssb: emoji-fy other cases of some reactions.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-05 21:32:40 -04:00
278b5566a1 ssb: Remove some unintended whitespace at the bottom of messages.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-05 21:09:58 -04:00
e8c1390f09 ssb: Reply arrow that shows up better on my phone.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m57s
2025-05-01 12:38:11 -04:00
3c04abda45 ssb: More shutdown correctness. #108
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m26s
2025-04-30 12:35:40 -04:00
2597f99ccf ssb: Get recent reactions up front so that we don't need to delay showing the dialog for them.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m33s
2025-04-29 20:48:47 -04:00
9d3a07c1cf build: OpenBSD 7.7's SSL matches these signatures again.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m57s
2025-04-27 17:29:59 -04:00
bdfd8925b5 ssb: Remove some extra margin that slipped in.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m13s
2025-04-27 10:18:37 -04:00
1a4d1985f4 core: Revisit drag+drop into the editor. Works better but not as I intended. Use it just to update the screenshot in the welcome app.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-27 10:18:20 -04:00
6273f3ea53 ssb: Make the connections sidebar header also a link to the connections tab.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m18s
2025-04-26 18:53:08 -04:00
5bdc6fa471 update: CodeMirror.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-26 18:47:04 -04:00
3ba41291db update: QuickJS 2025-04-26.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-26 18:30:56 -04:00
0867811952 update: libuv 1.51.0.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m0s
2025-04-26 09:06:37 -04:00
8d961cd805 ssb: Indicate that content warnings can be expanded.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m38s
2025-04-24 12:35:36 -04:00
97cea7b40b build: Attempt to include native debug symbols in the Android .aab file.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m58s
2025-04-23 20:47:31 -04:00
4106834db8 build: nix flake update did this (but really it was nix --extra-experimental-features nix-command --extra-experimental-features flakes flake update). I don't know how any of this actually works.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m7s
2025-04-23 18:36:28 -04:00
a4a8f7cab2 build: Let's start work on 0.0.31.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-23 18:33:28 -04:00
9e209ee800 build: nix => 0.0.30. 2025-04-23 18:19:58 -04:00
63 changed files with 5548 additions and 2511 deletions

View File

@@ -16,14 +16,14 @@ MAKEFLAGS += --no-builtin-rules
## LD := Linker. ## LD := Linker.
## ANDROID_SDK := Path to the Android SDK. ## ANDROID_SDK := Path to the Android SDK.
VERSION_CODE := 35 VERSION_CODE := 38
VERSION_CODE_IOS := 12 VERSION_CODE_IOS := 14
VERSION_NUMBER := 0.0.30 VERSION_NUMBER := 0.0.32-wip
VERSION_NAME := This program kills fascists. VERSION_NAME := This program kills fascists.
IPHONEOS_VERSION_MIN=14.0 IPHONEOS_VERSION_MIN=14.0
SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3490100.zip SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3500000.zip
BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.17.0/bundletool-all-1.17.0.jar BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.17.0/bundletool-all-1.17.0.jar
APPIMAGETOOL_URL := https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage APPIMAGETOOL_URL := https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
APPIMAGETOOL_MD5 := e989fadfc4d685fd3d6aeeb9b525d74d out/appimagetool APPIMAGETOOL_MD5 := e989fadfc4d685fd3d6aeeb9b525d74d out/appimagetool
@@ -614,15 +614,16 @@ $(UV_OBJS): CFLAGS += \
-Ideps/libuv/include \ -Ideps/libuv/include \
-Ideps/libuv/src \ -Ideps/libuv/src \
-Wno-dangling-pointer \ -Wno-dangling-pointer \
-Wno-format-truncation \
-Wno-incompatible-pointer-types \ -Wno-incompatible-pointer-types \
-Wno-maybe-uninitialized \ -Wno-maybe-uninitialized \
-Wno-nonnull \
-Wno-sign-compare \ -Wno-sign-compare \
-Wno-unknown-attributes \ -Wno-unknown-attributes \
-Wno-unused-but-set-parameter \ -Wno-unused-but-set-parameter \
-Wno-unused-but-set-variable \ -Wno-unused-but-set-variable \
-Wno-unused-result \ -Wno-unused-result \
-Wno-unused-variable \ -Wno-unused-variable
-Wno-nonnull
$(filter out/win%,$(UV_OBJS)): \ $(filter out/win%,$(UV_OBJS)): \
CFLAGS += \ CFLAGS += \
-Wno-cast-function-type \ -Wno-cast-function-type \
@@ -742,7 +743,7 @@ $(SQLITE_OBJS): CFLAGS += \
QUICKJS_SOURCES := \ QUICKJS_SOURCES := \
deps/quickjs/cutils.c \ deps/quickjs/cutils.c \
deps/quickjs/libbf.c \ deps/quickjs/dtoa.c \
deps/quickjs/libregexp.c \ deps/quickjs/libregexp.c \
deps/quickjs/libunicode.c \ deps/quickjs/libunicode.c \
deps/quickjs/quickjs.c deps/quickjs/quickjs.c
@@ -1015,7 +1016,7 @@ $(BUNDLETOOL):
@curl -q -L --create-dirs -o $@ $(BUNDLETOOL_URL) @curl -q -L --create-dirs -o $@ $(BUNDLETOOL_URL)
out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGETS)) $(RAW_FILES) out/apk/res.apk src/android/AndroidManifest.xml $(BUNDLETOOL) out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGETS)) $(RAW_FILES) out/apk/res.apk src/android/AndroidManifest.xml $(BUNDLETOOL)
@rm -rf out/aab/staging/ @rm -rf out/aab/staging/ out/aab/base.zip
@mkdir -p out/aab/staging @mkdir -p out/aab/staging
@$(ANDROID_BUILD_TOOLS)/aapt2 link --proto-format -o out/aab/temporary.apk \ @$(ANDROID_BUILD_TOOLS)/aapt2 link --proto-format -o out/aab/temporary.apk \
-I $(ANDROID_PLATFORM)/android.jar \ -I $(ANDROID_PLATFORM)/android.jar \
@@ -1035,14 +1036,11 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET
@cp out/apk/classes.dex out/aab/staging/dex/ @cp out/apk/classes.dex out/aab/staging/dex/
@rm -fv out/base.zip @rm -fv out/base.zip
@mkdir -p out/aab/staging/lib/arm64-v8a out/aab/staging/lib/armeabi-v7a out/aab/staging/lib/x86_64 out/aab/staging/lib/x86 @mkdir -p out/aab/staging/lib/arm64-v8a out/aab/staging/lib/armeabi-v7a out/aab/staging/lib/x86_64 out/aab/staging/lib/x86
@cp out/androidrelease/tildefriends out/aab/staging/lib/arm64-v8a/libtildefriends.so @mkdir -p out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64 out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86
@cp out/androidrelease-armv7a/tildefriends out/aab/staging/lib/armeabi-v7a/libtildefriends.so @$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease/tildefriends -o out/aab/staging/lib/arm64-v8a/libtildefriends.so
@cp out/androidrelease-x86_64/tildefriends out/aab/staging/lib/x86_64/libtildefriends.so @$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-armv7a/tildefriends -o out/aab/staging/lib/armeabi-v7a/libtildefriends.so
@cp out/androidrelease-x86/tildefriends out/aab/staging/lib/x86/libtildefriends.so @$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-x86_64/tildefriends -o out/aab/staging/lib/x86_64/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/arm64-v8a/libtildefriends.so @$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-x86/tildefriends -o out/aab/staging/lib/x86/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/armeabi-v7a/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/x86_64/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/x86/libtildefriends.so
@cp -r apps/ out/aab/staging/root/ @cp -r apps/ out/aab/staging/root/
@rm -rf out/aab/staging/root/apps/welcome* @rm -rf out/aab/staging/root/apps/welcome*
@cp -r core/ out/aab/staging/root/ @cp -r core/ out/aab/staging/root/
@@ -1051,6 +1049,11 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET
@cp -r deps/codemirror/ out/aab/staging/root/deps/ @cp -r deps/codemirror/ out/aab/staging/root/deps/
@cd out/aab/staging/; zip -r ../base.zip *; cd ../../../ @cd out/aab/staging/; zip -r ../base.zip *; cd ../../../
@java -jar $(BUNDLETOOL) build-bundle --overwrite --config=src/android/BundleConfig.json --modules=out/aab/base.zip --output=$@ @java -jar $(BUNDLETOOL) build-bundle --overwrite --config=src/android/BundleConfig.json --modules=out/aab/base.zip --output=$@
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/libtildefriends.so.sym
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-armv7a/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a/libtildefriends.so.sym
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-x86_64/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64/libtildefriends.so.sym
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-x86/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86/libtildefriends.so.sym
@cd out/aab/staging; zip -u ../../../$@ BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86/libtildefriends.so.sym; cd ../../../
@$(ANDROID_BUILD_TOOLS)/apksigner sign -ks .keys/android.jks --ks-key-alias androidKey -ks-pass pass:android --min-sdk-version=$(ANDROID_MIN_SDK_VERSION) $@ @$(ANDROID_BUILD_TOOLS)/apksigner sign -ks .keys/android.jks --ks-key-alias androidKey -ks-pass pass:android --min-sdk-version=$(ANDROID_MIN_SDK_VERSION) $@
aab: out/TildeFriends.aab ## Build an Android App Bundle. aab: out/TildeFriends.aab ## Build an Android App Bundle.

View File

@@ -1,4 +1,4 @@
/* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */ /* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ /* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0} html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
@@ -108,10 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px} .w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px} .w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px} .w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex} .w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic} .w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px} .w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word} .w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%} .w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
@@ -152,10 +150,11 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-button:hover{color:#000!important;background-color:#ccc!important} .w3-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important} .w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important} .w3-hover-none:hover{box-shadow:none!important}
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
/* Colors */ /* Colors */
.w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important} .w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important} .w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
.w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important} .w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important} .w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important} .w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important} .w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
@@ -170,24 +169,28 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important} .w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important} .w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important} .w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
.w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important} .w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important} .w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important} .w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
.w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important} .w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important} .w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important} .w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important}
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important} .w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important} .w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important} .w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important} .w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
.w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important} .w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important} .w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important} .w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important} .w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
.w3-danger{color:#fff!important;background-color:#dd0000!important}
.w3-note{color:#000!important;background-color:#fff599!important}
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
.w3-warning{color:#000!important;background-color:#ffb305!important}
.w3-success{color:#fff!important;background-color:#008a00!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important} .w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important} .w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important} .w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}

View File

@@ -1,4 +1,4 @@
/* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */ /* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ /* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0} html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
@@ -108,10 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px} .w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px} .w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px} .w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex} .w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic} .w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px} .w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word} .w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%} .w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
@@ -152,10 +150,11 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-button:hover{color:#000!important;background-color:#ccc!important} .w3-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important} .w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important} .w3-hover-none:hover{box-shadow:none!important}
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
/* Colors */ /* Colors */
.w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important} .w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important} .w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
.w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important} .w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important} .w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important} .w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important} .w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
@@ -170,24 +169,28 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important} .w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important} .w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important} .w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
.w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important} .w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important} .w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important} .w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
.w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important} .w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important} .w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important} .w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important}
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important} .w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important} .w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important} .w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important} .w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
.w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important} .w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important} .w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important} .w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important} .w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
.w3-danger{color:#fff!important;background-color:#dd0000!important}
.w3-note{color:#000!important;background-color:#fff599!important}
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
.w3-warning{color:#000!important;background-color:#ffb305!important}
.w3-success{color:#fff!important;background-color:#008a00!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important} .w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important} .w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important} .w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}

View File

@@ -1,4 +1,4 @@
/* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */ /* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ /* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0} html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
@@ -108,10 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px} .w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px} .w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px} .w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex} .w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic} .w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px} .w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word} .w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%} .w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
@@ -152,10 +150,11 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-button:hover{color:#000!important;background-color:#ccc!important} .w3-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important} .w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important} .w3-hover-none:hover{box-shadow:none!important}
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
/* Colors */ /* Colors */
.w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important} .w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important} .w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
.w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important} .w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important} .w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important} .w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important} .w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
@@ -170,24 +169,28 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important} .w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important} .w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important} .w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
.w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important} .w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important} .w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important} .w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
.w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important} .w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important} .w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important} .w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important}
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important} .w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important} .w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important} .w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important} .w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
.w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important} .w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important} .w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important} .w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important} .w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
.w3-danger{color:#fff!important;background-color:#dd0000!important}
.w3-note{color:#000!important;background-color:#fff599!important}
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
.w3-warning{color:#000!important;background-color:#ffb305!important}
.w3-success{color:#fff!important;background-color:#008a00!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important} .w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important} .w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important} .w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}

5
apps/intro.json Normal file
View File

@@ -0,0 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "💡",
"previous": "&ckE7T/dt9f1xV8jNSgXVcXYkAzMhU9689MRQbhOi9Wo=.sha256"
}

13
apps/intro/app.js Normal file
View File

@@ -0,0 +1,13 @@
import * as tfrpc from '/tfrpc.js';
async function main() {
await app.setDocument(utf8Decode(getFile('index.html')));
}
tfrpc.register(async function complete() {
if ((await core.globalSettingsGet('index')) == '/~core/intro/') {
return await core.globalSettingsSet('index', '/~core/ssb/');
}
});
main();

286
apps/intro/index.html Normal file
View File

@@ -0,0 +1,286 @@
<!doctype html>
<html style="height: 100%; margin: 0; padding: 0; box-sizing: border-box">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" type="text/css" href="w3.css" />
<style>
.slide {
display: none;
margin-left: auto;
margin-right: auto;
}
.dot {
width: 1em;
height: 1em;
cursor: pointer;
}
.w3-left,
.w3-right {
cursor: pointer;
}
</style>
</head>
<body
style="
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
margin: 0;
padding: 0;
flex-direction: column;
align-items: center;
"
class="w3-flex w3-dark-gray w3-center"
>
<div
style="
flex: 1 1 auto;
overflow: auto;
contain: content;
padding-top: 16px;
padding-bottom: 16px;
"
>
<div class="slide">
<div
class="w3-content w3-xlarge w3-card-4 w3-blue w3-panel w3-padding-32 w3-round-xlarge"
style="margin: 32px"
>
<div>
<div>Welcome to</div>
<div>~😎 Tilde Friends.</div>
</div>
<footer>
<button class="w3-button w3-yellow proceed">Next</button>
</footer>
</div>
</div>
<div class="slide w3-card-4 w3-gray" style="width: 90%">
<header class="w3-container w3-blue w3-xlarge">
<h1>This brief tutorial will introduce:</h1>
</header>
<ul class="w3-large w3-left-align">
<li><b>Secure Scuttlebutt</b>, a decentralized social network.</li>
<li>
<b>Tilde Friends</b>, the application platform that you are using
right now.
</li>
<li>
<b>How to get started</b> if you want to get gossiping right away.
</li>
</ul>
<footer class="w3-center w3-xlarge w3-padding">
<button class="w3-button w3-yellow proceed">Onward</button>
</footer>
</div>
<div class="slide w3-gray" style="width: 90%">
<div class="w3-card-4 w3-xlarge">
<header class="w3-container w3-blue">
<h1>💻Secure Scuttlebutt in a Nutshell🦀</h1>
</header>
<div class="w3-container w3-large w3-left-align">
<p>
Secure Scuttlebutt is a social network whose technical operation
attempts to mirrors human social interaction.
</p>
<ul>
<li>
You can create your own account and post to your own feed on
your own device. This is all <b>local</b> with no external
communication. This puts you fully in control of your own words
and actions.
</li>
<li>
Before you can interact with others, you need to
<b>connect over the network</b>, either directly to your friends
(i.e., peer-to-peer between your phones on coffee shop Wi-Fi) or
to 🚪<i>rooms</i> and 🍻<i>pubs</i> (hint: search the web for
<i>#ssbroom</i>).
</li>
<li>
Who you choose to <b>follow</b> determines what you see, with
most people choosing to see messages from friends and friends of
those friends. If you encounter content you'd rather not see,
<b>block</b> the offending account to improve the experience for
you and your followers.
</li>
<li>
Your feed is an <b>immutable</b> log of your activity. Post with
care, because like your words in real life, posts can't be taken
back.
</li>
</ul>
</div>
<footer class="w3-center w3-xlarge w3-padding">
<a
class="w3-button w3-light-gray"
href="https://scuttlebutt.nz/"
target="_blank"
>See scuttlebutt.nz</a
>
<button class="w3-button w3-yellow proceed">Got It</button>
</footer>
</div>
</div>
<div class="slide w3-gray" style="width: 90%">
<div class="w3-card-4 w3-xlarge">
<header class="w3-container w3-blue w3-center">
<h1>~😎 Let's Talk Tilde Friends ~😎</h1>
</header>
<div class="w3-container w3-large w3-left-align">
<p>
Tilde Friends is an application platform that is an application of
its own.
</p>
<ul>
<li>
This intro is a Tilde Friends app. You can click <b>edit</b> at
the top to look under the hood and make changes.
</li>
<li>
It is already possible to make and share new applications using
only Tilde Friends and Secure Scuttlebutt without having to set
up development environments, configure web servers, register
domain names, or pay for hosting services.
</li>
<li>
But it's also set up so that you can't just break an app that
everybody is using or do malicious things with personal content.
There are <b>protections</b> in place like an operating system.
The intent is also for it to be <b>safe</b> to run strange apps
without worrying about adverse effects.
</li>
<li>
But this is all a big 🚧work in progress🚧 and
<b>experiment</b>. Let's see where it takes us.
</li>
</ul>
</div>
<footer class="w3-center w3-xlarge w3-padding">
<button class="w3-button w3-yellow proceed">Okay</button>
</footer>
</div>
</div>
<div class="slide w3-gray" style="width: 90%">
<div class="w3-card-4 w3-xlarge">
<header class="w3-container w3-blue w3-center">
<h1>🦀Let's Get this Tilde Friends Party Started🎉</h1>
</header>
<div class="w3-container w3-large w3-left-align">
<p>The button below will take you to the Secure Scuttlebutt app.</p>
<ul>
<li>
Remember:
<ol>
<li>You are in charge. This is all on your device.</li>
<li>
Make network connections to exchange messages with others.
</li>
<li>
Follow more accounts to see more content, and block those
posting content you'd rather not see.
</li>
<li>
Be respectful, and consider the consequences of what you
post.
</li>
<li>
This is all under active development. Exercise patience, and
report issues encountered.
</li>
</ol>
</li>
<li>
To see this tutorial again later, select <b>apps</b> -&gt;
<b>Core Apps</b> -&gt; <b>intro</b>.
</li>
</ul>
</div>
<footer class="w3-center w3-xlarge w3-padding">
<button class="w3-button w3-yellow" id="complete">Let's Go!</button>
</footer>
</div>
</div>
</div>
<div
class="w3-text-white w3-xlarge w3-center w3-flex"
style="
width: 100%;
flex: 0 1;
flex-direction: row;
align-items: center;
gap: 8px;
"
>
<div class="w3-jumbo" id="left" style="flex: 1 0; cursor: pointer">
&#10094;
</div>
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
<div class="w3-jumbo" style="flex: 1 0; cursor: pointer" id="right">
&#10095;
</div>
</div>
<script type="module">
import * as tfrpc from '/static/tfrpc.js';
let index = 0;
function set(i) {
show(i - index);
}
function show(delta) {
let slides = [...document.getElementsByClassName('slide')];
let dots = [...document.getElementsByClassName('dot')];
index = (index + delta + slides.length) % slides.length;
for (let slide of slides) {
slide.style.display =
slides.indexOf(slide) == index ? 'block' : 'none';
}
for (let dot of dots) {
if (dots.indexOf(dot) == index) {
dot.classList.add('w3-white');
} else {
dot.classList.remove('w3-white');
}
}
document.getElementById('left').style.visibility =
index == 0 ? 'hidden' : 'visible';
document.getElementById('right').style.visibility =
index == slides.length - 1 ? 'hidden' : 'visible';
}
let dots = [...document.getElementsByClassName('dot')];
for (let dot of dots) {
dot.onclick = () => set(dots.indexOf(dot));
}
for (let button of document.getElementsByClassName('proceed')) {
button.onclick = () => show(1);
}
document.getElementById('left').onclick = () => show(-1);
document.getElementById('right').onclick = () => show(1);
document.getElementById('complete').onclick = function () {
console.log('completing');
tfrpc.rpc.complete().finally(function () {
console.log('completed');
let a = document.createElement('a');
a.href = '/~core/ssb/';
a.target = '_top';
document.body.appendChild(a);
a.click();
});
};
window.addEventListener('keyup', function (event) {
if (event.key == 'ArrowLeft') {
show(-1);
} else if (event.key == 'ArrowRight') {
show(1);
}
});
show(0);
</script>
</body>
</html>

251
apps/intro/w3.css Normal file
View File

@@ -0,0 +1,251 @@
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item}
audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline}
audio:not([controls]){display:none;height:0}[hidden],template{display:none}
a{background-color:transparent}a:active,a:hover{outline-width:0}
abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}
b,strong{font-weight:bolder}dfn{font-style:italic}mark{background:#ff0;color:#000}
small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none}
code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible}
button,input,select,textarea,optgroup{font:inherit;margin:0}optgroup{font-weight:bold}
button,input{overflow:visible}button,select{text-transform:none}
button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}
button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}
button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}
fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}
legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}
[type=checkbox],[type=radio]{padding:0}
[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}
[type=search]{-webkit-appearance:textfield;outline-offset:-2px}
[type=search]::-webkit-search-decoration{-webkit-appearance:none}
::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}
/* End extract */
html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden}
h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px}
.w3-serif{font-family:serif}.w3-sans-serif{font-family:sans-serif}.w3-cursive{font-family:cursive}.w3-monospace{font-family:monospace}
h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px}
hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit}
.w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc}
.w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1}
.w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1}
.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center}
.w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top}
.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px}
.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap}
.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}
.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none}
.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none}
.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%}
.w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none}
.w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block}
.w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s}
.w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%}
.w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc}
.w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer}
.w3-dropdown-hover:hover .w3-dropdown-content{display:block}
.w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000}
.w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000}
.w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1}
.w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px}
.w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto}
.w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%}
.w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%}
.w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px}
.w3-main,#main{transition:margin-left .4s}
.w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)}
.w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px}
.w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto}
.w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0}
.w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left}
.w3-bar .w3-button{white-space:normal}
.w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0}
.w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%}
.w3-responsive{display:block;overflow-x:auto}
.w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before,
.w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both}
.w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%}
.w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%}
.w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%}
.w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%}
@media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%}
.w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%}
.w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}}
@media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%}
.w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%}
.w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}}
.w3-rest{overflow:hidden}.w3-stretch{margin-left:-16px;margin-right:-16px}
.w3-content,.w3-auto{margin-left:auto;margin-right:auto}.w3-content{max-width:980px}.w3-auto{max-width:1140px}
.w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell}
.w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom}
.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important}
@media (max-width:1205px){.w3-auto{max-width:95%}}
@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px}
.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}
.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center}
.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}}
@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}}
@media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}}
@media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}}
@media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}.w3-auto{max-width:100%}}
.w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0}
.w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2}
.w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0}
.w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0}
.w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)}
.w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)}
.w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)}
.w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
.w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
.w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none}
.w3-display-position{position:absolute}
.w3-circle{border-radius:50%}
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
.w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)}
.w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)}
.w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}
.w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}}
.w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}}
.w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}}
.w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}}
.w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}}
.w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}}
.w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}}
.w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important}
.w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1}
.w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75}
.w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)}
.w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)}
.w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)}
.w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important}
.w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important}
.w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important}
.w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important}
.w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important}
.w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important}
.w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important}
.w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important}
.w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important}
.w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important}
.w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important}
.w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important}
.w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important}
.w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important}
.w3-padding-64{padding-top:64px!important;padding-bottom:64px!important}
.w3-padding-top-64{padding-top:64px!important}.w3-padding-top-48{padding-top:48px!important}
.w3-padding-top-32{padding-top:32px!important}.w3-padding-top-24{padding-top:24px!important}
.w3-left{float:left!important}.w3-right{float:right!important}
.w3-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important}
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
/* Colors */
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
.w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important}
.w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important}
.w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important}
.w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important}
.w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important}
.w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important}
.w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important}
.w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important}
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
.w3-danger{color:#fff!important;background-color:#dd0000!important}
.w3-note{color:#000!important;background-color:#fff599!important}
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
.w3-warning{color:#000!important;background-color:#ffb305!important}
.w3-success{color:#fff!important;background-color:#008a00!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
.w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important}
.w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important}
.w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important}
.w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important}
.w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important}
.w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important}
.w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important}
.w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important}
.w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important}
.w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important}
.w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important}
.w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important}
.w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important}
.w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important}
.w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important}
.w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important}
.w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important}
.w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important}
.w3-text-red,.w3-hover-text-red:hover{color:#f44336!important}
.w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important}
.w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important}
.w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important}
.w3-text-white,.w3-hover-text-white:hover{color:#fff!important}
.w3-text-black,.w3-hover-text-black:hover{color:#000!important}
.w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important}
.w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important}
.w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important}
.w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important}
.w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important}
.w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important}
.w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important}
.w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important}
.w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important}
.w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important}
.w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important}
.w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important}
.w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important}
.w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important}
.w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important}
.w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important}
.w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important}
.w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important}
.w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important}
.w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important}
.w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important}
.w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important}
.w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important}
.w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important}
.w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important}
.w3-border-black,.w3-hover-border-black:hover{border-color:#000!important}
.w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important}
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}

View File

@@ -1,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "🦀", "emoji": "🦀",
"previous": "&Zv/eOewtUPxYuALmYV8v+JDKwH4+aN8zCTYFwB7oYEw=.sha256" "previous": "&R6lVyXLYem8Qkuhok/USflvzqw/ZgGic1aUsE23yzR0=.sha256"
} }

View File

@@ -14,23 +14,8 @@ function get_emojis() {
}); });
} }
async function get_recent(author) { export async function picker(callback, anchor, author, recent) {
let recent = await tfrpc.rpc.query(
`
SELECT DISTINCT content ->> '$.vote.expression' AS value
FROM messages
WHERE author = ? AND
content ->> '$.type' = 'vote'
ORDER BY timestamp DESC LIMIT 10
`,
[author]
);
return recent.map((x) => x.value);
}
export async function picker(callback, anchor, author) {
let json = await get_emojis(); let json = await get_emojis();
let recent = await get_recent(author);
let div = document.createElement('div'); let div = document.createElement('div');
div.id = 'emoji_picker'; div.id = 'emoji_picker';

View File

@@ -11,6 +11,7 @@ class TfElement extends LitElement {
broadcasts: {type: Array}, broadcasts: {type: Array},
connections: {type: Array}, connections: {type: Array},
loading: {type: Boolean}, loading: {type: Boolean},
loading_about: {type: Number},
loaded: {type: Boolean}, loaded: {type: Boolean},
following: {type: Array}, following: {type: Array},
users: {type: Object}, users: {type: Object},
@@ -21,6 +22,7 @@ class TfElement extends LitElement {
guest: {type: Boolean}, guest: {type: Boolean},
url: {type: String}, url: {type: String},
private_messages: {type: Array}, private_messages: {type: Array},
recent_reactions: {type: Array},
}; };
} }
@@ -36,11 +38,13 @@ class TfElement extends LitElement {
this.following = []; this.following = [];
this.users = {}; this.users = {};
this.loaded = false; this.loaded = false;
this.loading_about = 0;
this.channels = []; this.channels = [];
this.channels_unread = {}; this.channels_unread = {};
this.channels_latest = {}; this.channels_latest = {};
this.loading_latest = 0; this.loading_latest = 0;
this.loading_latest_scheduled = 0; this.loading_latest_scheduled = 0;
this.recent_reactions = [];
tfrpc.rpc.getBroadcasts().then((b) => { tfrpc.rpc.getBroadcasts().then((b) => {
self.broadcasts = b || []; self.broadcasts = b || [];
}); });
@@ -149,8 +153,9 @@ class TfElement extends LitElement {
} }
async fetch_about(following, users) { async fetch_about(following, users) {
this.loading_about++;
let ids = Object.keys(following).sort(); let ids = Object.keys(following).sort();
const k_cache_version = 1; const k_cache_version = 3;
let cache = await tfrpc.rpc.databaseGet('about'); let cache = await tfrpc.rpc.databaseGet('about');
let original_cache = cache; let original_cache = cache;
cache = cache ? JSON.parse(cache) : {}; cache = cache ? JSON.parse(cache) : {};
@@ -158,116 +163,86 @@ class TfElement extends LitElement {
cache = { cache = {
version: k_cache_version, version: k_cache_version,
about: {}, about: {},
last_row_id: 0,
}; };
} }
let max_row_id = (
await tfrpc.rpc.query( let ids_out_of_date = ids.filter(
` (x) =>
SELECT MAX(rowid) AS max_row_id FROM messages (users[x]?.seq && !cache.about[x]?.seq) ||
`, (users[x]?.seq && users[x]?.seq > cache.about[x].seq)
[] );
)
)[0].max_row_id;
for (let id of Object.keys(cache.about)) { for (let id of Object.keys(cache.about)) {
if (ids.indexOf(id) == -1) { if (ids.indexOf(id) == -1) {
delete cache.about[id]; delete cache.about[id];
} else {
users[id] = Object.assign(cache.about[id], users[id] || {});
} }
} }
const k_chunk_size = 1024;
let min_row_id = 0;
console.log( console.log(
'loading about for', 'loading about for',
ids.length, ids.length,
'accounts', 'accounts',
cache.last_row_id, ids_out_of_date.length,
'=>', 'out of date'
max_row_id
); );
try { if (ids_out_of_date.length) {
while (true) { try {
let abouts = await tfrpc.rpc.query( let rows = await tfrpc.rpc.query(
` `
SELECT * FROM ( SELECT all_abouts.author, json(json_group_object(all_abouts.key, all_abouts.value)) AS about
FROM (
SELECT SELECT
messages.rowid AS rowid, messages.author, json(messages.content) AS content, messages.sequence messages.author,
FROM fields.key,
messages, RANK() OVER (PARTITION BY messages.author, fields.key ORDER BY messages.sequence DESC) AS rank,
json_each(?1) AS following fields.value
FROM messages JOIN json_each(messages.content) AS fields
WHERE WHERE
messages.author = following.value AND messages.content ->> '$.type' = 'about' AND
messages.content ->> 'type' = 'about' AND messages.content ->> '$.about' = messages.author AND
messages.rowid > ?3 AND NOT fields.key IN ('about', 'type')) all_abouts
messages.rowid <= ?4 JOIN json_each(?) AS following ON all_abouts.author = following.value
UNION WHERE rank = 1
SELECT GROUP BY all_abouts.author
messages.rowid AS rowid, messages.author, json(messages.content) AS content, messages.sequence
FROM
messages,
json_each(?2) AS following
WHERE
messages.author = following.value AND
messages.content ->> 'type' = 'about' AND
messages.rowid > ?6 AND
messages.rowid <= ?4
)
ORDER BY rowid LIMIT ?5
`, `,
[ [JSON.stringify(ids_out_of_date)]
JSON.stringify(ids.filter((id) => cache.about[id])),
JSON.stringify(ids.filter((id) => !cache.about[id])),
cache.last_row_id,
max_row_id,
k_chunk_size,
min_row_id,
]
); );
let max_seen;
for (let about of abouts) {
let content = JSON.parse(about.content);
if (content.about === about.author) {
delete content.type;
delete content.about;
cache.about[about.author] = Object.assign(
cache.about[about.author] || {},
content
);
}
max_seen = about.rowid;
}
console.log(
'cache =',
cache.last_row_id,
'seen =',
max_seen,
'max =',
max_row_id
);
cache.last_row_id = Math.max(cache.last_row_id, max_seen ?? max_row_id);
min_row_id = Math.max(min_row_id, max_seen ?? max_row_id);
let new_cache = JSON.stringify(cache);
if (new_cache !== original_cache) {
let start_time = new Date();
tfrpc.rpc.databaseSet('about', new_cache).then(function () {
console.log('saving about took', (new Date() - start_time) / 1000);
});
}
users = users || {}; users = users || {};
for (let id of Object.keys(cache.about)) { for (let row of rows) {
users[id] = Object.assign( users[row.author] = Object.assign(
{follow_depth: following[id]?.d}, users[row.author] || {},
users[id] || {}, JSON.parse(row.about)
cache.about[id] );
cache.about[row.author] = Object.assign(
{seq: users[row.author].seq},
JSON.parse(row.about)
); );
} }
if (cache.last_row_id >= max_row_id) { } catch (e) {
break; console.log(e);
}
} }
} catch (e) {
console.log(e);
} }
for (let id of ids_out_of_date) {
if (!cache.about[id]?.seq) {
cache.about[id] = Object.assign(cache.about[id] ?? {}, {
seq: users[id]?.seq ?? 0,
});
}
}
this.loading_about--;
let new_cache = JSON.stringify(cache);
if (new_cache != original_cache) {
let start_time = new Date();
tfrpc.rpc.databaseSet('about', new_cache).then(function () {
console.log('saving about took', (new Date() - start_time) / 1000);
});
}
return Object.assign({}, users); return Object.assign({}, users);
} }
@@ -378,27 +353,37 @@ class TfElement extends LitElement {
let latest_private = this.get_latest_private(following); let latest_private = this.get_latest_private(following);
let channels = await tfrpc.rpc.query( let channels = await tfrpc.rpc.query(
` `
SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages
JOIN json_each(?1) AS channels ON messages.content ->> 'channel' = channels.value JOIN json_each(?1) AS channels ON messages.content ->> 'channel' = channels.value
JOIN json_each(?2) AS following ON messages.author = following.value JOIN json_each(?2) AS following ON messages.author = following.value
WHERE WHERE
messages.content ->> 'type' = 'post' AND messages.content ->> 'type' = 'post' AND
messages.content ->> 'root' IS NULL AND messages.content ->> 'root' IS NULL AND
messages.author != ?4 messages.author != ?4
GROUP by channel GROUP by channel
UNION UNION
SELECT '' AS channel, MAX(messages.rowid) AS rowid FROM messages SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages
JOIN json_each(?2) AS following ON messages.author = following.value JOIN messages_refs ON messages.id = messages_refs.message
WHERE JOIN json_each(?1) AS channels ON messages_refs.ref = '#' || channels.value
messages.content ->> 'type' = 'post' AND JOIN json_each(?2) AS following ON messages.author = following.value
messages.content ->> 'root' IS NULL AND WHERE
messages.author != ?4 messages.content ->> 'type' = 'post' AND
UNION messages.content ->> 'root' IS NULL AND
SELECT '@' AS channel, MAX(messages.rowid) AS rowid FROM messages_fts(?3) messages.author != ?4
JOIN messages ON messages.rowid = messages_fts.rowid GROUP by channel
JOIN json_each(?2) AS following ON messages.author = following.value UNION
WHERE messages.author != ?4 SELECT '' AS channel, MAX(messages.rowid) AS rowid FROM messages
`, JOIN json_each(?2) AS following ON messages.author = following.value
WHERE
messages.content ->> 'type' = 'post' AND
messages.content ->> 'root' IS NULL AND
messages.author != ?4
UNION
SELECT '@' AS channel, MAX(messages.rowid) AS rowid FROM messages_fts(?3)
JOIN messages ON messages.rowid = messages_fts.rowid
JOIN json_each(?2) AS following ON messages.author = following.value
WHERE messages.author != ?4
`,
[ [
JSON.stringify(this.channels), JSON.stringify(this.channels),
JSON.stringify(following), JSON.stringify(following),
@@ -406,9 +391,15 @@ class TfElement extends LitElement {
this.whoami, this.whoami,
] ]
); );
this.channels_latest = Object.fromEntries( let latest = {};
channels.map((x) => [x.channel, x.rowid]) for (let row of channels) {
); if (!latest[row.channel]) {
latest[row.channel] = row.rowid;
} else {
latest[row.channel] = Math.max(row.rowid, latest[row.channel]);
}
}
this.channels_latest = latest;
console.log('channels took', (new Date() - start_time) / 1000.0); console.log('channels took', (new Date() - start_time) / 1000.0);
let self = this; let self = this;
start_time = new Date(); start_time = new Date();
@@ -446,43 +437,58 @@ class TfElement extends LitElement {
[JSON.stringify(Object.keys(users))] [JSON.stringify(Object.keys(users))]
); );
for (let row of info) { for (let row of info) {
users[row.author].seq = row.max_seq; users[row.author] = Object.assign(users[row.author], {
users[row.author].ts = row.max_ts; seq: row.max_sequence,
ts: row.max_ts,
});
} }
return users; return users;
} }
async load_recent_reactions() {
this.recent_reactions = (
await tfrpc.rpc.query(
`
SELECT DISTINCT content ->> '$.vote.expression' AS value
FROM messages
WHERE author = ? AND
content ->> '$.type' = 'vote'
ORDER BY timestamp DESC LIMIT 10
`,
[this.whoami]
)
).map((x) => x.value);
}
async load() { async load() {
this.loading_latest = true; this.loading_latest = true;
try { try {
let start_time = new Date(); let start_time = new Date();
let whoami = this.whoami; let whoami = this.whoami;
let following = await tfrpc.rpc.following([whoami], 2); let following = await tfrpc.rpc.following([whoami], 2);
let old_users = this.users ?? {};
let users = {}; let users = {};
let by_count = []; let by_count = [];
for (let [id, v] of Object.entries(following)) { for (let [id, v] of Object.entries(following)) {
users[id] = { users[id] = Object.assign(
following: v.of, {
blocking: v.ob, following: v.of,
followed: v.if, blocking: v.ob,
blocked: v.ib, followed: v.if,
}; blocked: v.ib,
follow_depth: following[id]?.d,
},
old_users[id]
);
by_count.push({count: v.of, id: id}); by_count.push({count: v.of, id: id});
} }
let reactions = this.load_recent_reactions();
this.load_channels_latest(Object.keys(following)); this.load_channels_latest(Object.keys(following));
this.channels_unread = JSON.parse( this.channels_unread = JSON.parse(
(await tfrpc.rpc.databaseGet('unread')) ?? '{}' (await tfrpc.rpc.databaseGet('unread')) ?? '{}'
); );
this.following = Object.keys(following); this.following = Object.keys(following);
let about_start_time = new Date(); let about_start_time = new Date();
users = await this.fetch_about(following, users);
console.log(
'about took',
(new Date() - about_start_time) / 1000.0,
'seconds for',
Object.keys(users).length,
'users'
);
start_time = new Date(); start_time = new Date();
users = await this.fetch_user_info(users); users = await this.fetch_user_info(users);
console.log( console.log(
@@ -491,9 +497,22 @@ class TfElement extends LitElement {
'seconds' 'seconds'
); );
this.users = users; this.users = users;
let self = this;
this.fetch_about(following, users).then(function (result) {
self.users = result;
console.log(
'about took',
(new Date() - about_start_time) / 1000.0,
'seconds for',
Object.keys(users).length,
'users'
);
});
console.log( console.log(
`load finished ${whoami} => ${this.whoami} in ${(new Date() - start_time) / 1000}` `load finished ${whoami} => ${this.whoami} in ${(new Date() - start_time) / 1000}`
); );
await reactions;
this.whoami = whoami; this.whoami = whoami;
this.loaded = whoami; this.loaded = whoami;
} finally { } finally {
@@ -544,13 +563,14 @@ class TfElement extends LitElement {
whoami=${this.whoami} whoami=${this.whoami}
.users=${this.users} .users=${this.users}
hash=${this.hash} hash=${this.hash}
?loading=${this.loading} ?loading=${this.loading || this.loading_about != 0}
.channels=${this.channels} .channels=${this.channels}
.channels_latest=${this.channels_latest} .channels_latest=${this.channels_latest}
.channels_unread=${this.channels_unread} .channels_unread=${this.channels_unread}
@channelsetunread=${this.channel_set_unread} @channelsetunread=${this.channel_set_unread}
.connections=${this.connections} .connections=${this.connections}
.private_messages=${this.private_messages} .private_messages=${this.private_messages}
.recent_reactions=${this.recent_reactions}
></tf-tab-news> ></tf-tab-news>
`; `;
} else if (this.tab === 'connections') { } else if (this.tab === 'connections') {

View File

@@ -524,7 +524,7 @@ class TfComposeElement extends LitElement {
return html` return html`
<div style="display: flex; flex-direction: row; width: 100%"> <div style="display: flex; flex-direction: row; width: 100%">
<label for="encrypt_to">🔐 To:</label> <label for="encrypt_to">🔐 To:</label>
<input type="text" id="encrypt_to" style="display: flex; flex: 1 1" @input=${this.update_encrypt}></input> <input type="text" id="encrypt_to" class="w3-input w3-theme-d1 w3-border" style="display: flex; flex: 1 1" @input=${this.update_encrypt}></input>
<button class="w3-button w3-theme-d1" @click=${() => this.set_encrypt(undefined)}>🚮</button> <button class="w3-button w3-theme-d1" @click=${() => this.set_encrypt(undefined)}>🚮</button>
</div> </div>
<ul> <ul>

View File

@@ -16,6 +16,7 @@ class TfMessageElement extends LitElement {
expanded: {type: Object}, expanded: {type: Object},
channel: {type: String}, channel: {type: String},
channel_unread: {type: Number}, channel_unread: {type: Number},
recent_reactions: {type: Array},
}; };
} }
@@ -31,6 +32,7 @@ class TfMessageElement extends LitElement {
this.format = 'message'; this.format = 'message';
this.expanded = {}; this.expanded = {};
this.channel_unread = -1; this.channel_unread = -1;
this.recent_reactions = [];
} }
connectedCallback() { connectedCallback() {
@@ -84,20 +86,25 @@ class TfMessageElement extends LitElement {
render_votes() { render_votes() {
function normalize_expression(expression) { function normalize_expression(expression) {
if (expression === 'Like' || !expression) { if (
return '👍'; expression === 'Unlike' ||
} else if (expression === 'Unlike') { expression === 'unlike' ||
expression == 'undig'
) {
return '👎'; return '👎';
} else if (expression === 'heart') { } else if (expression === 'heart') {
return '❤️'; return '❤️';
} else if ((expression ?? '').split('').every((x) => x.charCodeAt(0) < 256)) {
return '👍';
} else { } else {
return expression; return expression;
} }
} }
if (this.message?.votes?.length) { if (this.message?.votes?.length) {
return html` <div class="w3-container"> return html` <footer class="w3-container">
<div <div
class="w3-button w3-bar w3-padding-small" class="w3-button w3-bar"
style="padding: 0"
@click=${this.show_reactions} @click=${this.show_reactions}
> >
${(this.message.votes || []).map( ${(this.message.votes || []).map(
@@ -112,7 +119,7 @@ class TfMessageElement extends LitElement {
` `
)} )}
</div> </div>
</div>`; </footer>`;
} }
} }
@@ -155,7 +162,12 @@ class TfMessageElement extends LitElement {
} }
react(event) { react(event) {
emojis.picker((x) => this.vote(x), null, this.whoami); emojis.picker(
(x) => this.vote(x),
null,
this.whoami,
this.recent_reactions
);
} }
show_image(link) { show_image(link) {
@@ -289,27 +301,35 @@ class TfMessageElement extends LitElement {
return total; return total;
} }
expanded_key() {
return this.message?.id || this.messages?.map((x) => x.id).join(':');
}
set_expanded(expanded, tag) { set_expanded(expanded, tag) {
let key = this.expanded_key();
this.dispatchEvent( this.dispatchEvent(
new CustomEvent('tf-expand', { new CustomEvent('tf-expand', {
bubbles: true, bubbles: true,
composed: true, composed: true,
detail: {id: (this.message.id || '') + (tag || ''), expanded: expanded}, detail: {id: key + (tag || ''), expanded: expanded},
}) })
); );
} }
toggle_expanded(tag) { toggle_expanded(tag) {
this.set_expanded( let key = this.expanded_key();
!this.expanded[(this.message.id || '') + (tag || '')], this.set_expanded(!this.expanded[key + (tag || '')], tag);
tag }
);
is_expanded(tag) {
let key = this.expanded_key();
return this.expanded[key + (tag || '')];
} }
render_children() { render_children() {
let self = this; let self = this;
if (this.message.child_messages?.length) { if (this.message.child_messages?.length) {
if (!this.expanded[this.message.id]) { if (!this.expanded[this.expanded_key()]) {
return html` return html`
<button <button
class="w3-button w3-theme-d1 w3-block w3-bar" class="w3-button w3-theme-d1 w3-block w3-bar"
@@ -333,6 +353,7 @@ class TfMessageElement extends LitElement {
.expanded=${this.expanded} .expanded=${this.expanded}
channel=${this.channel} channel=${this.channel}
channel_unread=${this.channel_unread} channel_unread=${this.channel_unread}
.recent_reactions=${this.recent_reactions}
></tf-message>` ></tf-message>`
)} )}
</div> </div>
@@ -441,7 +462,7 @@ class TfMessageElement extends LitElement {
${this.drafts[this.message?.id] === undefined ${this.drafts[this.message?.id] === undefined
? html` ? html`
<button class="w3-button w3-bar-item" @click=${this.show_reply}> <button class="w3-button w3-bar-item" @click=${this.show_reply}>
Reply ↩️ Reply
</button> </button>
` `
: undefined} : undefined}
@@ -529,6 +550,7 @@ class TfMessageElement extends LitElement {
.expanded=${self.expanded} .expanded=${self.expanded}
channel=${self.channel} channel=${self.channel}
channel_unread=${self.channel_unread} channel_unread=${self.channel_unread}
.recent_reactions=${self.recent_reactions}
></tf-message> ></tf-message>
` `
)} )}
@@ -540,23 +562,63 @@ class TfMessageElement extends LitElement {
let reply = let reply =
this.drafts[this.message?.id] !== undefined this.drafts[this.message?.id] !== undefined
? html` ? html`
<tf-compose <div class="w3-section w3-container">
whoami=${this.whoami} <tf-compose
.users=${this.users} whoami=${this.whoami}
root=${content.root || this.message.id} .users=${this.users}
branch=${this.message.id} root=${content.root || this.message.id}
.drafts=${this.drafts} branch=${this.message.id}
@tf-discard=${this.discard_reply} .drafts=${this.drafts}
author=${this.message.author} @tf-discard=${this.discard_reply}
></tf-compose> author=${this.message.author}
.recent_reactions=${this.recent_reactions}
></tf-compose>
</div>
` `
: undefined; : undefined;
return html` return html`
<div class="w3-section w3-container">${reply}</div> ${reply}
<footer>${this.render_children()}</footer> <footer>${this.render_children()}</footer>
`; `;
} }
content_group_by_author() {
let sorted = this.message.messages
.map((x) => [
x.author,
x.content.blocking !== undefined
? x.content.blocking
? 'is blocking'
: 'is no longer blocking'
: x.content.following !== undefined
? x.content.following
? 'is following'
: 'is no longer following'
: '',
x.content.contact,
x,
])
.sort();
let result = [];
let last;
let group;
for (let row of sorted) {
if (last && last[0] == row[0] && last[1] == row[1]) {
group.push(row[2]);
} else {
if (group) {
result.push({author: last[0], action: last[1], users: group});
}
last = row;
group = [row[2]];
}
}
if (group) {
result.push({author: last[0], action: last[1], users: group});
}
return result;
}
render() { render() {
let content = this.message?.content; let content = this.message?.content;
if (this.message?.decrypted?.type == 'post') { if (this.message?.decrypted?.type == 'post') {
@@ -565,29 +627,88 @@ class TfMessageElement extends LitElement {
let class_background = this.class_background(); let class_background = this.class_background();
let self = this; let self = this;
if (this.message?.type === 'contact_group') { if (this.message?.type === 'contact_group') {
return this.render_frame( if (this.expanded[this.expanded_key()]) {
html` ${this.message.messages.map( return this.render_frame(html`
(x) => <div class="w3-padding">
html`<tf-message ${this.message.messages.map(
.message=${x} (x) =>
whoami=${this.whoami} html`<tf-message
.users=${this.users} .message=${x}
.drafts=${this.drafts} whoami=${this.whoami}
.expanded=${this.expanded} .users=${this.users}
channel=${this.channel} .drafts=${this.drafts}
channel_unread=${this.channel_unread} .expanded=${this.expanded}
></tf-message>` channel=${this.channel}
)}` channel_unread=${this.channel_unread}
); ></tf-message>`
)}
</div>
<button
class="w3-button w3-theme-d1 w3-block w3-bar"
style="box-sizing: border-box"
@click=${() => self.set_expanded(false)}
>
Collapse
</button>
`);
} else {
return this.render_frame(html`
<div class="w3-padding">
${this.content_group_by_author().map(
(x) => html`
<tf-user id=${x.author} .users=${this.users}></tf-user>
${x.action}
${x.users.map(
(y) => html`
<tf-user id=${y} .users=${this.users}></tf-user>
`
)}
`
)}
</div>
<button
class="w3-button w3-theme-d1 w3-block w3-bar"
style="box-sizing: border-box"
@click=${() => self.set_expanded(true)}
>
Expand
</button>
`);
}
} else if (this.message.placeholder) { } else if (this.message.placeholder) {
return this.render_frame( return this.render_frame(
html`<div class="w3-padding"> html`<div>
<p> <div class="w3-bar">
<a target="_top" href=${'#' + encodeURIComponent(this.message.id)} <a
>${this.message.id}</a class="w3-bar-item w3-panel w3-round-xlarge w3-theme-d1 w3-margin w3-button"
target="_top"
href=${'#' + encodeURIComponent(this.message?.id)}
> >
(placeholder) This message is not currently available.
</p> </a>
<div class="w3-bar-item w3-right">
<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
%
</button>
<div
class="w3-dropdown-content w3-bar-block w3-card-4 w3-theme-l1"
style="right: 48px"
>
<a
target="_top"
class="w3-button w3-bar-item"
href=${'#' + encodeURIComponent(this.message?.id)}
>View Message</a
>
<button
class="w3-button w3-bar-item w3-border-bottom"
@click=${this.copy_id}
>
Copy ID
</button>
</div>
</div>
</div>
<div>${this.render_votes()}</div> <div>${this.render_votes()}</div>
${(this.message.child_messages || []).map( ${(this.message.child_messages || []).map(
(x) => html` (x) => html`
@@ -614,7 +735,7 @@ class TfMessageElement extends LitElement {
} }
if (content.image !== undefined) { if (content.image !== undefined) {
image = html` image = html`
<div><img src=${'/' + (typeof content.image?.link == 'string' ? content.image.link : content.image) + '/view'} style="width: 256px; height: auto"></img></div> <div @click=${this.body_click}><img src=${'/' + (typeof content.image?.link == 'string' ? content.image.link : content.image) + '/view'} style="width: 256px; height: auto"></img></div>
`; `;
} }
if (content.description !== undefined) { if (content.description !== undefined) {
@@ -637,25 +758,60 @@ class TfMessageElement extends LitElement {
</div> </div>
`); `);
} else if (content.type == 'contact') { } else if (content.type == 'contact') {
return html` return this.render_frame(html`
<div class="w3-padding"> <div class="w3-bar">
<tf-user id=${this.message.author} .users=${this.users}></tf-user> <div class="w3-bar-item">
is <tf-user id=${this.message.author} .users=${this.users}></tf-user>
${content.blocking === true is
? 'blocking' ${content.blocking === true
: content.blocking === false ? 'blocking'
? 'no longer blocking' : content.blocking === false
: content.following === true ? 'no longer blocking'
? 'following' : content.following === true
: content.following === false ? 'following'
? 'no longer following' : content.following === false
: '?'} ? 'no longer following'
<tf-user : '?'}
id=${this.message.content.contact} <tf-user
.users=${this.users} id=${this.message.content.contact}
></tf-user> .users=${this.users}
></tf-user>
</div>
<div class="w3-bar-item w3-right">
<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
%
</button>
<div
class="w3-dropdown-content w3-bar-block w3-card-4 w3-theme-l1"
style="right: 48px"
>
<a
target="_top"
class="w3-button w3-bar-item"
href=${'#' + encodeURIComponent(this.message?.id)}
>View Message</a
>
<button
class="w3-button w3-bar-item w3-border-bottom"
@click=${this.copy_id}
>
Copy ID
</button>
${this.drafts[this.message?.id] === undefined
? html`
<button
class="w3-button w3-bar-item"
@click=${this.show_reply}
>
↩️ Reply
</button>
`
: undefined}
</div>
</div>
${this.render_votes()} ${this.render_actions()}
</div> </div>
`; `);
} else if (content.type == 'post') { } else if (content.type == 'post') {
let self = this; let self = this;
let body; let body;
@@ -678,11 +834,14 @@ class TfMessageElement extends LitElement {
} }
let content_warning = html` let content_warning = html`
<div <div
class="w3-panel w3-round-xlarge w3-theme-l4" class="w3-panel w3-round-xlarge w3-theme-l4 w3"
style="cursor: pointer" style="cursor: pointer"
@click=${(x) => this.toggle_expanded(':cw')} @click=${(x) => this.toggle_expanded(':cw')}
> >
<p>${content.contentWarning}</p> <p>${content.contentWarning}</p>
<p class="w3-small">
${this.is_expanded(':cw') ? 'Show less' : 'Show more'}
</p>
</div> </div>
`; `;
let content_html = html` let content_html = html`

View File

@@ -13,6 +13,7 @@ class TfNewsElement extends LitElement {
expanded: {type: Object}, expanded: {type: Object},
channel: {type: String}, channel: {type: String},
channel_unread: {type: Number}, channel_unread: {type: Number},
recent_reactions: {type: Array},
}; };
} }
@@ -28,6 +29,7 @@ class TfNewsElement extends LitElement {
this.drafts = {}; this.drafts = {};
this.expanded = {}; this.expanded = {};
this.channel_unread = -1; this.channel_unread = -1;
this.recent_reactions = [];
} }
process_messages(messages) { process_messages(messages) {
@@ -164,7 +166,10 @@ class TfNewsElement extends LitElement {
if (message?.content?.type === 'contact') { if (message?.content?.type === 'contact') {
group.push(message); group.push(message);
} else { } else {
if (group.length > 0) { if (group.length == 1) {
result.push(group[0]);
group = [];
} else if (group.length > 1) {
result.push({ result.push({
rowid: Math.max(...group.map((x) => x.rowid)), rowid: Math.max(...group.map((x) => x.rowid)),
type: 'contact_group', type: 'contact_group',
@@ -175,7 +180,10 @@ class TfNewsElement extends LitElement {
result.push(message); result.push(message);
} }
} }
if (group.length > 0) { if (group.length == 1) {
result.push(group[0]);
group = [];
} else if (group.length > 1) {
result.push({ result.push({
rowid: Math.max(...group.map((x) => x.rowid)), rowid: Math.max(...group.map((x) => x.rowid)),
type: 'contact_group', type: 'contact_group',
@@ -211,6 +219,7 @@ class TfNewsElement extends LitElement {
collapsed="true" collapsed="true"
channel=${this.channel} channel=${this.channel}
channel_unread=${this.channel_unread} channel_unread=${this.channel_unread}
.recent_reactions=${this.recent_reactions}
></tf-message> ></tf-message>
${x.rowid == unread_rowid ${x.rowid == unread_rowid
? html`<div style="display: flex; flex-direction: row"> ? html`<div style="display: flex; flex-direction: row">

View File

@@ -166,6 +166,40 @@ class TfProfileElement extends LitElement {
navigator.clipboard.writeText(this.id); navigator.clipboard.writeText(this.id);
} }
show_image(link) {
let div = document.createElement('div');
div.style.left = 0;
div.style.top = 0;
div.style.width = '100%';
div.style.height = '100%';
div.style.position = 'fixed';
div.style.background = '#000';
div.style.zIndex = 100;
div.style.display = 'grid';
let img = document.createElement('img');
img.src = link;
img.style.maxWidth = '100%';
img.style.maxHeight = '100%';
img.style.display = 'block';
img.style.margin = 'auto';
img.style.objectFit = 'contain';
img.style.width = '100%';
div.appendChild(img);
function image_close(event) {
document.body.removeChild(div);
window.removeEventListener('keydown', image_close);
}
div.onclick = image_close;
window.addEventListener('keydown', image_close);
document.body.appendChild(div);
}
body_click(event) {
if (event.srcElement.tagName == 'IMG') {
this.show_image(event.srcElement.src);
}
}
render() { render() {
this.load(); this.load();
let self = this; let self = this;
@@ -242,15 +276,19 @@ class TfProfileElement extends LitElement {
</div> </div>
</div>` </div>`
: null; : null;
let image = let image = profile.image;
typeof profile.image == 'string' ? profile.image : profile.image?.link; if (typeof image == 'string' && !image.startsWith('&')) {
try {
image = JSON.parse(image)?.link;
} catch {}
}
image = this.editing?.image ?? image; image = this.editing?.image ?? image;
let description = this.editing?.description ?? profile.description; let description = this.editing?.description ?? profile.description;
return html`<div class="w3-card-4 w3-container w3-theme-d3" style="box-sizing: border-box"> return html`<div class="w3-card-4 w3-container w3-theme-d3" style="box-sizing: border-box">
<header class="w3-container"> <header class="w3-container">
<p><tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)} in ${this.sequence} messages)</p> <p><tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)} in ${this.sequence} messages)</p>
</header> </header>
<div class="w3-container"> <div class="w3-container" @click=${this.body_click}>
<div class="w3-margin-bottom" style="display: flex; flex-direction: row"> <div class="w3-margin-bottom" style="display: flex; flex-direction: row">
<input type="text" class="w3-input w3-border w3-theme-d1" style="display: flex 1 1" readonly value=${this.id}></input> <input type="text" class="w3-input w3-border w3-theme-d1" style="display: flex 1 1" readonly value=${this.id}></input>
<button class="w3-button w3-theme-d1 w3-ripple" style="flex: 0 0 auto" @click=${this.copy_id}>Copy</button> <button class="w3-button w3-theme-d1 w3-ripple" style="flex: 0 0 auto" @click=${this.copy_id}>Copy</button>

View File

@@ -277,7 +277,7 @@ class TfTabConnectionsElement extends LitElement {
class="w3-button w3-block w3-theme-d1" class="w3-button w3-block w3-theme-d1"
@click=${() => self.toggle_accordian('broadcasts')} @click=${() => self.toggle_accordian('broadcasts')}
> >
Broadcasts (${this.valid_broadcasts().length}) Discovery (${this.valid_broadcasts().length})
</h2> </h2>
<ul class="w3-ul w3-border w3-hide" id="broadcasts"> <ul class="w3-ul w3-border w3-hide" id="broadcasts">
${this.valid_broadcasts().map((x) => self.render_broadcast(x))} ${this.valid_broadcasts().map((x) => self.render_broadcast(x))}
@@ -308,6 +308,12 @@ class TfTabConnectionsElement extends LitElement {
<div class="w3-bar-item"> <div class="w3-bar-item">
<tf-user id=${x.pubkey} .users=${self.users}></tf-user> <tf-user id=${x.pubkey} .users=${self.users}></tf-user>
<div><small>${x.address}:${x.port}</small></div> <div><small>${x.address}:${x.port}</small></div>
<div>
<small
>Last connection:
${new Date(x.last_success * 1000)}</small
>
</div>
</div> </div>
</div> </div>
${this.render_message(x)} ${this.render_message(x)}

View File

@@ -18,6 +18,7 @@ class TfTabNewsFeedElement extends LitElement {
time_range: {type: Array}, time_range: {type: Array},
time_loading: {type: Array}, time_loading: {type: Array},
private_messages: {type: Array}, private_messages: {type: Array},
recent_reactions: {type: Array},
}; };
} }
@@ -37,6 +38,7 @@ class TfTabNewsFeedElement extends LitElement {
this.start_time = new Date().valueOf(); this.start_time = new Date().valueOf();
this.time_range = [0, 0]; this.time_range = [0, 0];
this.time_loading = undefined; this.time_loading = undefined;
this.recent_reactions = [];
this.loading = 0; this.loading = 0;
} }
@@ -439,9 +441,14 @@ class TfTabNewsFeedElement extends LitElement {
`; `;
} }
return cache(html` return cache(html`
<button class="w3-button w3-theme-d1" @click=${this.mark_all_read}> ${!this.hash.startsWith('#%')
Mark All Read ? html`<button
</button> class="w3-button w3-theme-d1"
@click=${this.mark_all_read}
>
Mark All Read
</button>`
: undefined}
<tf-news <tf-news
id="news" id="news"
whoami=${this.whoami} whoami=${this.whoami}
@@ -452,6 +459,7 @@ class TfTabNewsFeedElement extends LitElement {
.expanded=${this.expanded} .expanded=${this.expanded}
channel=${this.channel()} channel=${this.channel()}
channel_unread=${this.channels_unread?.[this.channel()]} channel_unread=${this.channels_unread?.[this.channel()]}
.recent_reactions=${this.recent_reactions}
></tf-news> ></tf-news>
${more} ${more}
`); `);

View File

@@ -24,6 +24,7 @@ class TfTabNewsElement extends LitElement {
channels_latest: {type: Object}, channels_latest: {type: Object},
connections: {type: Array}, connections: {type: Array},
private_messages: {type: Array}, private_messages: {type: Array},
recent_reactions: {type: Array},
}; };
} }
@@ -43,6 +44,7 @@ class TfTabNewsElement extends LitElement {
this.channels_latest = {}; this.channels_latest = {};
this.channels = []; this.channels = [];
this.connections = []; this.connections = [];
this.recent_reactions = [];
tfrpc.rpc.localStorageGet('drafts').then(function (d) { tfrpc.rpc.localStorageGet('drafts').then(function (d) {
self.drafts = JSON.parse(d || '{}'); self.drafts = JSON.parse(d || '{}');
}); });
@@ -95,7 +97,13 @@ class TfTabNewsElement extends LitElement {
} }
unread_status(channel) { unread_status(channel) {
if ( if (channel === undefined) {
if (
Object.keys(this.channels_unread).some((x) => this.unread_status(x))
) {
return '✉️ ';
}
} else if (
this.channels_latest[channel] && this.channels_latest[channel] &&
this.channels_latest[channel] > 0 && this.channels_latest[channel] > 0 &&
(this.channels_unread[channel] === undefined || (this.channels_unread[channel] === undefined ||
@@ -223,14 +231,22 @@ class TfTabNewsElement extends LitElement {
` `
)} )}
<h4 class="w3-bar-item w3-theme-d2">Connections</h4> <a class="w3-bar-item w3-theme-d2 w3-button" href="#connections">
<h4 style="margin: 0">Connections</h4>
</a>
${this.connections ${this.connections
.filter((x) => x.id && !x.destroy_reason) .filter((x) => x.id)
.map( .map(
(x) => html` (x) => html`
<tf-user <tf-user
class="w3-bar-item" class="w3-bar-item"
style="max-width: 100%" style=${x.destroy_reason
? 'border-left: 4px solid red; border-right: 4px solid red'
: x.connected
? x.flags?.one_shot
? 'border-left: 4px solid blue; border-right: 4px solid blue'
: 'border-left: 4px solid green; border-right: 4px solid green'
: ''}
id=${x.id} id=${x.id}
fallback_name=${x.host} fallback_name=${x.host}
.users=${this.users} .users=${this.users}
@@ -311,7 +327,7 @@ class TfTabNewsElement extends LitElement {
class="w3-button w3-hide-large" class="w3-button w3-hide-large"
@click=${this.show_sidebar} @click=${this.show_sidebar}
> >
&#9776; ${this.unread_status()}&#9776;
</div> </div>
Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>! Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>!
${edit_profile} ${edit_profile}
@@ -340,6 +356,7 @@ class TfTabNewsElement extends LitElement {
.channels_unread=${this.channels_unread} .channels_unread=${this.channels_unread}
.channels_latest=${this.channels_latest} .channels_latest=${this.channels_latest}
.private_messages=${this.private_messages} .private_messages=${this.private_messages}
.recent_reactions=${this.recent_reactions}
></tf-tab-news-feed> ></tf-tab-news-feed>
</div> </div>
</div> </div>

View File

@@ -38,8 +38,11 @@ class TfUserElement extends LitElement {
if (user) { if (user) {
let image_link = user.image; let image_link = user.image;
image_link = if (typeof image_link == 'string' && !image_link.startsWith('&')) {
typeof image_link == 'string' ? image_link : image_link?.link; try {
image_link = JSON.parse(image_link)?.link;
} catch {}
}
if (image_link !== undefined) { if (image_link !== undefined) {
image = html`<img image = html`<img
class=${'w3-theme-l4 ' + shape} class=${'w3-theme-l4 ' + shape}

View File

@@ -1,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "👋", "emoji": "👋",
"previous": "&wAb7J6E35xEXpiXsQ6t1RaWTGIvlatUnyH8ipF6pVic=.sha256" "previous": "&1o8MrBHfH42NnO+ruajwCmW/DUCb+IT1qtnAZI/agyo=.sha256"
} }

View File

@@ -45,6 +45,11 @@
<i class="fa fa-mobile-screen w3-xlarge"></i> <i class="fa fa-mobile-screen w3-xlarge"></i>
<i class="fa-brands fa-windows w3-xlarge"></i> <i class="fa-brands fa-windows w3-xlarge"></i>
</p> </p>
<a
class="w3-button w3-blue w3-padding-large"
href="https://www.tildefriends.net/~core/ssb/"
>🦀 Try It</a
>
<a <a
class="w3-button w3-black w3-padding-large" class="w3-button w3-black w3-padding-large"
href="https://dev.tildefriends.net/cory/tildefriends/releases" href="https://dev.tildefriends.net/cory/tildefriends/releases"
@@ -52,12 +57,7 @@
> >
<a <a
class="w3-button w3-black w3-padding-large" class="w3-button w3-black w3-padding-large"
href="https://www.tildefriends.net/~core/ssb/" href="https://dev.tildefriends.net/cory/tildefriends"
><i class="fa fa-link"></i> Try It</a
>
<a
class="w3-button w3-black w3-padding-large"
href="https://dev.tildefriends.net/"
><i class="fa fa-mug-hot"></i> Development</a ><i class="fa fa-mug-hot"></i> Development</a
> >
<a <a
@@ -65,6 +65,11 @@
href="https://docs.tildefriends.net/" href="https://docs.tildefriends.net/"
><i class="fa fa-book"></i> Documentation</a ><i class="fa fa-book"></i> Documentation</a
> >
<a
class="w3-button w3-black w3-padding-large"
href="https://www.tildefriends.net/~cory/tildeblog/"
><i class="fa fa-solid fa-square-rss"></i> Blog</a
>
<p> <p>
<a <a
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top" class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
@@ -86,6 +91,13 @@
<img src="googleplay.svg" style="height: 2em; margin: 0" /> <img src="googleplay.svg" style="height: 2em; margin: 0" />
Get it on Google Play (Open Testing) Get it on Google Play (Open Testing)
</a> </a>
<a
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
href="https://testflight.apple.com/join/tXxgtSpE"
>
<img src="ios.svg" style="height: 2em; margin: 0" />
Get it on iOS (TestFlight)
</a>
</p> </p>
</div> </div>
<div class="w3-col l4 m6"> <div class="w3-col l4 m6">

3
apps/welcome/ios.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="814" height="1000">
<path d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z"/>
</svg>

After

Width:  |  Height:  |  Size: 660 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 141 KiB

View File

@@ -1,4 +1,4 @@
/* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */ /* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ /* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0} html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
@@ -108,10 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px} .w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px} .w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px} .w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex} .w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic} .w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px} .w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word} .w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%} .w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
@@ -152,10 +150,11 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-button:hover{color:#000!important;background-color:#ccc!important} .w3-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important} .w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important} .w3-hover-none:hover{box-shadow:none!important}
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
/* Colors */ /* Colors */
.w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important} .w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important} .w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
.w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important} .w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important} .w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important} .w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important} .w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
@@ -170,24 +169,28 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important} .w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important} .w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important} .w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
.w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important} .w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important} .w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important} .w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
.w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important} .w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important} .w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important} .w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important}
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important} .w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important} .w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important} .w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important} .w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
.w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important} .w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important} .w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important} .w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important} .w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
.w3-danger{color:#fff!important;background-color:#dd0000!important}
.w3-note{color:#000!important;background-color:#fff599!important}
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
.w3-warning{color:#000!important;background-color:#ffb305!important}
.w3-success{color:#fff!important;background-color:#008a00!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important} .w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important} .w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important} .w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}

View File

@@ -411,6 +411,7 @@ class TfFilesElement extends LitElement {
current: {type: String}, current: {type: String},
files: {type: Object}, files: {type: Object},
dropping: {type: Number}, dropping: {type: Number},
drop_target: {type: String},
}; };
} }
@@ -449,6 +450,9 @@ class TfFilesElement extends LitElement {
if (!this.files[file].clean) { if (!this.files[file].clean) {
classes.push('dirty'); classes.push('dirty');
} }
if (this.drop_target == file) {
classes.push('drop');
}
return html`<div return html`<div
class="${classes.join(' ')}" class="${classes.join(' ')}"
@click=${(x) => this.file_click(file)} @click=${(x) => this.file_click(file)}
@@ -465,11 +469,12 @@ class TfFilesElement extends LitElement {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
this.dropping = 0; this.dropping = 0;
this.drop_target = undefined;
for (let file of event.dataTransfer.files) { for (let file of event.dataTransfer.files) {
let buffer = await file.arrayBuffer(); let buffer = await file.arrayBuffer();
let text = new TextDecoder('latin1').decode(buffer); let text = new TextDecoder('latin1').decode(buffer);
gFiles[file.name] = { gFiles[file.name] = {
doc: new cm6.EditorState.create({ doc: cm6.EditorState.create({
doc: text, doc: text,
extensions: cm6.extensions, extensions: cm6.extensions,
}), }),
@@ -488,6 +493,7 @@ class TfFilesElement extends LitElement {
*/ */
drag_enter(event) { drag_enter(event) {
this.dropping++; this.dropping++;
this.drop_target = event.srcElement.innerText.trim();
event.preventDefault(); event.preventDefault();
} }
@@ -497,6 +503,13 @@ class TfFilesElement extends LitElement {
*/ */
drag_leave(event) { drag_leave(event) {
this.dropping--; this.dropping--;
if (this.dropping == 0) {
this.drop_target = undefined;
}
}
drag_over(event) {
event.preventDefault();
} }
/** /**
@@ -523,6 +536,10 @@ class TfFilesElement extends LitElement {
background-color: #2aa198; background-color: #2aa198;
} }
div.file.drop {
border: 4px solid red;
}
div.file.dirty::after { div.file.dirty::after {
content: '*'; content: '*';
} }
@@ -531,20 +548,12 @@ class TfFilesElement extends LitElement {
@drop=${this.drop} @drop=${this.drop}
@dragenter=${this.drag_enter} @dragenter=${this.drag_enter}
@dragleave=${this.drag_leave} @dragleave=${this.drag_leave}
@dragover=${this.drag_over}
> >
${Object.keys(this.files) ${Object.keys(this.files)
.sort() .sort()
.map((x) => self.render_file(x))} .map((x) => self.render_file(x))}
</div> </div>
<div
?hidden=${this.dropping == 0}
@drop=${this.drop}
@dragenter=${this.drag_enter}
@dragleave=${this.drag_leave}
style="text-align: center; vertical-align: middle; outline: 16px solid red; margin: -8px; background-color: rgba(255, 0, 0, 0.5); position: absolute; left: 16px; top: 16px; width: calc(100% - 16px); height: calc(100% - 16px); z-index: 1000"
>
Drop File(s)
</div>
`; `;
} }
} }

View File

@@ -1,4 +1,4 @@
/* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */ /* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ /* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0} html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
@@ -108,10 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px} .w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px} .w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px} .w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex} .w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic} .w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px} .w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word} .w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%} .w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
@@ -152,10 +150,11 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-button:hover{color:#000!important;background-color:#ccc!important} .w3-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important} .w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important} .w3-hover-none:hover{box-shadow:none!important}
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
/* Colors */ /* Colors */
.w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important} .w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important} .w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
.w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important} .w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important} .w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important} .w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important} .w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
@@ -170,24 +169,28 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important} .w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important} .w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important} .w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
.w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important} .w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important} .w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important} .w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
.w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important} .w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important} .w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important} .w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important}
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important} .w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important} .w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important} .w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important} .w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
.w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important} .w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important} .w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important} .w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important} .w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
.w3-danger{color:#fff!important;background-color:#dd0000!important}
.w3-note{color:#000!important;background-color:#fff599!important}
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
.w3-warning{color:#000!important;background-color:#ffb305!important}
.w3-success{color:#fff!important;background-color:#008a00!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important} .w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important} .w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important} .w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}

View File

@@ -25,14 +25,14 @@
}: }:
pkgs.stdenv.mkDerivation rec { pkgs.stdenv.mkDerivation rec {
pname = "tildefriends"; pname = "tildefriends";
version = "0.0.29"; version = "0.0.31";
src = pkgs.fetchFromGitea { src = pkgs.fetchFromGitea {
domain = "dev.tildefriends.net"; domain = "dev.tildefriends.net";
owner = "cory"; owner = "cory";
repo = "tildefriends"; repo = "tildefriends";
rev = "v${version}"; rev = "v${version}";
hash = "sha256-bQXFpocOYOlFmVj9OZeQhNrgFuTJ8sx2RSw1tgmelOM="; hash = "sha256-c2ZKVNikI5jN5GQuvp7S53qqnRZniSrJMF1FUZdVNPI=";
fetchSubmodules = true; fetchSubmodules = true;
}; };

File diff suppressed because one or more lines are too long

200
deps/codemirror_src/package-lock.json generated vendored
View File

@@ -69,9 +69,9 @@
} }
}, },
"node_modules/@codemirror/lang-javascript": { "node_modules/@codemirror/lang-javascript": {
"version": "6.2.3", "version": "6.2.4",
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.3.tgz", "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz",
"integrity": "sha512-8PR3vIWg7pSu7ur8A07pGiYHgy3hHj+mRYRCSG8q+mPIrl0F02rgpGv+DsQTHRTc30rydOsf5PZ7yjKFg2Ackw==", "integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==",
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.0.0", "@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.6.0", "@codemirror/language": "^6.6.0",
@@ -115,9 +115,9 @@
} }
}, },
"node_modules/@codemirror/search": { "node_modules/@codemirror/search": {
"version": "6.5.10", "version": "6.5.11",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.10.tgz", "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz",
"integrity": "sha512-RMdPdmsrUf53pb2VwflKGHEe1XVM07hI7vV2ntgw1dmqhimpatSJKva4VA9h4TLUDOD4EIF02201oZurpnEFsg==", "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
"dependencies": { "dependencies": {
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0", "@codemirror/view": "^6.0.0",
@@ -144,9 +144,9 @@
} }
}, },
"node_modules/@codemirror/view": { "node_modules/@codemirror/view": {
"version": "6.36.5", "version": "6.37.0",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.5.tgz", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.37.0.tgz",
"integrity": "sha512-cd+FZEUlu3GQCYnguYm3EkhJ8KJVisqqUsCOKedBoAt/d9c76JUUap6U0UrpElln5k6VyrEOYliMuDAKIeDQLg==", "integrity": "sha512-ghHIeRGfWB8h9Tc3sMdr7D5zp4sZvlCzG36Xjdh2ymmfAwvSaCJAAsL3HLyLEnHcE953+5Uox1bx5OS+YCW/7Q==",
"dependencies": { "dependencies": {
"@codemirror/state": "^6.5.0", "@codemirror/state": "^6.5.0",
"style-mod": "^4.1.0", "style-mod": "^4.1.0",
@@ -217,13 +217,13 @@
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==" "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
}, },
"node_modules/@lezer/css": { "node_modules/@lezer/css": {
"version": "1.1.11", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.11.tgz", "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.2.1.tgz",
"integrity": "sha512-FuAnusbLBl1SEAtfN8NdShxYJiESKw9LAFysfea1T96jD3ydBn12oYjaSG1a04BQRIUd93/0D8e5CV1cUMkmQg==", "integrity": "sha512-2F5tOqzKEKbCUNraIXc0f6HKeyKlmMWJnBB0i4XW6dJgssrZO/YlZ2pY5xgyqDleqqhiNJ3dQhbrV2aClZQMvg==",
"dependencies": { "dependencies": {
"@lezer/common": "^1.2.0", "@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0", "@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0" "@lezer/lr": "^1.3.0"
} }
}, },
"node_modules/@lezer/highlight": { "node_modules/@lezer/highlight": {
@@ -344,9 +344,9 @@
} }
}, },
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.40.0", "version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz",
"integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", "integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -356,9 +356,9 @@
] ]
}, },
"node_modules/@rollup/rollup-android-arm64": { "node_modules/@rollup/rollup-android-arm64": {
"version": "4.40.0", "version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz",
"integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", "integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -368,9 +368,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-arm64": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.40.0", "version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz",
"integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", "integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -380,9 +380,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-x64": { "node_modules/@rollup/rollup-darwin-x64": {
"version": "4.40.0", "version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz",
"integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", "integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -392,9 +392,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-arm64": { "node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.40.0", "version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz",
"integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", "integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -404,9 +404,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-x64": { "node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.40.0", "version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz",
"integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", "integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -416,9 +416,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": { "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.40.0", "version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz",
"integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", "integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -428,9 +428,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-musleabihf": { "node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.40.0", "version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz",
"integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", "integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -440,9 +440,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-gnu": { "node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.40.0", "version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz",
"integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", "integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -452,9 +452,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-musl": { "node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.40.0", "version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz",
"integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", "integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -464,9 +464,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-loongarch64-gnu": { "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.40.0", "version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz",
"integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", "integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==",
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
@@ -476,9 +476,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": { "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.40.0", "version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz",
"integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", "integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@@ -488,9 +488,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-gnu": { "node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.40.0", "version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz",
"integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", "integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@@ -500,9 +500,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-musl": { "node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.40.0", "version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz",
"integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", "integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@@ -512,9 +512,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-s390x-gnu": { "node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.40.0", "version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz",
"integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", "integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@@ -524,9 +524,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-gnu": { "node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.40.0", "version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz",
"integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", "integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -536,9 +536,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-musl": { "node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.40.0", "version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz",
"integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", "integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -548,9 +548,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-arm64-msvc": { "node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.40.0", "version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz",
"integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", "integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -560,9 +560,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-ia32-msvc": { "node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.40.0", "version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz",
"integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", "integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -572,9 +572,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-msvc": { "node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.40.0", "version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz",
"integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", "integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -745,9 +745,9 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.40.0", "version": "4.41.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz",
"integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", "integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==",
"dependencies": { "dependencies": {
"@types/estree": "1.0.7" "@types/estree": "1.0.7"
}, },
@@ -759,26 +759,26 @@
"npm": ">=8.0.0" "npm": ">=8.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.40.0", "@rollup/rollup-android-arm-eabi": "4.41.1",
"@rollup/rollup-android-arm64": "4.40.0", "@rollup/rollup-android-arm64": "4.41.1",
"@rollup/rollup-darwin-arm64": "4.40.0", "@rollup/rollup-darwin-arm64": "4.41.1",
"@rollup/rollup-darwin-x64": "4.40.0", "@rollup/rollup-darwin-x64": "4.41.1",
"@rollup/rollup-freebsd-arm64": "4.40.0", "@rollup/rollup-freebsd-arm64": "4.41.1",
"@rollup/rollup-freebsd-x64": "4.40.0", "@rollup/rollup-freebsd-x64": "4.41.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.40.0", "@rollup/rollup-linux-arm-gnueabihf": "4.41.1",
"@rollup/rollup-linux-arm-musleabihf": "4.40.0", "@rollup/rollup-linux-arm-musleabihf": "4.41.1",
"@rollup/rollup-linux-arm64-gnu": "4.40.0", "@rollup/rollup-linux-arm64-gnu": "4.41.1",
"@rollup/rollup-linux-arm64-musl": "4.40.0", "@rollup/rollup-linux-arm64-musl": "4.41.1",
"@rollup/rollup-linux-loongarch64-gnu": "4.40.0", "@rollup/rollup-linux-loongarch64-gnu": "4.41.1",
"@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1",
"@rollup/rollup-linux-riscv64-gnu": "4.40.0", "@rollup/rollup-linux-riscv64-gnu": "4.41.1",
"@rollup/rollup-linux-riscv64-musl": "4.40.0", "@rollup/rollup-linux-riscv64-musl": "4.41.1",
"@rollup/rollup-linux-s390x-gnu": "4.40.0", "@rollup/rollup-linux-s390x-gnu": "4.41.1",
"@rollup/rollup-linux-x64-gnu": "4.40.0", "@rollup/rollup-linux-x64-gnu": "4.41.1",
"@rollup/rollup-linux-x64-musl": "4.40.0", "@rollup/rollup-linux-x64-musl": "4.41.1",
"@rollup/rollup-win32-arm64-msvc": "4.40.0", "@rollup/rollup-win32-arm64-msvc": "4.41.1",
"@rollup/rollup-win32-ia32-msvc": "4.40.0", "@rollup/rollup-win32-ia32-msvc": "4.41.1",
"@rollup/rollup-win32-x64-msvc": "4.40.0", "@rollup/rollup-win32-x64-msvc": "4.41.1",
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
@@ -853,13 +853,13 @@
} }
}, },
"node_modules/terser": { "node_modules/terser": {
"version": "5.39.0", "version": "5.40.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-5.40.0.tgz",
"integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", "integrity": "sha512-cfeKl/jjwSR5ar7d0FGmave9hFGJT8obyo0z+CrQOylLDbk7X81nPU6vq9VORa5jU30SkDnT2FXjLbR8HLP+xA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@jridgewell/source-map": "^0.3.3", "@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2", "acorn": "^8.14.0",
"commander": "^2.20.0", "commander": "^2.20.0",
"source-map-support": "~0.5.20" "source-map-support": "~0.5.20"
}, },

2
deps/libuv vendored

Submodule deps/libuv updated: 8fb9cb9194...5152db2cbf

2
deps/quickjs vendored

1022
deps/sqlite/shell.c vendored

File diff suppressed because it is too large Load Diff

4317
deps/sqlite/sqlite3.c vendored

File diff suppressed because it is too large Load Diff

146
deps/sqlite/sqlite3.h vendored
View File

@@ -133,7 +133,7 @@ extern "C" {
** **
** Since [version 3.6.18] ([dateof:3.6.18]), ** Since [version 3.6.18] ([dateof:3.6.18]),
** SQLite source code has been stored in the ** SQLite source code has been stored in the
** <a href="http://www.fossil-scm.org/">Fossil configuration management ** <a href="http://fossil-scm.org/">Fossil configuration management
** system</a>. ^The SQLITE_SOURCE_ID macro evaluates to ** system</a>. ^The SQLITE_SOURCE_ID macro evaluates to
** a string which identifies a particular check-in of SQLite ** a string which identifies a particular check-in of SQLite
** within its configuration management system. ^The SQLITE_SOURCE_ID ** within its configuration management system. ^The SQLITE_SOURCE_ID
@@ -146,9 +146,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()]. ** [sqlite_version()] and [sqlite_source_id()].
*/ */
#define SQLITE_VERSION "3.49.1" #define SQLITE_VERSION "3.50.0"
#define SQLITE_VERSION_NUMBER 3049001 #define SQLITE_VERSION_NUMBER 3050000
#define SQLITE_SOURCE_ID "2025-02-18 13:38:58 873d4e274b4988d260ba8354a9718324a1c26187a4ab4c1cc0227c03d0f10e70" #define SQLITE_SOURCE_ID "2025-05-29 14:26:00 dfc790f998f450d9c35e3ba1c8c89c17466cb559f87b0239e4aab9d34e28f742"
/* /*
** CAPI3REF: Run-Time Library Version Numbers ** CAPI3REF: Run-Time Library Version Numbers
@@ -1163,6 +1163,12 @@ struct sqlite3_io_methods {
** the value that M is to be set to. Before returning, the 32-bit signed ** the value that M is to be set to. Before returning, the 32-bit signed
** integer is overwritten with the previous value of M. ** integer is overwritten with the previous value of M.
** **
** <li>[[SQLITE_FCNTL_BLOCK_ON_CONNECT]]
** The [SQLITE_FCNTL_BLOCK_ON_CONNECT] opcode is used to configure the
** VFS to block when taking a SHARED lock to connect to a wal mode database.
** This is used to implement the functionality associated with
** SQLITE_SETLK_BLOCK_ON_CONNECT.
**
** <li>[[SQLITE_FCNTL_DATA_VERSION]] ** <li>[[SQLITE_FCNTL_DATA_VERSION]]
** The [SQLITE_FCNTL_DATA_VERSION] opcode is used to detect changes to ** The [SQLITE_FCNTL_DATA_VERSION] opcode is used to detect changes to
** a database file. The argument is a pointer to a 32-bit unsigned integer. ** a database file. The argument is a pointer to a 32-bit unsigned integer.
@@ -1259,6 +1265,7 @@ struct sqlite3_io_methods {
#define SQLITE_FCNTL_CKSM_FILE 41 #define SQLITE_FCNTL_CKSM_FILE 41
#define SQLITE_FCNTL_RESET_CACHE 42 #define SQLITE_FCNTL_RESET_CACHE 42
#define SQLITE_FCNTL_NULL_IO 43 #define SQLITE_FCNTL_NULL_IO 43
#define SQLITE_FCNTL_BLOCK_ON_CONNECT 44
/* deprecated names */ /* deprecated names */
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
@@ -1989,13 +1996,16 @@ struct sqlite3_mem_methods {
** **
** [[SQLITE_CONFIG_LOOKASIDE]] <dt>SQLITE_CONFIG_LOOKASIDE</dt> ** [[SQLITE_CONFIG_LOOKASIDE]] <dt>SQLITE_CONFIG_LOOKASIDE</dt>
** <dd> ^(The SQLITE_CONFIG_LOOKASIDE option takes two arguments that determine ** <dd> ^(The SQLITE_CONFIG_LOOKASIDE option takes two arguments that determine
** the default size of lookaside memory on each [database connection]. ** the default size of [lookaside memory] on each [database connection].
** The first argument is the ** The first argument is the
** size of each lookaside buffer slot and the second is the number of ** size of each lookaside buffer slot ("sz") and the second is the number of
** slots allocated to each database connection.)^ ^(SQLITE_CONFIG_LOOKASIDE ** slots allocated to each database connection ("cnt").)^
** sets the <i>default</i> lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE] ** ^(SQLITE_CONFIG_LOOKASIDE sets the <i>default</i> lookaside size.
** option to [sqlite3_db_config()] can be used to change the lookaside ** The [SQLITE_DBCONFIG_LOOKASIDE] option to [sqlite3_db_config()] can
** configuration on individual connections.)^ </dd> ** be used to change the lookaside configuration on individual connections.)^
** The [-DSQLITE_DEFAULT_LOOKASIDE] option can be used to change the
** default lookaside configuration at compile-time.
** </dd>
** **
** [[SQLITE_CONFIG_PCACHE2]] <dt>SQLITE_CONFIG_PCACHE2</dt> ** [[SQLITE_CONFIG_PCACHE2]] <dt>SQLITE_CONFIG_PCACHE2</dt>
** <dd> ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is ** <dd> ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is
@@ -2232,31 +2242,50 @@ struct sqlite3_mem_methods {
** [[SQLITE_DBCONFIG_LOOKASIDE]] ** [[SQLITE_DBCONFIG_LOOKASIDE]]
** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt> ** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt>
** <dd> The SQLITE_DBCONFIG_LOOKASIDE option is used to adjust the ** <dd> The SQLITE_DBCONFIG_LOOKASIDE option is used to adjust the
** configuration of the lookaside memory allocator within a database ** configuration of the [lookaside memory allocator] within a database
** connection. ** connection.
** The arguments to the SQLITE_DBCONFIG_LOOKASIDE option are <i>not</i> ** The arguments to the SQLITE_DBCONFIG_LOOKASIDE option are <i>not</i>
** in the [DBCONFIG arguments|usual format]. ** in the [DBCONFIG arguments|usual format].
** The SQLITE_DBCONFIG_LOOKASIDE option takes three arguments, not two, ** The SQLITE_DBCONFIG_LOOKASIDE option takes three arguments, not two,
** so that a call to [sqlite3_db_config()] that uses SQLITE_DBCONFIG_LOOKASIDE ** so that a call to [sqlite3_db_config()] that uses SQLITE_DBCONFIG_LOOKASIDE
** should have a total of five parameters. ** should have a total of five parameters.
** ^The first argument (the third parameter to [sqlite3_db_config()] is a ** <ol>
** <li><p>The first argument ("buf") is a
** pointer to a memory buffer to use for lookaside memory. ** pointer to a memory buffer to use for lookaside memory.
** ^The first argument after the SQLITE_DBCONFIG_LOOKASIDE verb ** The first argument may be NULL in which case SQLite will allocate the
** may be NULL in which case SQLite will allocate the ** lookaside buffer itself using [sqlite3_malloc()].
** lookaside buffer itself using [sqlite3_malloc()]. ^The second argument is the ** <li><P>The second argument ("sz") is the
** size of each lookaside buffer slot. ^The third argument is the number of ** size of each lookaside buffer slot. Lookaside is disabled if "sz"
** slots. The size of the buffer in the first argument must be greater than ** is less than 8. The "sz" argument should be a multiple of 8 less than
** or equal to the product of the second and third arguments. The buffer ** 65536. If "sz" does not meet this constraint, it is reduced in size until
** must be aligned to an 8-byte boundary. ^If the second argument to ** it does.
** SQLITE_DBCONFIG_LOOKASIDE is not a multiple of 8, it is internally ** <li><p>The third argument ("cnt") is the number of slots. Lookaside is disabled
** rounded down to the next smaller multiple of 8. ^(The lookaside memory ** if "cnt"is less than 1. The "cnt" value will be reduced, if necessary, so
** that the product of "sz" and "cnt" does not exceed 2,147,418,112. The "cnt"
** parameter is usually chosen so that the product of "sz" and "cnt" is less
** than 1,000,000.
** </ol>
** <p>If the "buf" argument is not NULL, then it must
** point to a memory buffer with a size that is greater than
** or equal to the product of "sz" and "cnt".
** The buffer must be aligned to an 8-byte boundary.
** The lookaside memory
** configuration for a database connection can only be changed when that ** configuration for a database connection can only be changed when that
** connection is not currently using lookaside memory, or in other words ** connection is not currently using lookaside memory, or in other words
** when the "current value" returned by ** when the value returned by [SQLITE_DBSTATUS_LOOKASIDE_USED] is zero.
** [sqlite3_db_status](D,[SQLITE_DBSTATUS_LOOKASIDE_USED],...) is zero.
** Any attempt to change the lookaside memory configuration when lookaside ** Any attempt to change the lookaside memory configuration when lookaside
** memory is in use leaves the configuration unchanged and returns ** memory is in use leaves the configuration unchanged and returns
** [SQLITE_BUSY].)^</dd> ** [SQLITE_BUSY].
** If the "buf" argument is NULL and an attempt
** to allocate memory based on "sz" and "cnt" fails, then
** lookaside is silently disabled.
** <p>
** The [SQLITE_CONFIG_LOOKASIDE] configuration option can be used to set the
** default lookaside configuration at initialization. The
** [-DSQLITE_DEFAULT_LOOKASIDE] option can be used to set the default lookaside
** configuration at compile-time. Typical values for lookaside are 1200 for
** "sz" and 40 to 100 for "cnt".
** </dd>
** **
** [[SQLITE_DBCONFIG_ENABLE_FKEY]] ** [[SQLITE_DBCONFIG_ENABLE_FKEY]]
** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt> ** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt>
@@ -2993,6 +3022,44 @@ SQLITE_API int sqlite3_busy_handler(sqlite3*,int(*)(void*,int),void*);
*/ */
SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms); SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms);
/*
** CAPI3REF: Set the Setlk Timeout
** METHOD: sqlite3
**
** This routine is only useful in SQLITE_ENABLE_SETLK_TIMEOUT builds. If
** the VFS supports blocking locks, it sets the timeout in ms used by
** eligible locks taken on wal mode databases by the specified database
** handle. In non-SQLITE_ENABLE_SETLK_TIMEOUT builds, or if the VFS does
** not support blocking locks, this function is a no-op.
**
** Passing 0 to this function disables blocking locks altogether. Passing
** -1 to this function requests that the VFS blocks for a long time -
** indefinitely if possible. The results of passing any other negative value
** are undefined.
**
** Internally, each SQLite database handle store two timeout values - the
** busy-timeout (used for rollback mode databases, or if the VFS does not
** support blocking locks) and the setlk-timeout (used for blocking locks
** on wal-mode databases). The sqlite3_busy_timeout() method sets both
** values, this function sets only the setlk-timeout value. Therefore,
** to configure separate busy-timeout and setlk-timeout values for a single
** database handle, call sqlite3_busy_timeout() followed by this function.
**
** Whenever the number of connections to a wal mode database falls from
** 1 to 0, the last connection takes an exclusive lock on the database,
** then checkpoints and deletes the wal file. While it is doing this, any
** new connection that tries to read from the database fails with an
** SQLITE_BUSY error. Or, if the SQLITE_SETLK_BLOCK_ON_CONNECT flag is
** passed to this API, the new connection blocks until the exclusive lock
** has been released.
*/
SQLITE_API int sqlite3_setlk_timeout(sqlite3*, int ms, int flags);
/*
** CAPI3REF: Flags for sqlite3_setlk_timeout()
*/
#define SQLITE_SETLK_BLOCK_ON_CONNECT 0x01
/* /*
** CAPI3REF: Convenience Routines For Running Queries ** CAPI3REF: Convenience Routines For Running Queries
** METHOD: sqlite3 ** METHOD: sqlite3
@@ -5108,7 +5175,7 @@ SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int);
** other than [SQLITE_ROW] before any subsequent invocation of ** other than [SQLITE_ROW] before any subsequent invocation of
** sqlite3_step(). Failure to reset the prepared statement using ** sqlite3_step(). Failure to reset the prepared statement using
** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from ** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from
** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1], ** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1]),
** sqlite3_step() began ** sqlite3_step() began
** calling [sqlite3_reset()] automatically in this circumstance rather ** calling [sqlite3_reset()] automatically in this circumstance rather
** than returning [SQLITE_MISUSE]. This is not considered a compatibility ** than returning [SQLITE_MISUSE]. This is not considered a compatibility
@@ -7004,6 +7071,8 @@ SQLITE_API int sqlite3_autovacuum_pages(
** **
** ^The second argument is a pointer to the function to invoke when a ** ^The second argument is a pointer to the function to invoke when a
** row is updated, inserted or deleted in a rowid table. ** row is updated, inserted or deleted in a rowid table.
** ^The update hook is disabled by invoking sqlite3_update_hook()
** with a NULL pointer as the second parameter.
** ^The first argument to the callback is a copy of the third argument ** ^The first argument to the callback is a copy of the third argument
** to sqlite3_update_hook(). ** to sqlite3_update_hook().
** ^The second callback argument is one of [SQLITE_INSERT], [SQLITE_DELETE], ** ^The second callback argument is one of [SQLITE_INSERT], [SQLITE_DELETE],
@@ -11486,9 +11555,10 @@ SQLITE_API void sqlite3session_table_filter(
** is inserted while a session object is enabled, then later deleted while ** is inserted while a session object is enabled, then later deleted while
** the same session object is disabled, no INSERT record will appear in the ** the same session object is disabled, no INSERT record will appear in the
** changeset, even though the delete took place while the session was disabled. ** changeset, even though the delete took place while the session was disabled.
** Or, if one field of a row is updated while a session is disabled, and ** Or, if one field of a row is updated while a session is enabled, and
** another field of the same row is updated while the session is enabled, the ** then another field of the same row is updated while the session is disabled,
** resulting changeset will contain an UPDATE change that updates both fields. ** the resulting changeset will contain an UPDATE change that updates both
** fields.
*/ */
SQLITE_API int sqlite3session_changeset( SQLITE_API int sqlite3session_changeset(
sqlite3_session *pSession, /* Session object */ sqlite3_session *pSession, /* Session object */
@@ -11560,8 +11630,9 @@ SQLITE_API sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession
** database zFrom the contents of the two compatible tables would be ** database zFrom the contents of the two compatible tables would be
** identical. ** identical.
** **
** It an error if database zFrom does not exist or does not contain the ** Unless the call to this function is a no-op as described above, it is an
** required compatible table. ** error if database zFrom does not exist or does not contain the required
** compatible table.
** **
** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite ** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite
** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg ** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg
@@ -11696,7 +11767,7 @@ SQLITE_API int sqlite3changeset_start_v2(
** The following flags may passed via the 4th parameter to ** The following flags may passed via the 4th parameter to
** [sqlite3changeset_start_v2] and [sqlite3changeset_start_v2_strm]: ** [sqlite3changeset_start_v2] and [sqlite3changeset_start_v2_strm]:
** **
** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd> ** <dt>SQLITE_CHANGESETSTART_INVERT <dd>
** Invert the changeset while iterating through it. This is equivalent to ** Invert the changeset while iterating through it. This is equivalent to
** inverting a changeset using sqlite3changeset_invert() before applying it. ** inverting a changeset using sqlite3changeset_invert() before applying it.
** It is an error to specify this flag with a patchset. ** It is an error to specify this flag with a patchset.
@@ -12011,19 +12082,6 @@ SQLITE_API int sqlite3changeset_concat(
void **ppOut /* OUT: Buffer containing output changeset */ void **ppOut /* OUT: Buffer containing output changeset */
); );
/*
** CAPI3REF: Upgrade the Schema of a Changeset/Patchset
*/
SQLITE_API int sqlite3changeset_upgrade(
sqlite3 *db,
const char *zDb,
int nIn, const void *pIn, /* Input changeset */
int *pnOut, void **ppOut /* OUT: Inverse of input */
);
/* /*
** CAPI3REF: Changegroup Handle ** CAPI3REF: Changegroup Handle
** **

View File

@@ -366,6 +366,8 @@ struct sqlite3_api_routines {
/* Version 3.44.0 and later */ /* Version 3.44.0 and later */
void *(*get_clientdata)(sqlite3*,const char*); void *(*get_clientdata)(sqlite3*,const char*);
int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*)); int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*));
/* Version 3.50.0 and later */
int (*setlk_timeout)(sqlite3*,int,int);
}; };
/* /*
@@ -699,6 +701,8 @@ typedef int (*sqlite3_loadext_entry)(
/* Version 3.44.0 and later */ /* Version 3.44.0 and later */
#define sqlite3_get_clientdata sqlite3_api->get_clientdata #define sqlite3_get_clientdata sqlite3_api->get_clientdata
#define sqlite3_set_clientdata sqlite3_api->set_clientdata #define sqlite3_set_clientdata sqlite3_api->set_clientdata
/* Version 3.50.0 and later */
#define sqlite3_setlk_timeout sqlite3_api->setlk_timeout
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)

View File

@@ -14,7 +14,7 @@
- upload to Apple with dist-ios on macos - upload to Apple with dist-ios on macos
- nix - nix
- june and december: update release version - june and december: update release version
- run `nix flake update` - run `nix --extra-experimental-features nix-command --extra-experimental-features flakes flake update`
- comment out the hash in default.nix - comment out the hash in default.nix
- update the version - update the version
- run `nix-build` - run `nix-build`

6
flake.lock generated
View File

@@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1739758141, "lastModified": 1748037224,
"narHash": "sha256-uq6A2L7o1/tR6VfmYhZWoVAwb3gTy7j4Jx30MIrH0rE=", "narHash": "sha256-92vihpZr6dwEMV6g98M5kHZIttrWahb9iRPBm1atcPk=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "c618e28f70257593de75a7044438efc1c1fc0791", "rev": "f09dede81861f3a83f7f06641ead34f02f37597f",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -0,0 +1,13 @@
* Faster loads.
* Minor UI tweaks.
* Added an intro app as part of the initial flow for first-time users.
* Fixed more shutdown issues.
* Fixed a longstanding potential database issue.
* Added a blob export command.
* Refresh blob wants for blobs that are requested over the web.
* Updates:
* CodeMirror
* QuickJS 2025-04-26
* libuv 1.51.0
* sqlite 3.49.2
* w3.css 5.02

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 108 KiB

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.unprompted.tildefriends" package="com.unprompted.tildefriends"
android:versionCode="35" android:versionCode="38"
android:versionName="0.0.30"> android:versionName="0.0.32-wip">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<application <application

View File

@@ -107,7 +107,7 @@ static void _database_get_work(tf_ssb_t* ssb, void* user_data)
database_get_t* work = user_data; database_get_t* work = user_data;
sqlite3_stmt* statement; sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT value FROM properties WHERE id = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW) sqlite3_step(statement) == SQLITE_ROW)
@@ -185,7 +185,7 @@ static void _database_set_work(tf_ssb_t* ssb, void* user_data)
database_set_t* work = user_data; database_set_t* work = user_data;
sqlite3* db = tf_ssb_acquire_db_writer(ssb); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES (?1, ?2, ?3)", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES (?1, ?2, ?3)", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE) sqlite3_bind_text(statement, 3, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE)
@@ -265,7 +265,7 @@ static void _database_exchange_work(tf_ssb_t* ssb, void* user_data)
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (!work->expected) if (!work->expected)
{ {
if (sqlite3_prepare(db, "INSERT INTO properties (id, key, value) VALUES (?1, ?2, ?3) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "INSERT INTO properties (id, key, value) VALUES (?1, ?2, ?3) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE) sqlite3_bind_text(statement, 3, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE)
@@ -275,7 +275,7 @@ static void _database_exchange_work(tf_ssb_t* ssb, void* user_data)
sqlite3_finalize(statement); sqlite3_finalize(statement);
} }
} }
else if (sqlite3_prepare(db, "UPDATE properties SET value = ?1 WHERE id = ?2 AND key = ?3 AND value = ?4", -1, &statement, NULL) == SQLITE_OK) else if (sqlite3_prepare_v2(db, "UPDATE properties SET value = ?1 WHERE id = ?2 AND key = ?3 AND value = ?4", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->id, -1, NULL) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->id, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, work->key, work->key_length, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 3, work->key, work->key_length, NULL) == SQLITE_OK &&
@@ -339,7 +339,7 @@ static void _database_remove_work(tf_ssb_t* ssb, void* user_data)
database_remove_t* work = user_data; database_remove_t* work = user_data;
sqlite3_stmt* statement; sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_writer(ssb); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
if (sqlite3_prepare(db, "DELETE FROM properties WHERE id = ?1 AND key = ?2", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "DELETE FROM properties WHERE id = ?1 AND key = ?2", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_OK) sqlite3_step(statement) == SQLITE_OK)
@@ -401,7 +401,7 @@ static void _database_get_all_work(tf_ssb_t* ssb, void* user_data)
database_get_all_t* work = user_data; database_get_all_t* work = user_data;
sqlite3_stmt* statement; sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, "SELECT key FROM properties WHERE id = ?", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT key FROM properties WHERE id = ?", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK) if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK)
{ {
@@ -487,7 +487,7 @@ static void _database_get_like_work(tf_ssb_t* ssb, void* user_data)
database_get_like_t* work = user_data; database_get_like_t* work = user_data;
sqlite3_stmt* statement; sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, "SELECT key, value FROM properties WHERE id = ? AND KEY LIKE ?", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT key, value FROM properties WHERE id = ? AND KEY LIKE ?", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->pattern, -1, NULL) == SQLITE_OK) if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->pattern, -1, NULL) == SQLITE_OK)
{ {
@@ -566,7 +566,7 @@ static void _databases_list_work(tf_ssb_t* ssb, void* user_data)
databases_list_t* work = user_data; databases_list_t* work = user_data;
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT DISTINCT id FROM properties WHERE id LIKE ?", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT DISTINCT id FROM properties WHERE id LIKE ?", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, work->pattern, -1, NULL) == SQLITE_OK) if (sqlite3_bind_text(statement, 1, work->pattern, -1, NULL) == SQLITE_OK)
{ {

View File

@@ -1116,6 +1116,7 @@ typedef struct _view_t
void* data; void* data;
size_t size; size_t size;
char etag[256]; char etag[256];
char notify_want_blob_id[k_blob_id_len];
bool not_modified; bool not_modified;
} view_t; } view_t;
@@ -1170,7 +1171,13 @@ static void _httpd_endpoint_view_work(tf_ssb_t* ssb, void* user_data)
} }
else else
{ {
tf_ssb_db_blob_get(ssb, blob_id, (uint8_t**)&view->data, &view->size); if (!tf_ssb_db_blob_get(ssb, blob_id, (uint8_t**)&view->data, &view->size))
{
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
tf_ssb_db_add_blob_wants(db, blob_id);
tf_ssb_release_db_writer(ssb, db);
snprintf(view->notify_want_blob_id, sizeof(view->notify_want_blob_id), "%s", blob_id);
}
} }
} }
} }
@@ -1213,6 +1220,12 @@ static void _httpd_endpoint_view_after_work(tf_ssb_t* ssb, int status, void* use
const char* k_payload = tf_http_status_text(404); const char* k_payload = tf_http_status_text(404);
tf_http_respond(view->request, 404, NULL, 0, k_payload, strlen(k_payload)); tf_http_respond(view->request, 404, NULL, 0, k_payload, strlen(k_payload));
} }
if (*view->notify_want_blob_id)
{
tf_ssb_notify_blob_want_added(ssb, view->notify_want_blob_id);
}
tf_free(view->form_data); tf_free(view->form_data);
tf_http_request_unref(view->request); tf_http_request_unref(view->request);
tf_free(view); tf_free(view);

View File

@@ -13,13 +13,13 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.0.30</string> <string>0.0.32</string>
<key>CFBundleSupportedPlatforms</key> <key>CFBundleSupportedPlatforms</key>
<array> <array>
<string>iPhoneOS</string> <string>iPhoneOS</string>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>12</string> <string>14</string>
<key>DTPlatformName</key> <key>DTPlatformName</key>
<string>iphoneos</string> <string>iphoneos</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>

View File

@@ -148,6 +148,7 @@ static int _tf_command_private(const char* file, int argc, char* argv[]);
static int _tf_command_run(const char* file, int argc, char* argv[]); static int _tf_command_run(const char* file, int argc, char* argv[]);
static int _tf_command_sandbox(const char* file, int argc, char* argv[]); static int _tf_command_sandbox(const char* file, int argc, char* argv[]);
static int _tf_command_has_blob(const char* file, int argc, char* argv[]); static int _tf_command_has_blob(const char* file, int argc, char* argv[]);
static int _tf_command_get_blob(const char* file, int argc, char* argv[]);
static int _tf_command_store_blob(const char* file, int argc, char* argv[]); static int _tf_command_store_blob(const char* file, int argc, char* argv[]);
static int _tf_command_create_invite(const char* file, int argc, char* argv[]); static int _tf_command_create_invite(const char* file, int argc, char* argv[]);
static int _tf_command_get_sequence(const char* file, int argc, char* argv[]); static int _tf_command_get_sequence(const char* file, int argc, char* argv[]);
@@ -178,6 +179,7 @@ const command_t k_commands[] = {
{ "get_profile", _tf_command_get_profile, "Get profile information for the given identity." }, { "get_profile", _tf_command_get_profile, "Get profile information for the given identity." },
{ "get_contacts", _tf_command_get_contacts, "Get information about followed, blocked, and friend identities." }, { "get_contacts", _tf_command_get_contacts, "Get information about followed, blocked, and friend identities." },
{ "has_blob", _tf_command_has_blob, "Check whether a blob is in the blob store." }, { "has_blob", _tf_command_has_blob, "Check whether a blob is in the blob store." },
{ "get_blob", _tf_command_get_blob, "Read a file from the blob store." },
{ "store_blob", _tf_command_store_blob, "Write a file to the blob store." }, { "store_blob", _tf_command_store_blob, "Write a file to the blob store." },
{ "verify", _tf_command_verify, "Verify a feed." }, { "verify", _tf_command_verify, "Verify a feed." },
{ "test", _tf_command_test, "Test SSB." }, { "test", _tf_command_test, "Test SSB." },
@@ -186,8 +188,10 @@ const command_t k_commands[] = {
static int _tf_command_test(const char* file, int argc, char* argv[]) static int _tf_command_test(const char* file, int argc, char* argv[])
{ {
#if !defined(__ANDROID__) #if !defined(__ANDROID__)
const char* default_db_path = _get_db_path();
tf_test_options_t test_options = { tf_test_options_t test_options = {
.exe_path = file, .exe_path = file,
.db_path = default_db_path,
}; };
bool show_usage = false; bool show_usage = false;
@@ -195,10 +199,11 @@ static int _tf_command_test(const char* file, int argc, char* argv[])
{ {
static const struct option k_options[] = { static const struct option k_options[] = {
{ "tests", required_argument, NULL, 't' }, { "tests", required_argument, NULL, 't' },
{ "db-path", required_argument, NULL, 'd' },
{ "help", no_argument, NULL, 'h' }, { "help", no_argument, NULL, 'h' },
{ 0 }, { 0 },
}; };
int c = getopt_long(argc, argv, "t:h", k_options, NULL); int c = getopt_long(argc, argv, "t:d:h", k_options, NULL);
if (c == -1) if (c == -1)
{ {
break; break;
@@ -214,6 +219,9 @@ static int _tf_command_test(const char* file, int argc, char* argv[])
case 't': case 't':
test_options.tests = optarg; test_options.tests = optarg;
break; break;
case 'd':
test_options.db_path = optarg;
break;
} }
} }
@@ -229,10 +237,12 @@ static int _tf_command_test(const char* file, int argc, char* argv[])
tf_printf("options\n"); tf_printf("options\n");
tf_printf(" -t, --tests tests Comma-separated list of tests to run. (default: all)\n"); tf_printf(" -t, --tests tests Comma-separated list of tests to run. (default: all)\n");
tf_printf(" -h, --help Show this usage information.\n"); tf_printf(" -h, --help Show this usage information.\n");
tf_free((void*)default_db_path);
return EXIT_FAILURE; return EXIT_FAILURE;
} }
tf_tests(&test_options); tf_tests(&test_options);
tf_free((void*)default_db_path);
return EXIT_SUCCESS; return EXIT_SUCCESS;
#else #else
return EXIT_FAILURE; return EXIT_FAILURE;
@@ -461,7 +471,7 @@ static int _tf_command_publish(const char* file, int argc, char* argv[])
} }
} }
if (show_usage || !user || !identity || !content) if (show_usage || !identity || !content)
{ {
tf_printf("\n%s publish [options]\n\n", file); tf_printf("\n%s publish [options]\n\n", file);
tf_printf("options:\n"); tf_printf("options:\n");
@@ -480,6 +490,14 @@ static int _tf_command_publish(const char* file, int argc, char* argv[])
tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path, NULL); tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path, NULL);
tf_ssb_set_quiet(ssb, true); tf_ssb_set_quiet(ssb, true);
uint8_t private_key[512] = { 0 }; uint8_t private_key[512] = { 0 };
bool free_user = false;
if (!user)
{
user = tf_ssb_db_get_user_for_identity(ssb, identity);
free_user = true;
}
if (tf_ssb_db_identity_get_private_key(ssb, user, identity, private_key, sizeof(private_key))) if (tf_ssb_db_identity_get_private_key(ssb, user, identity, private_key, sizeof(private_key)))
{ {
JSContext* context = tf_ssb_get_context(ssb); JSContext* context = tf_ssb_get_context(ssb);
@@ -509,6 +527,12 @@ static int _tf_command_publish(const char* file, int argc, char* argv[])
{ {
tf_printf("Did not find private key for identity %s belonging to %s.\n", identity, user); tf_printf("Did not find private key for identity %s belonging to %s.\n", identity, user);
} }
if (free_user)
{
tf_free((void*)user);
}
tf_ssb_destroy(ssb); tf_ssb_destroy(ssb);
tf_free((void*)default_db_path); tf_free((void*)default_db_path);
return result; return result;
@@ -531,7 +555,7 @@ static int _tf_command_private(const char* file, int argc, char* argv[])
{ "id", required_argument, NULL, 'i' }, { "id", required_argument, NULL, 'i' },
{ "recipients", required_argument, NULL, 'r' }, { "recipients", required_argument, NULL, 'r' },
{ "db-path", required_argument, NULL, 'd' }, { "db-path", required_argument, NULL, 'd' },
{ "text", required_argument, NULL, 'c' }, { "text", required_argument, NULL, 't' },
{ "help", no_argument, NULL, 'h' }, { "help", no_argument, NULL, 'h' },
{ 0 }, { 0 },
}; };
@@ -566,11 +590,11 @@ static int _tf_command_private(const char* file, int argc, char* argv[])
} }
} }
if (show_usage || !user || !identity || !recipients || !text) if (show_usage || !identity || !recipients || !text)
{ {
tf_printf("\n%s private [options]\n\n", file); tf_printf("\n%s private [options]\n\n", file);
tf_printf("options:\n"); tf_printf("options:\n");
tf_printf(" -u, --user user User owning identity with which to publish.\n"); tf_printf(" -u, --user user User owning identity with which to publish (optional).\n");
tf_printf(" -i, --id identity Identity with which to publish message.\n"); tf_printf(" -i, --id identity Identity with which to publish message.\n");
tf_printf(" -r, --recipients recipients Recipient identities.\n"); tf_printf(" -r, --recipients recipients Recipient identities.\n");
tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path); tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path);
@@ -591,6 +615,13 @@ static int _tf_command_private(const char* file, int argc, char* argv[])
recipient_list[recipient_count++] = identity; recipient_list[recipient_count++] = identity;
bool free_user = false;
if (!user)
{
user = tf_ssb_db_get_user_for_identity(ssb, identity);
free_user = true;
}
if (tf_ssb_db_identity_get_private_key(ssb, user, identity, private_key, sizeof(private_key))) if (tf_ssb_db_identity_get_private_key(ssb, user, identity, private_key, sizeof(private_key)))
{ {
char* copy = tf_strdup(recipients); char* copy = tf_strdup(recipients);
@@ -642,6 +673,11 @@ static int _tf_command_private(const char* file, int argc, char* argv[])
{ {
tf_printf("Did not find private key for identity %s belonging to %s.\n", identity, user); tf_printf("Did not find private key for identity %s belonging to %s.\n", identity, user);
} }
if (free_user)
{
tf_free((void*)user);
}
done: done:
tf_ssb_destroy(ssb); tf_ssb_destroy(ssb);
tf_free((void*)default_db_path); tf_free((void*)default_db_path);
@@ -745,6 +781,113 @@ static int _tf_command_store_blob(const char* file, int argc, char* argv[])
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
static int _tf_command_get_blob(const char* file, int argc, char* argv[])
{
const char* default_db_path = _get_db_path();
const char* db_path = default_db_path;
const char* output = NULL;
const char* blob_id = NULL;
bool show_usage = false;
while (!show_usage)
{
static const struct option k_options[] = {
{ "db-path", required_argument, NULL, 'd' },
{ "blob", required_argument, NULL, 'b' },
{ "output", required_argument, NULL, 'o' },
{ "help", no_argument, NULL, 'h' },
{ 0 },
};
int c = getopt_long(argc, argv, "d:b:o:h", k_options, NULL);
if (c == -1)
{
break;
}
switch (c)
{
case '?':
case 'h':
default:
show_usage = true;
break;
case 'd':
db_path = optarg;
break;
case 'b':
blob_id = optarg;
break;
case 'o':
output = optarg;
break;
}
}
if (show_usage || !blob_id)
{
tf_printf("\n%s store_blob [options]\n\n", file);
tf_printf("options:\n");
tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path);
tf_printf(" -b, --blob blob_id Blob identifier to retrieve.\n");
tf_printf(" -o, --output file_path Location to write the retrieved blob.\n");
tf_printf(" -h, --help Show this usage information.\n");
tf_free((void*)default_db_path);
return EXIT_FAILURE;
}
uint8_t* blob = NULL;
size_t size = 0;
_create_directories_for_file(db_path, 0700);
tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path, NULL);
tf_ssb_set_quiet(ssb, true);
bool fetched = tf_ssb_db_blob_get(ssb, blob_id, &blob, &size);
tf_ssb_destroy(ssb);
if (!fetched)
{
tf_printf("Failed to fetch blob: %s.\n", blob_id);
tf_free((void*)default_db_path);
return EXIT_FAILURE;
}
if (output)
{
FILE* blob_file = fopen(output, "wb");
if (!blob_file)
{
tf_printf("Failed to open %s: %s.\n", output, strerror(errno));
tf_free((void*)default_db_path);
tf_free(blob);
return EXIT_FAILURE;
}
if (fwrite(blob, 1, size, blob_file) != size)
{
tf_printf("Failed to write %s: %s\n", output, strerror(errno));
tf_free(blob);
tf_free((void*)default_db_path);
fclose(blob_file);
return EXIT_FAILURE;
}
fclose(blob_file);
}
else
{
if (fwrite(blob, 1, size, stdout) != size)
{
tf_free(blob);
tf_free((void*)default_db_path);
return EXIT_FAILURE;
}
}
tf_free(blob);
tf_free((void*)default_db_path);
return EXIT_SUCCESS;
}
static int _tf_command_has_blob(const char* file, int argc, char* argv[]) static int _tf_command_has_blob(const char* file, int argc, char* argv[])
{ {
const char* default_db_path = _get_db_path(); const char* default_db_path = _get_db_path();
@@ -1221,7 +1364,7 @@ static int _tf_command_verify(const char* file, int argc, char* argv[])
{ {
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "SELECT DISTINCT author FROM messages ORDER BY author", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT DISTINCT author FROM messages ORDER BY author", -1, &statement, NULL) == SQLITE_OK)
{ {
verified = true; verified = true;
while (sqlite3_step(statement) == SQLITE_ROW) while (sqlite3_step(statement) == SQLITE_ROW)

View File

@@ -386,29 +386,17 @@ size_t tf_mem_get_uv_malloc_size()
return s_uv_malloc_size; return s_uv_malloc_size;
} }
#if defined(__OpenBSD__)
static void* _tf_tls_alloc(size_t size)
#else
static void* _tf_tls_alloc(size_t size, const char* file, int line) static void* _tf_tls_alloc(size_t size, const char* file, int line)
#endif
{ {
return _tf_alloc(&s_tls_malloc_size, size); return _tf_alloc(&s_tls_malloc_size, size);
} }
#if defined(__OpenBSD__)
static void* _tf_tls_realloc(void* ptr, size_t size)
#else
static void* _tf_tls_realloc(void* ptr, size_t size, const char* file, int line) static void* _tf_tls_realloc(void* ptr, size_t size, const char* file, int line)
#endif
{ {
return _tf_realloc(&s_tls_malloc_size, ptr, size); return _tf_realloc(&s_tls_malloc_size, ptr, size);
} }
#if defined(__OpenBSD__)
static void _tf_tls_free(void* ptr)
#else
static void _tf_tls_free(void* ptr, const char* file, int line) static void _tf_tls_free(void* ptr, const char* file, int line)
#endif
{ {
_tf_free(&s_tls_malloc_size, ptr); _tf_free(&s_tls_malloc_size, ptr);
} }

View File

@@ -248,6 +248,8 @@ typedef struct _tf_ssb_t
bool is_replicator; bool is_replicator;
bool is_peer_exchange; bool is_peer_exchange;
bool talk_to_strangers; bool talk_to_strangers;
bool broadcast;
bool discovery;
char* room_name; char* room_name;
char seeds_host[256]; char seeds_host[256];
time_t last_seed_check; time_t last_seed_check;
@@ -2832,7 +2834,11 @@ void tf_ssb_destroy(tf_ssb_t* ssb)
} }
if (ssb->db_writer) if (ssb->db_writer)
{ {
sqlite3_close(ssb->db_writer); int r = sqlite3_close(ssb->db_writer);
if (r != SQLITE_OK)
{
tf_printf("sqlite3_close: %s\n", sqlite3_errstr(r));
}
ssb->db_writer = NULL; ssb->db_writer = NULL;
} }
while (ssb->broadcasts) while (ssb->broadcasts)
@@ -2844,7 +2850,11 @@ void tf_ssb_destroy(tf_ssb_t* ssb)
} }
for (int i = 0; i < ssb->db_readers_count; i++) for (int i = 0; i < ssb->db_readers_count; i++)
{ {
sqlite3_close(ssb->db_readers[i]); int r = sqlite3_close(ssb->db_readers[i]);
if (r != SQLITE_OK)
{
tf_printf("sqlite3_close: %s\n", sqlite3_errstr(r));
}
} }
ssb->db_readers_count = 0; ssb->db_readers_count = 0;
if (ssb->db_readers) if (ssb->db_readers)
@@ -3376,6 +3386,12 @@ static void _tf_ssb_update_seeds_after_work(tf_ssb_t* ssb, int status, void* use
static void _tf_ssb_broadcast_timer(uv_timer_t* timer) static void _tf_ssb_broadcast_timer(uv_timer_t* timer)
{ {
tf_ssb_t* ssb = timer->data; tf_ssb_t* ssb = timer->data;
if (!ssb->broadcast)
{
uv_timer_stop(timer);
return;
}
uv_interface_address_t* info = NULL; uv_interface_address_t* info = NULL;
int count = 0; int count = 0;
if (uv_interface_addresses(&info, &count) == 0) if (uv_interface_addresses(&info, &count) == 0)
@@ -3605,13 +3621,13 @@ void tf_ssb_add_broadcast(tf_ssb_t* ssb, const char* connection, tf_ssb_broadcas
static void _tf_ssb_on_broadcast_listener_recv(uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags) static void _tf_ssb_on_broadcast_listener_recv(uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags)
{ {
if (nread <= 0) tf_ssb_t* ssb = handle->data;
if (nread <= 0 || !ssb->discovery)
{ {
tf_free(buf->base); tf_free(buf->base);
return; return;
} }
tf_ssb_t* ssb = handle->data;
((char*)buf->base)[nread] = '\0'; ((char*)buf->base)[nread] = '\0';
const char* k_delim = ";"; const char* k_delim = ";";
@@ -3714,7 +3730,7 @@ void tf_ssb_broadcast_listener_start(tf_ssb_t* ssb, bool linger)
void tf_ssb_broadcast_sender_start(tf_ssb_t* ssb) void tf_ssb_broadcast_sender_start(tf_ssb_t* ssb)
{ {
if (ssb->broadcast_sender.data) if (ssb->broadcast_sender.data || !ssb->broadcast)
{ {
return; return;
} }
@@ -4428,6 +4444,8 @@ typedef struct _update_settings_t
bool is_replicator; bool is_replicator;
bool is_peer_exchange; bool is_peer_exchange;
bool talk_to_strangers; bool talk_to_strangers;
bool broadcast;
bool discovery;
char seeds_host[256]; char seeds_host[256];
char room_name[1024]; char room_name[1024];
} update_settings_t; } update_settings_t;
@@ -4440,12 +4458,16 @@ static void _tf_ssb_update_settings_work(tf_ssb_t* ssb, void* user_data)
update->is_replicator = true; update->is_replicator = true;
update->is_peer_exchange = true; update->is_peer_exchange = true;
update->talk_to_strangers = true; update->talk_to_strangers = true;
update->broadcast = true;
update->discovery = true;
tf_ssb_db_get_global_setting_bool(db, "room", &update->is_room); tf_ssb_db_get_global_setting_bool(db, "room", &update->is_room);
tf_ssb_db_get_global_setting_bool(db, "replicator", &update->is_replicator); tf_ssb_db_get_global_setting_bool(db, "replicator", &update->is_replicator);
tf_ssb_db_get_global_setting_bool(db, "peer_exchange", &update->is_peer_exchange); tf_ssb_db_get_global_setting_bool(db, "peer_exchange", &update->is_peer_exchange);
tf_ssb_db_get_global_setting_bool(db, "talk_to_strangers", &update->talk_to_strangers); tf_ssb_db_get_global_setting_bool(db, "talk_to_strangers", &update->talk_to_strangers);
tf_ssb_db_get_global_setting_string(db, "room_name", update->room_name, sizeof(update->room_name)); tf_ssb_db_get_global_setting_string(db, "room_name", update->room_name, sizeof(update->room_name));
tf_ssb_db_get_global_setting_string(db, "seeds_host", update->seeds_host, sizeof(update->seeds_host)); tf_ssb_db_get_global_setting_string(db, "seeds_host", update->seeds_host, sizeof(update->seeds_host));
tf_ssb_db_get_global_setting_bool(db, "broadcast", &update->broadcast);
tf_ssb_db_get_global_setting_bool(db, "discovery", &update->discovery);
tf_ssb_release_db_reader(ssb, db); tf_ssb_release_db_reader(ssb, db);
} }
@@ -4457,6 +4479,12 @@ static void _tf_ssb_update_settings_after_work(tf_ssb_t* ssb, int result, void*
tf_ssb_set_is_peer_exchange(ssb, update->is_peer_exchange); tf_ssb_set_is_peer_exchange(ssb, update->is_peer_exchange);
tf_ssb_set_is_replicator(ssb, update->is_replicator); tf_ssb_set_is_replicator(ssb, update->is_replicator);
ssb->talk_to_strangers = update->talk_to_strangers; ssb->talk_to_strangers = update->talk_to_strangers;
ssb->broadcast = update->broadcast;
if (ssb->broadcast && tf_ssb_server_get_port(ssb) && !uv_timer_get_due_in(&ssb->broadcast_timer))
{
uv_timer_start(&ssb->broadcast_timer, _tf_ssb_broadcast_timer, 2000, 2000);
}
ssb->discovery = update->discovery;
snprintf(ssb->seeds_host, sizeof(ssb->seeds_host), "%s", update->seeds_host); snprintf(ssb->seeds_host, sizeof(ssb->seeds_host), "%s", update->seeds_host);
_tf_ssb_start_update_settings(ssb); _tf_ssb_start_update_settings(ssb);
tf_free(update); tf_free(update);
@@ -4630,6 +4658,10 @@ void tf_ssb_sync_start(tf_ssb_t* ssb)
bool tf_ssb_tunnel_create(tf_ssb_t* ssb, const char* portal_id, const char* target_id, int connect_flags) bool tf_ssb_tunnel_create(tf_ssb_t* ssb, const char* portal_id, const char* target_id, int connect_flags)
{ {
if (!portal_id || !target_id)
{
return false;
}
tf_ssb_connection_t* connection = tf_ssb_connection_get(ssb, portal_id); tf_ssb_connection_t* connection = tf_ssb_connection_get(ssb, portal_id);
if (connection && !tf_ssb_connection_get(ssb, target_id)) if (connection && !tf_ssb_connection_get(ssb, target_id))
{ {

View File

@@ -56,8 +56,8 @@ static bool _tf_ssb_connections_get_next_connection(tf_ssb_connections_t* connec
bool result = false; bool result = false;
sqlite3_stmt* statement; sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(connections->ssb); sqlite3* db = tf_ssb_acquire_db_reader(connections->ssb);
if (sqlite3_prepare(db, "SELECT host, port, key FROM connections WHERE last_attempt IS NULL OR (strftime('%s', 'now') - last_attempt > ?1) ORDER BY last_attempt LIMIT 1", -1, if (sqlite3_prepare_v2(db, "SELECT host, port, key FROM connections WHERE last_attempt IS NULL OR (strftime('%s', 'now') - last_attempt > ?1) ORDER BY last_attempt LIMIT 1",
&statement, NULL) == SQLITE_OK) -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_int(statement, 1, 60000) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW) if (sqlite3_bind_int(statement, 1, 60000) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
{ {
@@ -178,7 +178,7 @@ static void _tf_ssb_connections_update_work(tf_ssb_t* ssb, void* user_data)
sqlite3* db = tf_ssb_acquire_db_writer(ssb); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
if (update->attempted) if (update->attempted)
{ {
if (sqlite3_prepare(db, "UPDATE connections SET last_attempt = strftime('%s', 'now') WHERE host = ?1 AND port = ?2 AND key = ?3", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "UPDATE connections SET last_attempt = strftime('%s', 'now') WHERE host = ?1 AND port = ?2 AND key = ?3", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, update->host, -1, NULL) == SQLITE_OK && sqlite3_bind_int(statement, 2, update->port) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, update->host, -1, NULL) == SQLITE_OK && sqlite3_bind_int(statement, 2, update->port) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, update->key, -1, NULL) == SQLITE_OK) sqlite3_bind_text(statement, 3, update->key, -1, NULL) == SQLITE_OK)
@@ -194,7 +194,7 @@ static void _tf_ssb_connections_update_work(tf_ssb_t* ssb, void* user_data)
} }
else if (update->succeeded) else if (update->succeeded)
{ {
if (sqlite3_prepare(db, "UPDATE connections SET last_success = strftime('%s', 'now') WHERE host = ?1 AND port = ?2 AND key = ?3", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "UPDATE connections SET last_success = strftime('%s', 'now') WHERE host = ?1 AND port = ?2 AND key = ?3", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, update->host, -1, NULL) == SQLITE_OK && sqlite3_bind_int(statement, 2, update->port) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, update->host, -1, NULL) == SQLITE_OK && sqlite3_bind_int(statement, 2, update->port) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, update->key, -1, NULL) == SQLITE_OK) sqlite3_bind_text(statement, 3, update->key, -1, NULL) == SQLITE_OK)
@@ -210,7 +210,7 @@ static void _tf_ssb_connections_update_work(tf_ssb_t* ssb, void* user_data)
} }
else else
{ {
if (sqlite3_prepare(db, "INSERT INTO connections (host, port, key) VALUES (?1, ?2, ?3) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "INSERT INTO connections (host, port, key) VALUES (?1, ?2, ?3) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, update->host, -1, NULL) == SQLITE_OK && sqlite3_bind_int(statement, 2, update->port) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, update->host, -1, NULL) == SQLITE_OK && sqlite3_bind_int(statement, 2, update->port) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, update->key, -1, NULL) == SQLITE_OK) sqlite3_bind_text(statement, 3, update->key, -1, NULL) == SQLITE_OK)
@@ -308,7 +308,7 @@ static void _tf_ssb_connections_get_all_work(tf_ssb_t* ssb, void* user_data)
tf_ssb_connections_get_all_work_t* work = user_data; tf_ssb_connections_get_all_work_t* work = user_data;
sqlite3_stmt* statement; sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, "SELECT host, port, key FROM connections ORDER BY last_attempt", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT host, port, key FROM connections ORDER BY last_attempt", -1, &statement, NULL) == SQLITE_OK)
{ {
while (sqlite3_step(statement) == SQLITE_ROW) while (sqlite3_step(statement) == SQLITE_ROW)
{ {

View File

@@ -49,7 +49,7 @@ static bool _tf_ssb_db_has_rows(sqlite3* db, const char* query)
{ {
bool found = false; bool found = false;
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, query, -1, &statement, NULL) == SQLITE_OK)
{ {
int result = SQLITE_OK; int result = SQLITE_OK;
while ((result = sqlite3_step(statement)) == SQLITE_ROW) while ((result = sqlite3_step(statement)) == SQLITE_ROW)
@@ -96,7 +96,7 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
int auto_vacuum = 0; int auto_vacuum = 0;
if (sqlite3_prepare(db, "PRAGMA auto_vacuum", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "PRAGMA auto_vacuum", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_step(statement) == SQLITE_ROW) if (sqlite3_step(statement) == SQLITE_ROW)
{ {
@@ -190,20 +190,23 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
_tf_ssb_db_exec(db, "ALTER TABLE messages RENAME COLUMN sequence_before_author TO flags"); _tf_ssb_db_exec(db, "ALTER TABLE messages RENAME COLUMN sequence_before_author TO flags");
} }
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_id_author_index ON messages (id, author)");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_author_sequence_index ON messages (author, sequence)"); _tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_author_sequence_index ON messages (author, sequence)");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_author_timestamp_index ON messages (author, timestamp)"); _tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_author_timestamp_index ON messages (author, timestamp)");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_channel_author_timestamp_root_index ON messages (content ->> 'channel', author, timestamp, content ->> 'root')");
_tf_ssb_db_exec(db,
"CREATE INDEX IF NOT EXISTS messages_contact_index ON messages(author, sequence, content ->> '$.contact', content ->> '$.following', content ->> '$.blocking') WHERE "
"content ->> '$.type' = 'contact'");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_id_author_index ON messages (id, author)");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_size_by_author_index ON messages (author, length(content))");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_timestamp_index ON messages (timestamp)"); _tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_timestamp_index ON messages (timestamp)");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_type_timestamp_index ON messages (content ->> 'type', timestamp)"); _tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_type_timestamp_index ON messages (content ->> 'type', timestamp)");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_size_by_author_index ON messages (author, length(content))");
_tf_ssb_db_exec(db, "DROP INDEX IF EXISTS messages_type_author_channel_root_timestamp_index ");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_channel_author_timestamp_root_index ON messages (content ->> 'channel', author, timestamp, content ->> 'root')");
_tf_ssb_db_exec(db, "DROP INDEX IF EXISTS messages_type_author_channel_index");
_tf_ssb_db_exec(db, "DROP INDEX IF EXISTS messages_author_id_index"); _tf_ssb_db_exec(db, "DROP INDEX IF EXISTS messages_author_id_index");
_tf_ssb_db_exec(db, "DROP INDEX IF EXISTS messages_by_author_index"); _tf_ssb_db_exec(db, "DROP INDEX IF EXISTS messages_by_author_index");
_tf_ssb_db_exec(db, "DROP INDEX IF EXISTS messages_timestamp_author_index");
_tf_ssb_db_exec(db, "DROP INDEX IF EXISTS messages_id_index"); _tf_ssb_db_exec(db, "DROP INDEX IF EXISTS messages_id_index");
_tf_ssb_db_exec(db, "DROP INDEX IF EXISTS messages_timestamp_author_index");
_tf_ssb_db_exec(db, "DROP INDEX IF EXISTS messages_type_author_channel_index");
_tf_ssb_db_exec(db, "DROP INDEX IF EXISTS messages_type_author_channel_root_rowid_index"); _tf_ssb_db_exec(db, "DROP INDEX IF EXISTS messages_type_author_channel_root_rowid_index");
_tf_ssb_db_exec(db, "DROP INDEX IF EXISTS messages_type_author_channel_root_timestamp_index ");
_tf_ssb_db_exec(db, _tf_ssb_db_exec(db,
"CREATE TABLE IF NOT EXISTS blobs (" "CREATE TABLE IF NOT EXISTS blobs ("
" id TEXT PRIMARY KEY," " id TEXT PRIMARY KEY,"
@@ -274,6 +277,13 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
"CREATE TRIGGER IF NOT EXISTS messages_ad AFTER DELETE ON messages BEGIN INSERT INTO messages_fts(messages_fts, rowid, content) VALUES ('delete', old.rowid, " "CREATE TRIGGER IF NOT EXISTS messages_ad AFTER DELETE ON messages BEGIN INSERT INTO messages_fts(messages_fts, rowid, content) VALUES ('delete', old.rowid, "
"old.content); END"); "old.content); END");
if (_tf_ssb_db_has_rows(db, "SELECT * FROM sqlite_schema WHERE type = 'trigger' AND name = 'messages_ai_refs' AND NOT sql LIKE '%ltrim%'"))
{
tf_printf("Deleting incorrect messages_refs...\n");
_tf_ssb_db_exec(db, "DROP TABLE IF EXISTS messages_refs");
tf_printf("Done.\n");
}
if (!_tf_ssb_db_has_rows(db, "PRAGMA table_list('messages_refs')")) if (!_tf_ssb_db_has_rows(db, "PRAGMA table_list('messages_refs')"))
{ {
_tf_ssb_db_exec(db, "BEGIN TRANSACTION"); _tf_ssb_db_exec(db, "BEGIN TRANSACTION");
@@ -288,8 +298,9 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
"INSERT INTO messages_refs(message, ref) " "INSERT INTO messages_refs(message, ref) "
"SELECT messages.id, j.value FROM messages, json_tree(messages.content) AS j WHERE " "SELECT messages.id, j.value FROM messages, json_tree(messages.content) AS j WHERE "
"j.value LIKE '&%.sha256' OR " "j.value LIKE '&%.sha256' OR "
"j.value LIKE '%%%.sha256' OR " "j.value LIKE '!%%.sha256' ESCAPE '!' OR "
"j.value LIKE '@%.ed25519' " "j.value LIKE '@%.ed25519' OR "
"(j.value LIKE '#%' AND ltrim(substr(j.value, 2), 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_') = '') "
"ON CONFLICT DO NOTHING"); "ON CONFLICT DO NOTHING");
_tf_ssb_db_exec(db, "COMMIT TRANSACTION"); _tf_ssb_db_exec(db, "COMMIT TRANSACTION");
tf_printf("Done.\n"); tf_printf("Done.\n");
@@ -301,8 +312,9 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
"INSERT INTO messages_refs(message, ref) " "INSERT INTO messages_refs(message, ref) "
"SELECT DISTINCT new.id, j.value FROM json_tree(new.content) AS j WHERE " "SELECT DISTINCT new.id, j.value FROM json_tree(new.content) AS j WHERE "
"j.value LIKE '&%.sha256' OR " "j.value LIKE '&%.sha256' OR "
"j.value LIKE '%%%.sha256' OR " "j.value LIKE '!%%.sha256' ESCAPE '!' OR "
"j.value LIKE '@%.ed25519' " "j.value LIKE '@%.ed25519' OR "
"(j.value LIKE '#%' AND ltrim(substr(j.value, 2), 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_') = '') "
"ON CONFLICT DO NOTHING; END"); "ON CONFLICT DO NOTHING; END");
_tf_ssb_db_exec(db, "DROP TRIGGER IF EXISTS messages_ad_refs"); _tf_ssb_db_exec(db, "DROP TRIGGER IF EXISTS messages_ad_refs");
_tf_ssb_db_exec(db, "CREATE TRIGGER IF NOT EXISTS messages_ad_refs AFTER DELETE ON messages BEGIN DELETE FROM messages_refs WHERE messages_refs.message = old.id; END"); _tf_ssb_db_exec(db, "CREATE TRIGGER IF NOT EXISTS messages_ad_refs AFTER DELETE ON messages BEGIN DELETE FROM messages_refs WHERE messages_refs.message = old.id; END");
@@ -376,6 +388,15 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
_tf_ssb_db_exec(db, "COMMIT TRANSACTION"); _tf_ssb_db_exec(db, "COMMIT TRANSACTION");
tf_printf("Done.\n"); tf_printf("Done.\n");
} }
if (!_tf_ssb_db_has_rows(db, "SELECT * FROM sqlite_schema WHERE type = 'index' AND name = 'blob_wants_cache_source_id_unique_index'"))
{
tf_printf("Creating blob_wants_cache UNIQUE constraint.\n");
_tf_ssb_db_exec(db, "BEGIN TRANSACTION");
_tf_ssb_db_exec(db, "DELETE FROM blob_wants_cache WHERE source IS NULL");
_tf_ssb_db_exec(db, "CREATE UNIQUE INDEX blob_wants_cache_source_id_unique_index ON blob_wants_cache (COALESCE(source, ''), id)");
_tf_ssb_db_exec(db, "COMMIT TRANSACTION");
tf_printf("Done.\n");
}
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS blob_wants_cache_id_idx ON blob_wants_cache (id)"); _tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS blob_wants_cache_id_idx ON blob_wants_cache (id)");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS blob_wants_cache_timestamp_id_idx ON blob_wants_cache (timestamp, id)"); _tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS blob_wants_cache_timestamp_id_idx ON blob_wants_cache (timestamp, id)");
@@ -403,7 +424,7 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
bool need_add_flags = true; bool need_add_flags = true;
bool need_convert_timestamp_to_real = false; bool need_convert_timestamp_to_real = false;
if (sqlite3_prepare(db, "PRAGMA table_info(messages)", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "PRAGMA table_info(messages)", -1, &statement, NULL) == SQLITE_OK)
{ {
int result = SQLITE_OK; int result = SQLITE_OK;
while ((result = sqlite3_step(statement)) == SQLITE_ROW) while ((result = sqlite3_step(statement)) == SQLITE_ROW)
@@ -452,7 +473,7 @@ static bool _tf_ssb_db_previous_message_exists(sqlite3* db, const char* author,
else else
{ {
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT COUNT(*), id != ?3 AS is_mismatch FROM messages WHERE author = ?1 AND sequence = ?2", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT COUNT(*), id != ?3 AS is_mismatch FROM messages WHERE author = ?1 AND sequence = ?2", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, author, -1, NULL) == SQLITE_OK && sqlite3_bind_int64(statement, 2, sequence - 1) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, author, -1, NULL) == SQLITE_OK && sqlite3_bind_int64(statement, 2, sequence - 1) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, previous, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW) sqlite3_bind_text(statement, 3, previous, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
@@ -477,7 +498,7 @@ static int64_t _tf_ssb_db_store_message_raw(sqlite3* db, const char* id, const c
const char* query = "INSERT INTO messages (id, previous, author, sequence, timestamp, content, hash, signature, flags) VALUES (?, ?, ?, ?, ?, jsonb(?), " const char* query = "INSERT INTO messages (id, previous, author, sequence, timestamp, content, hash, signature, flags) VALUES (?, ?, ?, ?, ?, jsonb(?), "
"?, ?, ?) ON CONFLICT DO NOTHING"; "?, ?, ?) ON CONFLICT DO NOTHING";
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, query, -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK &&
(previous ? sqlite3_bind_text(statement, 2, previous, -1, NULL) : sqlite3_bind_null(statement, 2)) == SQLITE_OK && (previous ? sqlite3_bind_text(statement, 2, previous, -1, NULL) : sqlite3_bind_null(statement, 2)) == SQLITE_OK &&
@@ -527,9 +548,9 @@ static char* _tf_ssb_db_get_message_blob_wants(tf_ssb_t* ssb, int64_t rowid)
char* result = NULL; char* result = NULL;
size_t size = 0; size_t size = 0;
if (sqlite3_prepare(db, if (sqlite3_prepare_v2(db,
"SELECT DISTINCT json.value FROM messages, json_tree(messages.content) AS json LEFT OUTER JOIN blobs ON json.value = blobs.id WHERE messages.rowid = ?1 AND " "SELECT DISTINCT json.value FROM messages, json_tree(messages.content) AS json LEFT OUTER JOIN blobs ON json.value = blobs.id WHERE messages.rowid = ?1 AND "
"json.value LIKE '&%%.sha256' AND length(json.value) = ?2 AND blobs.content IS NULL", "json.value LIKE '&%.sha256' AND length(json.value) = ?2 AND blobs.content IS NULL",
-1, &statement, NULL) == SQLITE_OK) -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_int64(statement, 1, rowid) == SQLITE_OK && sqlite3_bind_int(statement, 2, k_blob_id_len - 1) == SQLITE_OK) if (sqlite3_bind_int64(statement, 1, rowid) == SQLITE_OK && sqlite3_bind_int(statement, 2, k_blob_id_len - 1) == SQLITE_OK)
@@ -764,7 +785,7 @@ bool tf_ssb_db_message_content_get(tf_ssb_t* ssb, const char* id, uint8_t** out_
sqlite3_stmt* statement; sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
const char* query = "SELECT json(content) FROM messages WHERE id = ?"; const char* query = "SELECT json(content) FROM messages WHERE id = ?";
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, query, -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW) if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
{ {
@@ -793,7 +814,7 @@ bool tf_ssb_db_blob_has(sqlite3* db, const char* id)
bool result = false; bool result = false;
sqlite3_stmt* statement; sqlite3_stmt* statement;
const char* query = "SELECT COUNT(*) FROM blobs WHERE id = ?1"; const char* query = "SELECT COUNT(*) FROM blobs WHERE id = ?1";
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, query, -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW) if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
{ {
@@ -810,7 +831,7 @@ bool tf_ssb_db_blob_get(tf_ssb_t* ssb, const char* id, uint8_t** out_blob, size_
sqlite3_stmt* statement; sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
const char* query = "SELECT content FROM blobs WHERE id = ?1"; const char* query = "SELECT content FROM blobs WHERE id = ?1";
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, query, -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW) if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
{ {
@@ -837,6 +858,31 @@ bool tf_ssb_db_blob_get(tf_ssb_t* ssb, const char* id, uint8_t** out_blob, size_
return result; return result;
} }
void tf_ssb_db_add_blob_wants(sqlite3* db, const char* id)
{
sqlite3_stmt* statement;
if (sqlite3_prepare_v2(db, "INSERT OR REPLACE INTO blob_wants_cache (id, timestamp) VALUES (?, unixepoch() * 1000) ON CONFLICT DO UPDATE SET timestamp = excluded.timestamp",
-1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK)
{
if (sqlite3_step(statement) != SQLITE_DONE)
{
tf_printf("blob wants cache update failed: %s.\n", sqlite3_errmsg(db));
}
else
{
tf_printf("want: %s\n", id);
}
}
sqlite3_finalize(statement);
}
else
{
tf_printf("prepare failed: %s\n", sqlite3_errmsg(db));
}
}
typedef struct _blob_get_async_t typedef struct _blob_get_async_t
{ {
tf_ssb_t* ssb; tf_ssb_t* ssb;
@@ -941,7 +987,8 @@ bool tf_ssb_db_blob_store(tf_ssb_t* ssb, const uint8_t* blob, size_t size, char*
sqlite3* db = tf_ssb_acquire_db_writer(ssb); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, "INSERT INTO blobs (id, content, created) VALUES (?1, ?2, CAST(strftime('%s') AS INTEGER)) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "INSERT INTO blobs (id, content, created) VALUES (?1, ?2, CAST(strftime('%s') AS INTEGER)) ON CONFLICT DO NOTHING", -1, &statement, NULL) ==
SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_bind_blob(statement, 2, blob, size, NULL) == SQLITE_OK) if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_bind_blob(statement, 2, blob, size, NULL) == SQLITE_OK)
{ {
@@ -983,7 +1030,7 @@ bool tf_ssb_db_get_message_by_author_and_sequence(tf_ssb_t* ssb, const char* aut
sqlite3_stmt* statement; sqlite3_stmt* statement;
const char* query = "SELECT id, previous, timestamp, json(content), hash, signature, flags FROM messages WHERE author = ?1 AND sequence = ?2"; const char* query = "SELECT id, previous, timestamp, json(content), hash, signature, flags FROM messages WHERE author = ?1 AND sequence = ?2";
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, query, -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, author, -1, NULL) == SQLITE_OK && sqlite3_bind_int64(statement, 2, sequence) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW) if (sqlite3_bind_text(statement, 1, author, -1, NULL) == SQLITE_OK && sqlite3_bind_int64(statement, 2, sequence) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
{ {
@@ -1046,7 +1093,7 @@ bool tf_ssb_db_get_latest_message_by_author(tf_ssb_t* ssb, const char* author, i
if (out_message_id) if (out_message_id)
{ {
const char* query = "SELECT id, sequence FROM messages WHERE author = ?1 ORDER BY sequence DESC LIMIT 1"; const char* query = "SELECT id, sequence FROM messages WHERE author = ?1 ORDER BY sequence DESC LIMIT 1";
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, query, -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, author, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW) if (sqlite3_bind_text(statement, 1, author, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
{ {
@@ -1070,7 +1117,7 @@ bool tf_ssb_db_get_latest_message_by_author(tf_ssb_t* ssb, const char* author, i
else else
{ {
const char* query = "SELECT max_sequence FROM messages_stats WHERE author = ?1"; const char* query = "SELECT max_sequence FROM messages_stats WHERE author = ?1";
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, query, -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, author, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW) if (sqlite3_bind_text(statement, 1, author, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
{ {
@@ -1219,7 +1266,7 @@ JSValue tf_ssb_db_visit_query(tf_ssb_t* ssb, const char* query, const JSValue bi
sqlite3* db = tf_ssb_acquire_db_reader_restricted(ssb); sqlite3* db = tf_ssb_acquire_db_reader_restricted(ssb);
JSContext* context = tf_ssb_get_context(ssb); JSContext* context = tf_ssb_get_context(ssb);
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, query, -1, &statement, NULL) == SQLITE_OK)
{ {
JSValue bind_result = _tf_ssb_sqlite_bind_json(context, db, statement, binds); JSValue bind_result = _tf_ssb_sqlite_bind_json(context, db, statement, binds);
if (JS_IsUndefined(bind_result)) if (JS_IsUndefined(bind_result))
@@ -1280,7 +1327,7 @@ int tf_ssb_db_identity_get_count_for_user(tf_ssb_t* ssb, const char* user)
int count = 0; int count = 0;
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "SELECT COUNT(*) FROM identities WHERE user = ?", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT COUNT(*) FROM identities WHERE user = ?", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK) if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK)
{ {
@@ -1326,7 +1373,7 @@ bool tf_ssb_db_identity_add(tf_ssb_t* ssb, const char* user, const char* public_
bool added = false; bool added = false;
sqlite3* db = tf_ssb_acquire_db_writer(ssb); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "INSERT INTO identities (user, public_key, private_key) VALUES (?, ?, ?) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "INSERT INTO identities (user, public_key, private_key) VALUES (?, ?, ?) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, public_key, -1, NULL) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, public_key, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, private_key, -1, NULL) == SQLITE_OK) sqlite3_bind_text(statement, 3, private_key, -1, NULL) == SQLITE_OK)
@@ -1349,7 +1396,7 @@ bool tf_ssb_db_identity_delete(tf_ssb_t* ssb, const char* user, const char* publ
sqlite3* db = tf_ssb_acquire_db_writer(ssb); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
tf_printf("deleting [%s] [%s]\n", user, public_key); tf_printf("deleting [%s] [%s]\n", user, public_key);
if (sqlite3_prepare(db, "DELETE FROM identities WHERE user = ? AND public_key = ?", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "DELETE FROM identities WHERE user = ? AND public_key = ?", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, public_key, -1, NULL) == SQLITE_OK) if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, public_key, -1, NULL) == SQLITE_OK)
{ {
@@ -1369,7 +1416,7 @@ void tf_ssb_db_identity_visit(tf_ssb_t* ssb, const char* user, void (*callback)(
{ {
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "SELECT public_key FROM identities WHERE user = ? ORDER BY public_key", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT public_key FROM identities WHERE user = ? ORDER BY public_key", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK) if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK)
{ {
@@ -1387,7 +1434,7 @@ void tf_ssb_db_identity_visit_all(tf_ssb_t* ssb, void (*callback)(const char* id
{ {
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "SELECT public_key FROM identities ORDER BY public_key", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT public_key FROM identities ORDER BY public_key", -1, &statement, NULL) == SQLITE_OK)
{ {
while (sqlite3_step(statement) == SQLITE_ROW) while (sqlite3_step(statement) == SQLITE_ROW)
{ {
@@ -1398,6 +1445,34 @@ void tf_ssb_db_identity_visit_all(tf_ssb_t* ssb, void (*callback)(const char* id
tf_ssb_release_db_reader(ssb, db); tf_ssb_release_db_reader(ssb, db);
} }
const char* tf_ssb_db_get_user_for_identity(tf_ssb_t* ssb, const char* public_key)
{
const char* result = NULL;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = NULL;
if (sqlite3_prepare_v2(db, "SELECT user FROM identities WHERE public_key = ?", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, (public_key && *public_key == '@') ? public_key + 1 : public_key, -1, NULL) == SQLITE_OK)
{
if (sqlite3_step(statement) == SQLITE_ROW)
{
result = tf_strdup((const char*)sqlite3_column_text(statement, 0));
}
}
else
{
tf_printf("Bind failed: %s.\n", sqlite3_errmsg(db));
}
sqlite3_finalize(statement);
}
else
{
tf_printf("Prepare failed: %s.\n", sqlite3_errmsg(db));
}
tf_ssb_release_db_reader(ssb, db);
return result;
}
bool tf_ssb_db_identity_get_private_key(tf_ssb_t* ssb, const char* user, const char* public_key, uint8_t* out_private_key, size_t private_key_size) bool tf_ssb_db_identity_get_private_key(tf_ssb_t* ssb, const char* user, const char* public_key, uint8_t* out_private_key, size_t private_key_size)
{ {
bool success = false; bool success = false;
@@ -1407,7 +1482,7 @@ bool tf_ssb_db_identity_get_private_key(tf_ssb_t* ssb, const char* user, const c
} }
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "SELECT private_key FROM identities WHERE user = ? AND public_key = ?", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT private_key FROM identities WHERE user = ? AND public_key = ?", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 2, (public_key && *public_key == '@') ? public_key + 1 : public_key, -1, NULL) == SQLITE_OK) sqlite3_bind_text(statement, 2, (public_key && *public_key == '@') ? public_key + 1 : public_key, -1, NULL) == SQLITE_OK)
@@ -1549,75 +1624,65 @@ static following_t* _make_following_node(const char* id, following_t*** followin
return entry; return entry;
} }
static void _populate_follows_and_blocks(tf_ssb_t* ssb, following_t* entry, following_t*** following, int* following_count, block_node_t* active_blocks, bool include_blocks) static void _populate_follows_and_blocks(
sqlite3* db, sqlite3_stmt* statement, following_t* entry, following_t*** following, int* following_count, block_node_t* active_blocks, bool include_blocks)
{ {
sqlite3* db = tf_ssb_acquire_db_reader(ssb); if (sqlite3_bind_text(statement, 1, entry->id, -1, NULL) == SQLITE_OK)
sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db,
"SELECT content ->> '$.contact' AS contact, content ->> '$.following', content ->> '$.blocking' "
"FROM messages "
"WHERE contact IS NOT NULL AND author = ? AND content ->> '$.type' = 'contact' "
"ORDER BY sequence",
-1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, entry->id, -1, NULL) == SQLITE_OK) while (sqlite3_step(statement) == SQLITE_ROW)
{ {
while (sqlite3_step(statement) == SQLITE_ROW) const char* contact = (const char*)sqlite3_column_text(statement, 0);
if (sqlite3_column_type(statement, 1) != SQLITE_NULL)
{ {
const char* contact = (const char*)sqlite3_column_text(statement, 0); bool is_following = sqlite3_column_int(statement, 1) != 0;
if (sqlite3_column_type(statement, 1) != SQLITE_NULL) following_t* next = _make_following_node(contact, following, following_count, active_blocks, include_blocks);
if (next)
{ {
bool is_following = sqlite3_column_int(statement, 1) != 0; if (is_following)
following_t* next = _make_following_node(contact, following, following_count, active_blocks, include_blocks);
if (next)
{ {
if (is_following) if (_add_following_entry(&entry->following, &entry->following_count, next))
{ {
if (_add_following_entry(&entry->following, &entry->following_count, next)) next->ref_count++;
{
next->ref_count++;
}
} }
else }
else
{
if (_remove_following_entry(&entry->following, &entry->following_count, next))
{ {
if (_remove_following_entry(&entry->following, &entry->following_count, next)) next->ref_count--;
{
next->ref_count--;
}
} }
} }
} }
if (sqlite3_column_type(statement, 2) != SQLITE_NULL) }
if (sqlite3_column_type(statement, 2) != SQLITE_NULL)
{
bool is_blocking = sqlite3_column_int(statement, 2) != 0;
following_t* next = _make_following_node(contact, following, following_count, active_blocks, include_blocks);
if (next)
{ {
bool is_blocking = sqlite3_column_int(statement, 2) != 0; if (is_blocking)
following_t* next = _make_following_node(contact, following, following_count, active_blocks, include_blocks);
if (next)
{ {
if (is_blocking) if (_add_following_entry(&entry->blocking, &entry->blocking_count, next))
{ {
if (_add_following_entry(&entry->blocking, &entry->blocking_count, next)) next->block_ref_count++;
{
next->block_ref_count++;
}
} }
else }
else
{
if (_remove_following_entry(&entry->blocking, &entry->blocking_count, next))
{ {
if (_remove_following_entry(&entry->blocking, &entry->blocking_count, next)) next->block_ref_count--;
{
next->block_ref_count--;
}
} }
} }
} }
} }
} }
sqlite3_finalize(statement);
} }
tf_ssb_release_db_reader(ssb, db); sqlite3_reset(statement);
} }
static void _get_following( static void _get_following(sqlite3* db, sqlite3_stmt* statement, following_t* entry, following_t*** following, int* following_count, int depth, int max_depth,
tf_ssb_t* ssb, following_t* entry, following_t*** following, int* following_count, int depth, int max_depth, block_node_t* active_blocks, bool include_blocks) block_node_t* active_blocks, bool include_blocks)
{ {
int old_depth = entry->depth; int old_depth = entry->depth;
entry->depth = tf_min(depth, entry->depth); entry->depth = tf_min(depth, entry->depth);
@@ -1628,28 +1693,48 @@ static void _get_following(
if (!entry->populated) if (!entry->populated)
{ {
entry->populated = true; entry->populated = true;
_populate_follows_and_blocks(ssb, entry, following, following_count, active_blocks, include_blocks); _populate_follows_and_blocks(db, statement, entry, following, following_count, active_blocks, include_blocks);
} }
block_node_t blocks = { .entry = entry, .parent = active_blocks }; block_node_t blocks = { .entry = entry, .parent = active_blocks };
for (int i = 0; i < entry->following_count; i++) for (int i = 0; i < entry->following_count; i++)
{ {
_get_following(ssb, entry->following[i], following, following_count, depth + 1, max_depth, &blocks, include_blocks); _get_following(db, statement, entry->following[i], following, following_count, depth + 1, max_depth, &blocks, include_blocks);
} }
} }
} }
} }
static sqlite3_stmt* _make_following_statement(sqlite3* db)
{
sqlite3_stmt* statement = NULL;
if (sqlite3_prepare_v2(db,
"SELECT content ->> '$.contact' AS contact, content ->> '$.following', content ->> '$.blocking' "
"FROM messages "
"WHERE author = ? AND content ->> '$.type' = 'contact' AND contact IS NOT NULL "
"ORDER BY content ->> '$.contact', sequence",
-1, &statement, NULL) != SQLITE_OK)
{
tf_printf("prepare failed: %s", sqlite3_errmsg(db));
}
return statement;
}
tf_ssb_following_t* tf_ssb_db_following_deep(tf_ssb_t* ssb, const char** ids, int count, int depth, bool include_blocks) tf_ssb_following_t* tf_ssb_db_following_deep(tf_ssb_t* ssb, const char** ids, int count, int depth, bool include_blocks)
{ {
following_t** following = NULL; following_t** following = NULL;
int following_count = 0; int following_count = 0;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = _make_following_statement(db);
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
following_t* entry = _make_following_node(ids[i], &following, &following_count, NULL, include_blocks); following_t* entry = _make_following_node(ids[i], &following, &following_count, NULL, include_blocks);
_get_following(ssb, entry, &following, &following_count, 0, depth, NULL, include_blocks); _get_following(db, statement, entry, &following, &following_count, 0, depth, NULL, include_blocks);
entry->ref_count++; entry->ref_count++;
} }
sqlite3_finalize(statement);
tf_ssb_release_db_reader(ssb, db);
int actual_following_count = 0; int actual_following_count = 0;
for (int i = 0; i < following_count; i++) for (int i = 0; i < following_count; i++)
@@ -1693,12 +1778,17 @@ const char** tf_ssb_db_following_deep_ids(tf_ssb_t* ssb, const char** ids, int c
{ {
following_t** following = NULL; following_t** following = NULL;
int following_count = 0; int following_count = 0;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = _make_following_statement(db);
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
following_t* entry = _make_following_node(ids[i], &following, &following_count, NULL, false); following_t* entry = _make_following_node(ids[i], &following, &following_count, NULL, false);
_get_following(ssb, entry, &following, &following_count, 0, depth, NULL, false); _get_following(db, statement, entry, &following, &following_count, 0, depth, NULL, false);
entry->ref_count++; entry->ref_count++;
} }
sqlite3_finalize(statement);
tf_ssb_release_db_reader(ssb, db);
int actual_following_count = 0; int actual_following_count = 0;
for (int i = 0; i < following_count; i++) for (int i = 0; i < following_count; i++)
@@ -1769,7 +1859,7 @@ JSValue tf_ssb_db_get_message_by_id(tf_ssb_t* ssb, const char* id, bool is_keys)
JSContext* context = tf_ssb_get_context(ssb); JSContext* context = tf_ssb_get_context(ssb);
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT previous, author, id, sequence, timestamp, hash, json(content), signature, flags FROM messages WHERE id = ?", -1, &statement, NULL) == if (sqlite3_prepare_v2(db, "SELECT previous, author, id, sequence, timestamp, hash, json(content), signature, flags FROM messages WHERE id = ?", -1, &statement, NULL) ==
SQLITE_OK) SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK) if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK)
@@ -1807,13 +1897,15 @@ tf_ssb_db_stored_connection_t* tf_ssb_db_get_stored_connections(tf_ssb_t* ssb, i
int count = 0; int count = 0;
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT host, port, key FROM connections ORDER BY host, port, key", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT host, port, key, last_attempt, last_success FROM connections ORDER BY host, port, key", -1, &statement, NULL) == SQLITE_OK)
{ {
while (sqlite3_step(statement) == SQLITE_ROW) while (sqlite3_step(statement) == SQLITE_ROW)
{ {
result = tf_resize_vec(result, sizeof(tf_ssb_db_stored_connection_t) * (count + 1)); result = tf_resize_vec(result, sizeof(tf_ssb_db_stored_connection_t) * (count + 1));
result[count] = (tf_ssb_db_stored_connection_t) { result[count] = (tf_ssb_db_stored_connection_t) {
.port = sqlite3_column_int(statement, 1), .port = sqlite3_column_int(statement, 1),
.last_attempt = sqlite3_column_int64(statement, 3),
.last_success = sqlite3_column_int64(statement, 4),
}; };
snprintf(result[count].address, sizeof(result[count].address), "%s", (const char*)sqlite3_column_text(statement, 0)); snprintf(result[count].address, sizeof(result[count].address), "%s", (const char*)sqlite3_column_text(statement, 0));
snprintf(result[count].pubkey, sizeof(result[count].pubkey), "%s", (const char*)sqlite3_column_text(statement, 2)); snprintf(result[count].pubkey, sizeof(result[count].pubkey), "%s", (const char*)sqlite3_column_text(statement, 2));
@@ -1831,7 +1923,7 @@ void tf_ssb_db_forget_stored_connection(tf_ssb_t* ssb, const char* address, int
{ {
sqlite3* db = tf_ssb_acquire_db_writer(ssb); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, "DELETE FROM connections WHERE host = ? AND port = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "DELETE FROM connections WHERE host = ? AND port = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, address, -1, NULL) != SQLITE_OK || sqlite3_bind_int(statement, 2, port) != SQLITE_OK || if (sqlite3_bind_text(statement, 1, address, -1, NULL) != SQLITE_OK || sqlite3_bind_int(statement, 2, port) != SQLITE_OK ||
sqlite3_bind_text(statement, 3, pubkey, -1, NULL) != SQLITE_OK || sqlite3_step(statement) != SQLITE_DONE) sqlite3_bind_text(statement, 3, pubkey, -1, NULL) != SQLITE_OK || sqlite3_step(statement) != SQLITE_DONE)
@@ -1848,7 +1940,7 @@ bool tf_ssb_db_get_account_password_hash(tf_ssb_t* ssb, const char* name, char*
bool result = false; bool result = false;
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "SELECT value ->> '$.password' FROM properties WHERE id = 'auth' AND key = 'user:' || ?", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT value ->> '$.password' FROM properties WHERE id = 'auth' AND key = 'user:' || ?", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, name, -1, NULL) == SQLITE_OK) if (sqlite3_bind_text(statement, 1, name, -1, NULL) == SQLITE_OK)
{ {
@@ -1883,7 +1975,7 @@ bool tf_ssb_db_set_account_password(uv_loop_t* loop, sqlite3* db, JSContext* con
const char* user_string = JS_ToCStringLen(context, &user_length, user_json); const char* user_string = JS_ToCStringLen(context, &user_length, user_json);
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES ('auth', 'user:' || ?, ?)", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES ('auth', 'user:' || ?, ?)", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, name, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, user_string, user_length, NULL) == SQLITE_OK) if (sqlite3_bind_text(statement, 1, name, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, user_string, user_length, NULL) == SQLITE_OK)
{ {
@@ -1908,7 +2000,7 @@ bool tf_ssb_db_register_account(uv_loop_t* loop, sqlite3* db, JSContext* context
if (registration_allowed) if (registration_allowed)
{ {
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = 'auth' AND key = 'users'", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT value FROM properties WHERE id = 'auth' AND key = 'users'", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_step(statement) == SQLITE_ROW) if (sqlite3_step(statement) == SQLITE_ROW)
{ {
@@ -1927,7 +2019,7 @@ bool tf_ssb_db_register_account(uv_loop_t* loop, sqlite3* db, JSContext* context
JS_FreeValue(context, users_array); JS_FreeValue(context, users_array);
size_t value_length = 0; size_t value_length = 0;
const char* value = JS_ToCStringLen(context, &value_length, json); const char* value = JS_ToCStringLen(context, &value_length, json);
if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES ('auth', 'users', ?)", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES ('auth', 'users', ?)", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, value, value_length, NULL) == SQLITE_OK) if (sqlite3_bind_text(statement, 1, value, value_length, NULL) == SQLITE_OK)
{ {
@@ -1948,7 +2040,7 @@ const char* tf_ssb_db_get_property(tf_ssb_t* ssb, const char* id, const char* ke
char* result = NULL; char* result = NULL;
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT value FROM properties WHERE id = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, key, -1, NULL) == SQLITE_OK) if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, key, -1, NULL) == SQLITE_OK)
{ {
@@ -1971,7 +2063,7 @@ bool tf_ssb_db_set_property(tf_ssb_t* ssb, const char* id, const char* key, cons
bool result = false; bool result = false;
sqlite3* db = tf_ssb_acquire_db_writer(ssb); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES (?, ?, ?)", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES (?, ?, ?)", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, key, -1, NULL) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, key, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, value, -1, NULL) == SQLITE_OK) sqlite3_bind_text(statement, 3, value, -1, NULL) == SQLITE_OK)
@@ -1989,7 +2081,7 @@ bool tf_ssb_db_remove_property(tf_ssb_t* ssb, const char* id, const char* key)
bool result = false; bool result = false;
sqlite3* db = tf_ssb_acquire_db_writer(ssb); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "DELETE FROM properties WHERE id = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "DELETE FROM properties WHERE id = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, key, -1, NULL) == SQLITE_OK) if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, key, -1, NULL) == SQLITE_OK)
{ {
@@ -2006,7 +2098,7 @@ bool tf_ssb_db_remove_value_from_array_property(tf_ssb_t* ssb, const char* id, c
bool result = false; bool result = false;
sqlite3* db = tf_ssb_acquire_db_writer(ssb); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, if (sqlite3_prepare_v2(db,
"UPDATE properties SET value = json_remove(properties.value, entry.fullkey) FROM json_each(properties.value) AS entry WHERE properties.id = ? AND properties.key = ? " "UPDATE properties SET value = json_remove(properties.value, entry.fullkey) FROM json_each(properties.value) AS entry WHERE properties.id = ? AND properties.key = ? "
"AND entry.value = ?", "AND entry.value = ?",
-1, &statement, NULL) == SQLITE_OK) -1, &statement, NULL) == SQLITE_OK)
@@ -2027,7 +2119,7 @@ bool tf_ssb_db_add_value_to_array_property(tf_ssb_t* ssb, const char* id, const
bool result = false; bool result = false;
sqlite3* db = tf_ssb_acquire_db_writer(ssb); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, if (sqlite3_prepare_v2(db,
"INSERT INTO properties (id, key, value) VALUES (?1, ?2, json_array(?3)) ON CONFLICT DO UPDATE SET value = json_insert(properties.value, '$[#]', ?3) WHERE " "INSERT INTO properties (id, key, value) VALUES (?1, ?2, json_array(?3)) ON CONFLICT DO UPDATE SET value = json_insert(properties.value, '$[#]', ?3) WHERE "
"properties.id = ?1 AND properties.key = ?2 AND NOT EXISTS (SELECT 1 FROM json_each(properties.value) AS entry WHERE entry.value = ?3)", "properties.id = ?1 AND properties.key = ?2 AND NOT EXISTS (SELECT 1 FROM json_each(properties.value) AS entry WHERE entry.value = ?3)",
-1, &statement, NULL) == SQLITE_OK) -1, &statement, NULL) == SQLITE_OK)
@@ -2047,7 +2139,7 @@ bool tf_ssb_db_identity_get_active(sqlite3* db, const char* user, const char* pa
{ {
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
bool found = false; bool found = false;
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = ? AND key = 'id:' || ? || ':' || ?", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT value FROM properties WHERE id = ? AND key = 'id:' || ? || ':' || ?", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, package_owner, -1, NULL) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, package_owner, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, package_name, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW) sqlite3_bind_text(statement, 3, package_name, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
@@ -2074,7 +2166,7 @@ static void _tf_ssb_db_resolve_index_work(tf_ssb_t* ssb, void* user_data)
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT json_extract(value, '$.index_map') FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT json_extract(value, '$.index_map') FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_step(statement) == SQLITE_ROW) if (sqlite3_step(statement) == SQLITE_ROW)
{ {
@@ -2105,7 +2197,7 @@ static void _tf_ssb_db_resolve_index_work(tf_ssb_t* ssb, void* user_data)
if (!request->path) if (!request->path)
{ {
if (sqlite3_prepare(db, "SELECT json_extract(value, '$.index') FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT json_extract(value, '$.index') FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_step(statement) == SQLITE_ROW) if (sqlite3_step(statement) == SQLITE_ROW)
{ {
@@ -2122,8 +2214,7 @@ static void _tf_ssb_db_resolve_index_work(tf_ssb_t* ssb, void* user_data)
if (!request->path) if (!request->path)
{ {
/* From default global settings. */ request->path = tf_strdup(tf_util_get_default_global_setting_string("index"));
request->path = tf_strdup("/~core/ssb/");
} }
} }
@@ -2151,7 +2242,7 @@ static void _tf_ssb_db_set_flags(tf_ssb_t* ssb, const char* message_id, int flag
{ {
sqlite3* db = tf_ssb_acquire_db_writer(ssb); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "UPDATE messages SET flags = ? WHERE id = ?", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "UPDATE messages SET flags = ? WHERE id = ?", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_int(statement, 1, flags) == SQLITE_OK && sqlite3_bind_text(statement, 2, message_id, -1, NULL) == SQLITE_OK) if (sqlite3_bind_int(statement, 1, flags) == SQLITE_OK && sqlite3_bind_text(statement, 2, message_id, -1, NULL) == SQLITE_OK)
{ {
@@ -2240,7 +2331,7 @@ bool tf_ssb_db_user_has_permission(tf_ssb_t* ssb, sqlite3* db, const char* id, c
bool has_permission = false; bool has_permission = false;
sqlite3* reader = db ? db : tf_ssb_acquire_db_reader(ssb); sqlite3* reader = db ? db : tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(reader, if (sqlite3_prepare_v2(reader,
"SELECT COUNT(*) FROM properties, json_each(properties.value -> 'permissions' -> ?) AS permission WHERE properties.id = 'core' AND properties.key = 'settings' AND " "SELECT COUNT(*) FROM properties, json_each(properties.value -> 'permissions' -> ?) AS permission WHERE properties.id = 'core' AND properties.key = 'settings' AND "
"permission.value = ?", "permission.value = ?",
-1, &statement, NULL) == SQLITE_OK) -1, &statement, NULL) == SQLITE_OK)
@@ -2263,7 +2354,7 @@ bool tf_ssb_db_get_global_setting_bool(sqlite3* db, const char* name, bool* out_
{ {
bool result = false; bool result = false;
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT json_extract(value, '$.' || ?) FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT json_extract(value, '$.' || ?) FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, name, -1, NULL) == SQLITE_OK) if (sqlite3_bind_text(statement, 1, name, -1, NULL) == SQLITE_OK)
{ {
@@ -2290,7 +2381,7 @@ bool tf_ssb_db_get_global_setting_int64(sqlite3* db, const char* name, int64_t*
{ {
bool result = false; bool result = false;
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT json_extract(value, '$.' || ?) FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT json_extract(value, '$.' || ?) FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, name, -1, NULL) == SQLITE_OK) if (sqlite3_bind_text(statement, 1, name, -1, NULL) == SQLITE_OK)
{ {
@@ -2317,7 +2408,7 @@ bool tf_ssb_db_get_global_setting_string(sqlite3* db, const char* name, char* ou
{ {
bool result = false; bool result = false;
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT json_extract(value, '$.' || ?) FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT json_extract(value, '$.' || ?) FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, name, -1, NULL) == SQLITE_OK) if (sqlite3_bind_text(statement, 1, name, -1, NULL) == SQLITE_OK)
{ {
@@ -2350,7 +2441,7 @@ bool tf_ssb_db_set_global_setting_from_string(sqlite3* db, const char* name, cha
bool result = false; bool result = false;
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, if (sqlite3_prepare_v2(db,
"INSERT INTO properties (id, key, value) VALUES ('core', 'settings', json_object(?1, ?2)) ON CONFLICT DO UPDATE SET value = json_set(value, '$.' || ?1, ?2)", -1, "INSERT INTO properties (id, key, value) VALUES ('core', 'settings', json_object(?1, ?2)) ON CONFLICT DO UPDATE SET value = json_set(value, '$.' || ?1, ?2)", -1,
&statement, NULL) == SQLITE_OK) &statement, NULL) == SQLITE_OK)
{ {
@@ -2389,7 +2480,7 @@ const char* tf_ssb_db_get_profile(sqlite3* db, const char* id)
{ {
const char* result = NULL; const char* result = NULL;
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, if (sqlite3_prepare_v2(db,
"SELECT json(json_group_object(key, value)) FROM (SELECT fields.key, RANK() OVER (PARTITION BY fields.key ORDER BY messages.sequence DESC) AS rank, fields.value FROM " "SELECT json(json_group_object(key, value)) FROM (SELECT fields.key, RANK() OVER (PARTITION BY fields.key ORDER BY messages.sequence DESC) AS rank, fields.value FROM "
"messages, json_each(messages.content) AS fields WHERE messages.author = ? AND messages.content ->> '$.type' = 'about' AND messages.content ->> '$.about' = " "messages, json_each(messages.content) AS fields WHERE messages.author = ? AND messages.content ->> '$.type' = 'about' AND messages.content ->> '$.about' = "
"messages.author AND NOT fields.key IN ('about', 'type')) WHERE rank = 1", "messages.author AND NOT fields.key IN ('about', 'type')) WHERE rank = 1",
@@ -2415,7 +2506,7 @@ const char* tf_ssb_db_get_profile_name(sqlite3* db, const char* id)
{ {
const char* result = NULL; const char* result = NULL;
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, if (sqlite3_prepare_v2(db,
"SELECT name FROM (SELECT messages.author, RANK() OVER (PARTITION BY messages.author ORDER BY messages.sequence DESC) AS author_rank, " "SELECT name FROM (SELECT messages.author, RANK() OVER (PARTITION BY messages.author ORDER BY messages.sequence DESC) AS author_rank, "
"messages.content ->> 'name' AS name FROM messages WHERE messages.author = ? " "messages.content ->> 'name' AS name FROM messages WHERE messages.author = ? "
"AND messages.content ->> '$.type' = 'about' AND content ->> 'about' = messages.author AND name IS NOT NULL) " "AND messages.content ->> '$.type' = 'about' AND content ->> 'about' = messages.author AND name IS NOT NULL) "
@@ -2441,7 +2532,7 @@ const char* tf_ssb_db_get_profile_name(sqlite3* db, const char* id)
static void _tf_ssb_db_invite_cleanup(sqlite3* db) static void _tf_ssb_db_invite_cleanup(sqlite3* db)
{ {
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, "DELETE FROM invites WHERE use_count = 0 OR (expires > 0 AND expires < ?)", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "DELETE FROM invites WHERE use_count = 0 OR (expires > 0 AND expires < ?)", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_int64(statement, 1, (int64_t)time(NULL)) == SQLITE_OK) if (sqlite3_bind_int64(statement, 1, (int64_t)time(NULL)) == SQLITE_OK)
{ {
@@ -2493,7 +2584,7 @@ bool tf_ssb_db_generate_invite(sqlite3* db, const char* id, const char* host, in
bool inserted = false; bool inserted = false;
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, "INSERT INTO invites (invite_public_key, account, use_count, expires) VALUES (?, ?, ?, ?)", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "INSERT INTO invites (invite_public_key, account, use_count, expires) VALUES (?, ?, ?, ?)", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, public, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, id, -1, NULL) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, public, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, id, -1, NULL) == SQLITE_OK &&
sqlite3_bind_int(statement, 3, use_count) == SQLITE_OK && sqlite3_bind_int(statement, 3, use_count) == SQLITE_OK &&
@@ -2514,8 +2605,8 @@ bool tf_ssb_db_use_invite(sqlite3* db, const char* id)
{ {
bool used = false; bool used = false;
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, "UPDATE invites SET use_count = use_count - 1 WHERE invite_public_key = ? AND (expires < 0 OR expires >= ?) AND (use_count > 0 OR use_count = -1)", -1, if (sqlite3_prepare_v2(db, "UPDATE invites SET use_count = use_count - 1 WHERE invite_public_key = ? AND (expires < 0 OR expires >= ?) AND (use_count > 0 OR use_count = -1)",
&statement, NULL) == SQLITE_OK) -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_bind_int64(statement, 2, (int64_t)time(NULL)) == SQLITE_OK) if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_bind_int64(statement, 2, (int64_t)time(NULL)) == SQLITE_OK)
{ {
@@ -2533,7 +2624,7 @@ bool tf_ssb_db_has_invite(sqlite3* db, const char* id)
{ {
bool has = false; bool has = false;
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT COUNT(*) FROM invites WHERE invite_public_key = ? AND (expires < 0 OR expires >= ?) AND (use_count > 0 OR use_count = -1)", -1, &statement, if (sqlite3_prepare_v2(db, "SELECT COUNT(*) FROM invites WHERE invite_public_key = ? AND (expires < 0 OR expires >= ?) AND (use_count > 0 OR use_count = -1)", -1, &statement,
NULL) == SQLITE_OK) NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_bind_int64(statement, 2, (int64_t)time(NULL)) == SQLITE_OK) if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_bind_int64(statement, 2, (int64_t)time(NULL)) == SQLITE_OK)
@@ -2574,7 +2665,7 @@ tf_ssb_identity_info_t* tf_ssb_db_get_identity_info(tf_ssb_t* ssb, const char* u
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
int result = sqlite3_prepare(db, int result = sqlite3_prepare_v2(db,
"SELECT author, name FROM ( " "SELECT author, name FROM ( "
" SELECT " " SELECT "
" messages.author, " " messages.author, "

View File

@@ -249,6 +249,14 @@ void tf_ssb_db_identity_visit(tf_ssb_t* ssb, const char* user, void (*callback)(
*/ */
void tf_ssb_db_identity_visit_all(tf_ssb_t* ssb, void (*callback)(const char* identity, void* user_data), void* user_data); void tf_ssb_db_identity_visit_all(tf_ssb_t* ssb, void (*callback)(const char* identity, void* user_data), void* user_data);
/**
** Get the user owning an identity.
** @param ssb The SSB instance.
** @param public_key the identity.
** @return The username of the owner of the identity or NULL.
*/
const char* tf_ssb_db_get_user_for_identity(tf_ssb_t* ssb, const char* public_key);
/** /**
** Get the private key for an identity in the database. ** Get the private key for an identity in the database.
** @param ssb The SSB instance. ** @param ssb The SSB instance.
@@ -332,6 +340,10 @@ typedef struct _tf_ssb_db_stored_connection_t
int port; int port;
/** The identity. */ /** The identity. */
char pubkey[k_id_base64_len]; char pubkey[k_id_base64_len];
/** Time of last attempted connection. */
int64_t last_attempt;
/** Time of last successful connection. */
int64_t last_success;
} tf_ssb_db_stored_connection_t; } tf_ssb_db_stored_connection_t;
/** /**
@@ -591,4 +603,11 @@ typedef struct _tf_ssb_identity_info_t
*/ */
tf_ssb_identity_info_t* tf_ssb_db_get_identity_info(tf_ssb_t* ssb, const char* user, const char* package_owner, const char* package_name); tf_ssb_identity_info_t* tf_ssb_db_get_identity_info(tf_ssb_t* ssb, const char* user, const char* package_owner, const char* package_name);
/**
** Add or update a blob wants cache entry.
** @param db The database.
** @param id The wanted blob ID.
*/
void tf_ssb_db_add_blob_wants(sqlite3* db, const char* id);
/** @} */ /** @} */

View File

@@ -94,7 +94,7 @@ void tf_ssb_export(tf_ssb_t* ssb, const char* key)
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_busy_timeout(db, 10000); sqlite3_busy_timeout(db, 10000);
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = ?1 AND key = 'path:' || ?2", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT value FROM properties WHERE id = ?1 AND key = 'path:' || ?2", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, path, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW) if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, path, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
{ {

View File

@@ -21,7 +21,7 @@ static void _tf_ssb_import_add_app(tf_ssb_t* ssb, const char* user, const char*
JSContext* context = tf_ssb_get_context(ssb); JSContext* context = tf_ssb_get_context(ssb);
JSValue apps = JS_UNDEFINED; JSValue apps = JS_UNDEFINED;
sqlite3* db = tf_ssb_acquire_db_writer(ssb); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = ?1 AND key = 'apps'", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT value FROM properties WHERE id = ?1 AND key = 'apps'", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW) if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
{ {
@@ -68,7 +68,7 @@ static void _tf_ssb_import_add_app(tf_ssb_t* ssb, const char* user, const char*
JSValue json = JS_JSONStringify(context, out_apps, JS_NULL, JS_NULL); JSValue json = JS_JSONStringify(context, out_apps, JS_NULL, JS_NULL);
const char* text = JS_ToCString(context, json); const char* text = JS_ToCString(context, json);
if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES (?1, 'apps', ?2)", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES (?1, 'apps', ?2)", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, text, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_OK) if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, text, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_OK)
{ {
@@ -165,8 +165,9 @@ static bool _tf_ssb_register_app(tf_ssb_t* ssb, const char* user, const char* ap
bool result = false; bool result = false;
sqlite3_stmt* statement; sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_writer(ssb); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
if (sqlite3_prepare(db, "INSERT INTO properties (id, key, value) VALUES (?1, 'path:' || ?2, ?3) ON CONFLICT DO UPDATE SET value = excluded.value WHERE value != excluded.value", if (sqlite3_prepare_v2(db,
-1, &statement, NULL) == SQLITE_OK) "INSERT INTO properties (id, key, value) VALUES (?1, 'path:' || ?2, ?3) ON CONFLICT DO UPDATE SET value = excluded.value WHERE value != excluded.value", -1, &statement,
NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, app, -1, NULL) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, app, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, id, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE) sqlite3_bind_text(statement, 3, id, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE)

View File

@@ -271,7 +271,7 @@ static void _tf_ssb_swap_with_server_identity_work(tf_ssb_t* ssb, void* user_dat
if (sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &error) == SQLITE_OK) if (sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &error) == SQLITE_OK)
{ {
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "UPDATE identities SET user = ? WHERE user = ? AND '@' || public_key = ?", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "UPDATE identities SET user = ? WHERE user = ? AND '@' || public_key = ?", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, work->user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, ":admin", -1, NULL) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, work->user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, ":admin", -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, work->server_id, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE && sqlite3_changes(db) == 1 && sqlite3_bind_text(statement, 3, work->server_id, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE && sqlite3_changes(db) == 1 &&
@@ -988,6 +988,8 @@ static void _tf_ssb_stored_connections_after_work(tf_ssb_t* ssb, int status, voi
JS_SetPropertyStr(context, connection, "address", JS_NewString(context, work->connections[i].address)); JS_SetPropertyStr(context, connection, "address", JS_NewString(context, work->connections[i].address));
JS_SetPropertyStr(context, connection, "port", JS_NewInt32(context, work->connections[i].port)); JS_SetPropertyStr(context, connection, "port", JS_NewInt32(context, work->connections[i].port));
JS_SetPropertyStr(context, connection, "pubkey", JS_NewString(context, work->connections[i].pubkey)); JS_SetPropertyStr(context, connection, "pubkey", JS_NewString(context, work->connections[i].pubkey));
JS_SetPropertyStr(context, connection, "last_attempt", JS_NewInt64(context, work->connections[i].last_attempt));
JS_SetPropertyStr(context, connection, "last_success", JS_NewInt64(context, work->connections[i].last_success));
JS_SetPropertyUint32(context, result, i, connection); JS_SetPropertyUint32(context, result, i, connection);
} }
tf_free(work->connections); tf_free(work->connections);
@@ -1071,7 +1073,7 @@ static void _tf_ssb_sqlAsync_work(tf_ssb_t* ssb, void* user_data)
uv_mutex_unlock(&sql_work->lock); uv_mutex_unlock(&sql_work->lock);
uv_async_send(&sql_work->async); uv_async_send(&sql_work->async);
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
sql_work->result = sqlite3_prepare(db, sql_work->query, -1, &statement, NULL); sql_work->result = sqlite3_prepare_v2(db, sql_work->query, -1, &statement, NULL);
if (sql_work->result == SQLITE_OK) if (sql_work->result == SQLITE_OK)
{ {
const uint8_t* p = sql_work->binds; const uint8_t* p = sql_work->binds;
@@ -1788,7 +1790,7 @@ static JSValue _tf_ssb_createTunnel(JSContext* context, JSValueConst this_val, i
const char* portal_id = JS_ToCString(context, argv[0]); const char* portal_id = JS_ToCString(context, argv[0]);
const char* target_id = JS_ToCString(context, argv[1]); const char* target_id = JS_ToCString(context, argv[1]);
bool result = tf_ssb_tunnel_create(ssb, portal_id, target_id, 0); bool result = portal_id && target_id && tf_ssb_tunnel_create(ssb, portal_id, target_id, 0);
JS_FreeCString(context, target_id); JS_FreeCString(context, target_id);
JS_FreeCString(context, portal_id); JS_FreeCString(context, portal_id);
@@ -1805,7 +1807,7 @@ static bool _tf_ssb_get_private_key_curve25519_internal(sqlite3* db, const char*
bool success = false; bool success = false;
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "SELECT private_key FROM identities WHERE user = ? AND public_key = ?", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT private_key FROM identities WHERE user = ? AND public_key = ?", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, *identity == '@' ? identity + 1 : identity, -1, NULL) == SQLITE_OK) if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, *identity == '@' ? identity + 1 : identity, -1, NULL) == SQLITE_OK)
{ {

View File

@@ -232,7 +232,7 @@ static void _tf_ssb_request_blob_wants_work(tf_ssb_connection_t* connection, voi
db = tf_ssb_acquire_db_reader(ssb); db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT id FROM blob_wants_cache WHERE id > ? AND timestamp > ? ORDER BY id LIMIT ?", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare_v2(db, "SELECT id FROM blob_wants_cache WHERE id > ? AND timestamp > ? ORDER BY id LIMIT ?", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, blob_wants->last_id, -1, NULL) == SQLITE_OK && sqlite3_bind_int64(statement, 2, timestamp) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, blob_wants->last_id, -1, NULL) == SQLITE_OK && sqlite3_bind_int64(statement, 2, timestamp) == SQLITE_OK &&
sqlite3_bind_int(statement, 3, tf_countof(work->out_id)) == SQLITE_OK) sqlite3_bind_int(statement, 3, tf_countof(work->out_id)) == SQLITE_OK)
@@ -884,7 +884,7 @@ static void _tf_ssb_connection_send_history_stream_work(tf_ssb_connection_t* con
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement; sqlite3_stmt* statement;
const int k_max = 32; const int k_max = 32;
if (sqlite3_prepare(db, if (sqlite3_prepare_v2(db,
"SELECT previous, author, id, sequence, timestamp, hash, json(content), signature, flags FROM messages WHERE author = ?1 AND sequence > ?2 AND " "SELECT previous, author, id, sequence, timestamp, hash, json(content), signature, flags FROM messages WHERE author = ?1 AND sequence > ?2 AND "
"sequence < ?3 ORDER BY sequence", "sequence < ?3 ORDER BY sequence",
-1, &statement, NULL) == SQLITE_OK) -1, &statement, NULL) == SQLITE_OK)
@@ -1469,9 +1469,9 @@ static void _tf_ssb_rpc_checkpoint(tf_ssb_t* ssb)
sqlite3* db = tf_ssb_acquire_db_writer(ssb); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
int log = 0; int log = 0;
int checkpointed = 0; int checkpointed = 0;
if (sqlite3_wal_checkpoint_v2(db, NULL, SQLITE_CHECKPOINT_TRUNCATE, &log, &checkpointed) == SQLITE_OK) if (sqlite3_wal_checkpoint_v2(db, NULL, SQLITE_CHECKPOINT_PASSIVE, &log, &checkpointed) == SQLITE_OK)
{ {
tf_printf("Checkpointed %d frames in %d ms. Log is now %d frames.\n", (int)((uv_hrtime() - checkpoint_start_ms) / 1000000LL), checkpointed, log); tf_printf("Checkpointed %d frames in %d ms. Log is now %d frames.\n", checkpointed, (int)((uv_hrtime() - checkpoint_start_ms) / 1000000LL), log);
} }
else else
{ {
@@ -1505,7 +1505,18 @@ static void _tf_ssb_rpc_delete_blobs_work(tf_ssb_t* ssb, void* user_data)
int64_t timestamp = now - age * 1000ULL; int64_t timestamp = now - age * 1000ULL;
const int k_limit = 128; const int k_limit = 128;
int deleted = 0; int deleted = 0;
if (sqlite3_prepare(db, if (sqlite3_prepare_v2(db, "DELETE FROM blob_wants_cache WHERE source IS NULL and timestamp < ?1", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_int64(statement, 1, timestamp) == SQLITE_OK)
{
if (sqlite3_step(statement) != SQLITE_DONE)
{
tf_printf("Deleting stale blob wants cache entries: %s.\n", sqlite3_errmsg(db));
}
}
sqlite3_finalize(statement);
}
if (sqlite3_prepare_v2(db,
"DELETE FROM blobs WHERE blobs.id IN (" "DELETE FROM blobs WHERE blobs.id IN ("
" SELECT blobs.id FROM blobs " " SELECT blobs.id FROM blobs "
" JOIN messages_refs ON blobs.id = messages_refs.ref " " JOIN messages_refs ON blobs.id = messages_refs.ref "
@@ -1541,7 +1552,7 @@ static void _tf_ssb_rpc_delete_blobs_work(tf_ssb_t* ssb, void* user_data)
static void _tf_ssb_rpc_delete_blobs_after_work(tf_ssb_t* ssb, int status, void* user_data) static void _tf_ssb_rpc_delete_blobs_after_work(tf_ssb_t* ssb, int status, void* user_data)
{ {
delete_t* delete = user_data; delete_t* delete = user_data;
_tf_ssb_rpc_start_delete_blobs(ssb, delete->deleted ? (int)delete->duration_ms : (15 * 60 * 1000)); _tf_ssb_rpc_start_delete_blobs(ssb, delete->deleted ? (int)delete->duration_ms : (5 * 60 * 1000));
tf_free(delete); tf_free(delete);
} }
@@ -1593,7 +1604,7 @@ static void _tf_ssb_rpc_delete_feeds_work(tf_ssb_t* ssb, void* user_data)
db = tf_ssb_acquire_db_writer(ssb); db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, if (sqlite3_prepare_v2(db,
"DELETE FROM messages WHERE id IN (" "DELETE FROM messages WHERE id IN ("
" SELECT id FROM messages WHERE author NOT IN (SELECT value FROM json_each(?)) ORDER BY rowid DESC LIMIT 1024" " SELECT id FROM messages WHERE author NOT IN (SELECT value FROM json_each(?)) ORDER BY rowid DESC LIMIT 1024"
")", ")",

View File

@@ -1478,4 +1478,162 @@ void tf_ssb_test_triggers(const tf_test_options_t* options)
uv_loop_close(&loop); uv_loop_close(&loop);
} }
static void _subprocess_check_call(const char* command, int expected)
{
int result = system(command);
if (!WIFEXITED(result))
{
tf_printf("Command did not report exit: %s.\n", command);
abort();
}
if (WEXITSTATUS(result) != expected)
{
tf_printf("Command returned %d (expected %d): %s.\n", WEXITSTATUS(result), expected, command);
abort();
}
}
static char* _subprocess_check_output(const char* command)
{
FILE* proc = popen(command, "r");
if (!proc)
{
tf_printf("Command failed (%s): %s.\n", strerror(errno), command);
abort();
}
char* result = NULL;
size_t size = 0;
if (proc)
{
const int k_block_size = 1024;
result = tf_realloc(result, size + k_block_size);
while (true)
{
size_t bytes = fread(result + size, 1, k_block_size, proc);
if (bytes > 0)
{
size += bytes;
}
else
{
break;
}
}
pclose(proc);
}
result = tf_realloc(result, size + 1);
if (result)
{
result[size] = '\0';
}
return result;
}
static bool _isspace(char c)
{
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}
static char* _trim(char* p)
{
if (!p)
{
return NULL;
}
size_t length = strlen(p);
while (length > 0 && _isspace(p[length - 1]))
{
p[length - 1] = '\0';
length--;
}
return p;
}
void tf_ssb_test_cli(const tf_test_options_t* options)
{
tf_printf("Testing CLI.\n");
unlink("out/test_db0.sqlite");
char command[1024];
snprintf(command, sizeof(command), "%s get_identity -d out/test_db0.sqlite", options->exe_path);
_subprocess_check_call(command, 0);
char* id = _trim(_subprocess_check_output(command));
tf_printf("id = [%s]\n", id);
snprintf(command, sizeof(command), "%s publish -i %s -d out/test_db0.sqlite -c '{\"type\": \"about\", \"about\": \"%s\", \"name\": \"test_user\"}'", options->exe_path, id, id);
_subprocess_check_call(command, 0);
snprintf(command, sizeof(command), "%s private -i %s -r %s -d out/test_db0.sqlite -t '{\"type\": \"post\", \"text\": \"hello world\"}'", options->exe_path, id, id);
_subprocess_check_call(command, 0);
snprintf(command, sizeof(command), "%s verify -i %s -d out/test_db0.sqlite", options->exe_path, id);
_subprocess_check_call(command, 0);
snprintf(command, sizeof(command), "%s store_blob -f GNUmakefile -d out/test_db0.sqlite", options->exe_path);
char* blob = _trim(_subprocess_check_output(command));
snprintf(command, sizeof(command), "%s has_blob -b '%s' -d out/test_db0.sqlite", options->exe_path, blob);
_subprocess_check_call(command, 0);
snprintf(command, sizeof(command), "%s has_blob -b '&nonexistentid.sha256' -d out/test_db0.sqlite", options->exe_path);
_subprocess_check_call(command, EXIT_FAILURE);
snprintf(command, sizeof(command), "%s get_sequence -i %s -d out/test_db0.sqlite", options->exe_path, id);
char* sequence = _trim(_subprocess_check_output(command));
if (!sequence || strcmp(sequence, "2") != 0)
{
tf_printf("sequence = %s (expected 1)\n", sequence);
abort();
}
tf_free(sequence);
snprintf(command, sizeof(command), "%s get_profile -i %s -d out/test_db0.sqlite", options->exe_path, id);
char* profile = _trim(_subprocess_check_output(command));
const char* k_expected_profile = "{\"name\":\"test_user\"}";
if (!profile || strcmp(profile, k_expected_profile) != 0)
{
tf_printf("profile = %s (expected \"%s\")\n", profile, k_expected_profile);
abort();
}
tf_free(profile);
snprintf(command, sizeof(command), "%s get_contacts -i %s -d out/test_db0.sqlite", options->exe_path, id);
char* contacts = _trim(_subprocess_check_output(command));
tf_printf("contacts = %s\n", contacts);
tf_free(contacts);
tf_free(blob);
tf_free(id);
}
void tf_ssb_test_following_perf(const tf_test_options_t* options)
{
uv_loop_t loop = { 0 };
uv_loop_init(&loop);
tf_printf("Testing following_perf.\n");
tf_printf("Using %s.\n", options->db_path);
tf_ssb_t* ssb = tf_ssb_create(&loop, NULL, options->db_path, NULL);
uint64_t start = uv_hrtime();
int count = 0;
for (int i = 0; i < 100; i++)
{
const char** ids = tf_ssb_db_get_all_visible_identities(ssb, 2);
while (ids[count])
{
count++;
}
tf_free(ids);
}
uint64_t end = uv_hrtime();
tf_printf("completed in %.3fs (%d ids)\n", (end - start) / 1e9, count);
tf_ssb_destroy(ssb);
uv_run(&loop, UV_RUN_DEFAULT);
uv_loop_close(&loop);
}
#endif #endif

View File

@@ -89,4 +89,16 @@ void tf_ssb_test_invite(const tf_test_options_t* options);
*/ */
void tf_ssb_test_triggers(const tf_test_options_t* options); void tf_ssb_test_triggers(const tf_test_options_t* options);
/**
** Test command-line interface.
** @param options The test options.
*/
void tf_ssb_test_cli(const tf_test_options_t* options);
/**
** Test following performance.
** @param options The test options.
*/
void tf_ssb_test_following_perf(const tf_test_options_t* options);
/** @} */ /** @} */

View File

@@ -1869,15 +1869,15 @@ void tf_task_destroy(tf_task_t* task)
uv_close((uv_handle_t*)&timeout->_timer, _timeout_closed); uv_close((uv_handle_t*)&timeout->_timer, _timeout_closed);
} }
if (task->_ssb)
{
tf_ssb_destroy(task->_ssb);
}
if (task->_http) if (task->_http)
{ {
tf_httpd_destroy(task->_http); tf_httpd_destroy(task->_http);
task->_http = NULL; task->_http = NULL;
} }
if (task->_ssb)
{
tf_ssb_destroy(task->_ssb);
}
JS_FreeContext(task->_context); JS_FreeContext(task->_context);
JS_FreeRuntime(task->_runtime); JS_FreeRuntime(task->_runtime);

View File

@@ -1079,6 +1079,8 @@ void tf_tests(const tf_test_options_t* options)
_tf_test_run(options, "connect_str", tf_ssb_test_connect_str, false); _tf_test_run(options, "connect_str", tf_ssb_test_connect_str, false);
_tf_test_run(options, "invite", tf_ssb_test_invite, false); _tf_test_run(options, "invite", tf_ssb_test_invite, false);
_tf_test_run(options, "triggers", tf_ssb_test_triggers, false); _tf_test_run(options, "triggers", tf_ssb_test_triggers, false);
_tf_test_run(options, "cli", tf_ssb_test_cli, false);
_tf_test_run(options, "following_perf", tf_ssb_test_following_perf, true);
tf_printf("Tests completed.\n"); tf_printf("Tests completed.\n");
#endif #endif
} }

View File

@@ -15,6 +15,8 @@ typedef struct _tf_test_options_t
const char* exe_path; const char* exe_path;
/** A comma-separated list of tests to run, or NULL. */ /** A comma-separated list of tests to run, or NULL. */
const char* tests; const char* tests;
/** Path to the actual local database for running performance tests against. */
const char* db_path;
} tf_test_options_t; } tf_test_options_t;
/** /**

View File

@@ -368,7 +368,7 @@ static const setting_t k_settings[] = {
.type = "string", .type = "string",
.description = "If connecting by HTTP and HTTPS is configured, Location header prefix (ie, \"http://example.com\")", .description = "If connecting by HTTP and HTTPS is configured, Location header prefix (ie, \"http://example.com\")",
.default_value = { .kind = k_kind_string, .string_value = NULL } }, .default_value = { .kind = k_kind_string, .string_value = NULL } },
{ .name = "index", .type = "string", .description = "Default path.", .default_value = { .kind = k_kind_string, .string_value = "/~core/ssb/" } }, { .name = "index", .type = "string", .description = "Default path.", .default_value = { .kind = k_kind_string, .string_value = "/~core/intro/" } },
{ .name = "index_map", { .name = "index_map",
.type = "textarea", .type = "textarea",
.description = "Mappings from hostname to redirect path, one per line, as in: \"www.tildefriends.net=/~core/index/\"", .description = "Mappings from hostname to redirect path, one per line, as in: \"www.tildefriends.net=/~core/index/\"",
@@ -398,6 +398,8 @@ static const setting_t k_settings[] = {
.description = "Whether connections are accepted from accounts that aren't in the replication range or otherwise already known.", .description = "Whether connections are accepted from accounts that aren't in the replication range or otherwise already known.",
.default_value = { .kind = k_kind_bool, .bool_value = true } }, .default_value = { .kind = k_kind_bool, .bool_value = true } },
{ .name = "autologin", .type = "boolean", .description = "Whether mobile autologin is supported.", .default_value = { .kind = k_kind_bool, .bool_value = TF_IS_MOBILE != 0 } }, { .name = "autologin", .type = "boolean", .description = "Whether mobile autologin is supported.", .default_value = { .kind = k_kind_bool, .bool_value = TF_IS_MOBILE != 0 } },
{ .name = "broadcast", .type = "boolean", .description = "Send network discovery broadcasts.", .default_value = { .kind = k_kind_bool, .bool_value = true } },
{ .name = "discovery", .type = "boolean", .description = "Receive network discovery broadcasts.", .default_value = { .kind = k_kind_bool, .bool_value = true } },
}; };
static const setting_t* _util_get_setting(const char* name, tf_setting_kind_t kind) static const setting_t* _util_get_setting(const char* name, tf_setting_kind_t kind)

View File

@@ -1,2 +1,2 @@
#define VERSION_NUMBER "0.0.30" #define VERSION_NUMBER "0.0.32-wip"
#define VERSION_NAME "This program kills fascists." #define VERSION_NAME "This program kills fascists."

View File

@@ -80,6 +80,15 @@ try:
select(driver, ['tf-auth', 'shadow_root', '#loginButton'], ('click',)) select(driver, ['tf-auth', 'shadow_root', '#loginButton'], ('click',))
select(driver, ['#document', 'frame', '=identity']) select(driver, ['#document', 'frame', '=identity'])
driver.get('http://localhost:8888/')
select(driver, ['#document', 'frame', '//button[text()="Next"]'], ('click',))
select(driver, ['#document', 'frame', '//button[text()="Onward"]'], ('click',))
select(driver, ['#document', 'frame', '//button[text()="Got It"]'], ('click',))
select(driver, ['#document', 'frame', '//button[text()="Okay"]'], ('click',))
select(driver, ['#document', 'frame', '//button[text()="Let\'s Go!"]'], ('click',))
select(driver, ['//button[text()="✅ Allow"]'], ('click',))
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#edit'], ('send_keys', 'We made it to the ssb app.'))
driver.get('http://localhost:8888/~core/admin/') driver.get('http://localhost:8888/~core/admin/')
select(driver, ['#document', 'frame', '#gs_room_name'], ('send_keys', 'test room')) select(driver, ['#document', 'frame', '#gs_room_name'], ('send_keys', 'test room'))
select(driver, ['#document', 'frame', '//*[@id="gs_room_name"]/following-sibling::button'], ('click',)) select(driver, ['#document', 'frame', '//*[@id="gs_room_name"]/following-sibling::button'], ('click',))
@@ -115,7 +124,7 @@ try:
select(driver, ['tf-navigation', 'shadow_root', '#close_error'], ('click',)) select(driver, ['tf-navigation', 'shadow_root', '#close_error'], ('click',))
select(driver, ['tf-navigation', 'shadow_root', '=edit'], ('click',)) select(driver, ['tf-navigation', 'shadow_root', '=edit'], ('click',))
select(driver, ['#editor', '.cm-content'], ('click',)) select(driver, ['#editor', '.cm-content'], ('click',))
select(driver, ['#editor', '.cm-content'], ('send_keys', 'app.setDocument(\n\t"<div id=\'test-div\'>Hello, world!</div>"\n);')) select(driver, ['#editor', '.cm-content'], ('send_keys', 'app.setDocument(\n\t`<div id=\'test-div\' style=\'color: white; font-size: xx-large\'>\n\t\tHello, world!\n\t</div>`\n);'))
select(driver, ['#save'], ('click',)) select(driver, ['#save'], ('click',))
select(driver, ['#document', 'frame', '#test-div']) select(driver, ['#document', 'frame', '#test-div'])