Compare commits
288 Commits
Author | SHA1 | Date |
---|---|---|
nekobit | c2f859d6bd | |
nekobit | 0ed7b49709 | |
nekobit | f2107a803a | |
nekobit | 64f72cc20f | |
nekobit | d0eaca6439 | |
nekobit | a6a730649d | |
nekobit | 0dfa6560c0 | |
nekobit | fcd5abc7a8 | |
nekobit | 4ecb46c745 | |
nekobit | 9c70d7f1b5 | |
nekobit | 9f44fa8623 | |
nekobit | e36f9e05ca | |
nekobit | 8fdb143303 | |
nekobit | 4ad40369e9 | |
nekobit | bf8785674f | |
nekobit | 115acd3231 | |
nekobit | 4733000997 | |
nekobit | 7e77b9566a | |
nekobit | fe114a4995 | |
nekobit | cb0b676cd7 | |
nekobit | c2b64cdf33 | |
nekobit | 4eb1389418 | |
nekobit | bb8dedb37e | |
nekobit | 61b4c20f2e | |
nekobit | 682f6e0813 | |
nekobit | a8ac0d3b4c | |
nekobit | 6e6ca8ad9a | |
nekobit | e12be1a84b | |
nekobit | f1ef5c6fa0 | |
nekobit | 0617cdbd87 | |
nekobit | f5d4a08d3b | |
nekobit | 7390326d2c | |
nekobit | d68d553759 | |
nekobit | 9efd9f9a9f | |
nekobit | bebc9eb58b | |
nekobit | 1b80b589b7 | |
nekobit | d7d52a0a1f | |
nekobit | 62e782c8f7 | |
nekobit | 06867d36f5 | |
nekobit | 8d225b2c86 | |
nekobit | 8d9d537030 | |
nekobit | 67a2f90c8b | |
nekobit | dce5df9821 | |
nekobit | d2b9d500b2 | |
nekobit | 78c68f5a92 | |
nekobit | dceb8881bb | |
nekobit | f916e8d0c7 | |
nekobit | 09ddb21d42 | |
nekobit | 50e76ae712 | |
nekobit | 1b347848e8 | |
nekobit | d033f363c3 | |
nekobit | aae83c2bd9 | |
nekobit | 3b334562bf | |
nekobit | bbdb95187b | |
nekobit | 8fae1ddfe9 | |
nekobit | 549150504f | |
nekobit | d0989bed94 | |
nekobit | 52c011968f | |
nekobit | 9bcf209e5c | |
nekobit | 785394e435 | |
nekobit | 4db0dea58b | |
nekobit | 670a4021b8 | |
nekobit | 6f90b245a4 | |
nekobit | c33d7174f1 | |
nekobit | 2609e9680c | |
nekobit | c9eaaac234 | |
nekobit | 5f32afa0cd | |
nekobit | dfd49db8d3 | |
nekobit | ad8819f482 | |
nekobit | b49346e059 | |
nekobit | cbeba98890 | |
nekobit | 9917ac981e | |
nekobit | 56a4146a42 | |
nekobit | c9ec49af9f | |
nekobit | eac2f53aad | |
nekobit | ce74e7125e | |
nekobit | 2aa34fea2a | |
nekobit | 35982e5caa | |
nekobit | a2848c5df2 | |
nekobit | 93ba8c2737 | |
nekobit | 85c7e21d10 | |
nekobit | 177636a08a | |
nekobit | 242c11bf28 | |
nekobit | 37f23ec3a2 | |
nekobit | 399fe70f08 | |
nekobit | 2609b8d54f | |
nekobit | 9923154112 | |
nekobit | 71b636d77f | |
nekobit | 6f0c959dc7 | |
nekobit | 5904362b1a | |
nekobit | 3ffbfb2f13 | |
nekobit | 0504203df1 | |
nekobit | 15804cdf61 | |
nekobit | 6d46b56d54 | |
nekobit | 4b696779ae | |
nekobit | 6534dd27aa | |
nekobit | ca444fde1d | |
nekobit | a0240a63db | |
nekobit | 72ffa2abed | |
nekobit | 33dbabd49b | |
nekobit | b3c3fe360a | |
nekobit | d789caec0a | |
nekobit | 997cce08f6 | |
nekobit | c18179b52b | |
nekobit | ba24b6a993 | |
nekobit | d80f9a3c62 | |
nekobit | 82ca6009ae | |
nekobit | 03e62a5e51 | |
nekobit | 1a4175351a | |
nekobit | ec3dbcb60b | |
nekobit | 1edf678d90 | |
nekobit | ae453835b4 | |
nekobit | 19b19b900b | |
nekobit | a6d119833c | |
nekobit | 4697a9fce8 | |
nekobit | f598254df5 | |
nekobit | 6c28a9cbfa | |
nekobit | 3c4a5970e8 | |
nekobit | 937301e307 | |
nekobit | 388f4fadf4 | |
nekobit | 6ea891ae8b | |
nekobit | 72f8932155 | |
nekobit | 637e1591d2 | |
nekobit | bbe47e8721 | |
nekobit | e002975c75 | |
nekobit | 50b0404c51 | |
nekobit | 6ab40c0a66 | |
nekobit | 473b3b5920 | |
nekobit | ff3d383464 | |
nekobit | a3bb29d01a | |
nekobit | afba0ef35c | |
nekobit | 70b3fd7611 | |
nekobit | b745b140d9 | |
nekobit | 19f5e5c14d | |
nekobit | 3ac0145efc | |
nekobit | 4f07f2768f | |
nekobit | 7d0de2754f | |
nekobit | 849b63640c | |
nekobit | 025036832a | |
nekobit | d46209f0ee | |
nekobit | e19f1f854e | |
nekobit | a11470cfd7 | |
nekobit | 97c81d08b7 | |
nekobit | 830a9c05b0 | |
nekobit | 9069a17a41 | |
nekobit | 253f55dd36 | |
nekobit | eba2a360e0 | |
nekobit | 588bfc2126 | |
nekobit | a90878c1b8 | |
nekobit | 5b9c7d75f5 | |
nekobit | 2e21b6fea3 | |
nekobit | e038ff9b82 | |
nekobit | 440ee60b8d | |
nekobit | b8d67b3bcb | |
nekobit | 5f6c7b9108 | |
nekobit | bba2ad9cda | |
nekobit | 772db3ce28 | |
nekobit | 004b47ad07 | |
nekobit | 10875ff940 | |
nekobit | 09a667a6cd | |
nekobit | 8ab015670b | |
nekobit | 8dc34e0a53 | |
nekobit | f4ced103be | |
nekobit | 12020d7e6c | |
nekobit | 5e9701dfa3 | |
nekobit | 1083ed03ff | |
nekobit | 661272026c | |
nekobit | 65300b6ce8 | |
nekobit | 7e1b555925 | |
nekobit | b20d906292 | |
nekobit | 541556613d | |
nekobit | 04e48fc2e7 | |
nekobit | 170d651650 | |
nekobit | e3b0eebe7d | |
nekobit | 15849c03d5 | |
nekobit | 9b66c172d8 | |
nekobit | 95c750b07a | |
nekobit | afe85ce1ae | |
nekobit | f4fcb0dfa7 | |
nekobit | 8d7e376fb4 | |
nekobit | f110b7480a | |
nekobit | da5e8039db | |
nekobit | f078d16523 | |
nekobit | 6a8802adae | |
nekobit | 708ac7b023 | |
nekobit | 68528563ca | |
nekobit | 97bd6b561d | |
nekobit | 54363270ff | |
nekobit | fc6981e5c6 | |
nekobit | 05c0407ced | |
nekobit | 510e97aeeb | |
nekobit | 969f12e347 | |
nekobit | db83718dde | |
nekobit | 80ce3e38bc | |
nekobit | 95bca87556 | |
nekobit | 434ec2e8bd | |
nekobit | c0ccb74569 | |
nekobit | 3f28928a1b | |
nekobit | 7f654b7f5c | |
nekobit | 6d1ef5f938 | |
nekobit | c020158001 | |
nekobit | dab66cf833 | |
nekobit | dbc737226c | |
nekobit | 56af83d157 | |
nekobit | 1cec6bb4b5 | |
nekobit | c3e8af299f | |
nekobit | 0a5e3cd7ee | |
nekobit | 468d9cb8aa | |
nekobit | ba8a6c2027 | |
nekobit | 9eb8d79dca | |
nekobit | 40334b633e | |
nekobit | ff2aa768c9 | |
nekobit | 72b8fefc60 | |
nekobit | 38f9ecb55d | |
nekobit | f1abdd3b50 | |
nekobit | 91425632e2 | |
nekobit | e31e09029a | |
nekobit | f0befa3d3c | |
nekobit | 03f0b0eeba | |
nekobit | e0a6e8a751 | |
nekobit | 7099f504f1 | |
nekobit | 8432e66cb4 | |
nekobit | c88c30996c | |
nekobit | c2602801c4 | |
nekobit | d4deadf064 | |
nekobit | 5b4db3fb74 | |
nekobit | 06b95be3db | |
nekobit | a129f9178f | |
nekobit | ffa4d9cabb | |
nekobit | 12bcdc19ff | |
nekobit | dce2c390b5 | |
nekobit | a74c7d626b | |
nekobit | 9db1263ad5 | |
nekobit | 7fed076c04 | |
nekobit | 1616c5f6f0 | |
nekobit | 0cc4efb2a3 | |
nekobit | 4e2a9ae33b | |
nekobit | 7a64a6dc7f | |
nekobit | 4708687be2 | |
nekobit | 38230e38ee | |
nekobit | 9bc8783bff | |
nekobit | c55afbf2f6 | |
nekobit | 8d153983d6 | |
nekobit | d3bf48a1ed | |
nekobit | 8ee07faf50 | |
nekobit | b22d2acf1d | |
nekobit | 698bba32c6 | |
nekobit | eb6c8285f7 | |
nekobit | 3518f86b2d | |
nekobit | 72dd972c42 | |
nekobit | 8bd187bece | |
nekobit | d4d81258ba | |
nekobit | 04ae07d796 | |
nekobit | ef3f712883 | |
nekobit | 7b852dbbcb | |
nekobit | d3554ba3f2 | |
nekobit | 2240cee57a | |
nekobit | 5a6929e20b | |
nekobit | eaf3350511 | |
nekobit | 4a9500d0c0 | |
nekobit | 3dba7538f7 | |
nekobit | 4c0b6c7859 | |
nekobit | c5d8b9df68 | |
nekobit | 8382573d30 | |
nekobit | ada4630442 | |
nekobit | c070b6a031 | |
nekobit | 0a34c78ff8 | |
nekobit | 4e42784215 | |
nekobit | bbaaa166a8 | |
nekobit | 67094237a6 | |
nekobit | cebd17dad9 | |
nekobit | f34889cde1 | |
nekobit | 537193779f | |
nekobit | c8e3f5d70f | |
nekobit | d4c7564e03 | |
nekobit | 382bbe4a13 | |
nekobit | f1a407c79f | |
nekobit | e01ea11625 | |
nekobit | 8d5f9eb269 | |
nekobit | 0e71de0ba6 | |
nekobit | 3976497ae2 | |
nekobit | cdbbfed221 | |
nekobit | 0a580d0a32 | |
nekobit | dd3818abbc | |
nekobit | d3e5c4e706 | |
nekobit | edd88c095d | |
nekobit | f66c2c1e4d | |
nekobit | 57dbcc8ad8 |
|
@ -1,10 +1,15 @@
|
|||
template
|
||||
ctemplate
|
||||
filec
|
||||
emojitoc
|
||||
**/*.cgi
|
||||
**/*.o
|
||||
**/*.chtml
|
||||
static/*.ctmpl
|
||||
static/*.c
|
||||
mastodont-c
|
||||
config.h
|
||||
treebird
|
||||
test/tests
|
||||
scripts/*.o
|
||||
scripts/*.o
|
||||
templates/*.ctt
|
||||
test/unit/*.bin
|
14
CREDITS
|
@ -7,19 +7,18 @@
|
|||
* Nekobit <@neko@rdrama.cc>
|
||||
Created the Treebird frontend, created mastodont-c (A pleroma C library)
|
||||
Originally, this was called RatFE, but the name changed.
|
||||
[ Producer ]
|
||||
|
||||
* Grumbulon <@grumbulon@freecumextremist.com>
|
||||
Creator of the Logo and owns treebird.dev, made dark theme
|
||||
[ Camera support ]
|
||||
|
||||
* Pacific Coast Highway <@pch_xyz@seediqbale.xyz>
|
||||
Provided Chinese (Traditional) Translations
|
||||
|
||||
* SamTherapy <@sam@froth.zone>
|
||||
Documentation and cleanup, fixed bugs
|
||||
[ Support ]
|
||||
|
||||
* Khan <@Khan@sleepy.cafe>
|
||||
* Khan <@khan@sleepy.cafe>
|
||||
Helped with the old Treebird Husky fork (history lesson required)
|
||||
[ Executive producer ]
|
||||
|
||||
OTHER
|
||||
|
||||
|
@ -27,5 +26,6 @@
|
|||
* ICONSVG Project
|
||||
* cJSON developers
|
||||
* Pleroma developers
|
||||
* Alex Gleason - For API assistance
|
||||
* You! :)
|
||||
* Alex Gleason - For occasional API assistance
|
||||
* You! :)
|
||||
* Me
|
182
Makefile
|
@ -2,145 +2,60 @@ CC ?= cc
|
|||
GIT ?= git
|
||||
MASTODONT_DIR = mastodont-c/
|
||||
MASTODONT = $(MASTODONT_DIR)libmastodont.a
|
||||
CFLAGS += -Wall -I $(MASTODONT_DIR)include/ -Wno-unused-variable -Wno-ignored-qualifiers -I/usr/include/ $(shell pkg-config --cflags libcurl libcjson libpcre)
|
||||
LDFLAGS = -lm -L$(MASTODONT_DIR) -lmastodont $(shell pkg-config --libs libcjson libcurl libpcre) -lfcgi
|
||||
CFLAGS += -Wall -I $(MASTODONT_DIR)include/ -Wno-unused-variable -Wno-ignored-qualifiers -I/usr/include/ -I $(MASTODONT_DIR)/libs $(shell pkg-config --cflags libcurl) `perl -MExtUtils::Embed -e ccopts` -DDEBUGGING_MSTATS
|
||||
LDFLAGS += -L$(MASTODONT_DIR) -lmastodont $(shell pkg-config --libs libcurl) -lfcgi -lpthread `perl -MExtUtils::Embed -e ldopts` -DDEBUGGING_MSTATS
|
||||
# libpcre2-8 (?)
|
||||
SRC = $(wildcard src/*.c)
|
||||
OBJ = $(patsubst %.c,%.o,$(SRC))
|
||||
HEADERS = $(wildcard src/*.h)
|
||||
PAGES_DIR = static
|
||||
PAGES = $(wildcard $(PAGES_DIR)/*.html)
|
||||
PAGES_CMP = $(patsubst %.html,%.chtml,$(PAGES))
|
||||
HEADERS = $(wildcard src/*.h) config.h
|
||||
TMPL_DIR = templates
|
||||
TMPLS = $(wildcard $(TMPL_DIR)/*.tt)
|
||||
TMPLS_C = $(patsubst %.tt,%.ctt,$(TMPLS))
|
||||
TEST_DIR = test/unit
|
||||
TESTS = $(wildcard $(TEST_DIR)/t*.c)
|
||||
UNIT_TESTS = $(patsubst %.c,%.bin,$(TESTS))
|
||||
DIST = dist/
|
||||
PREFIX ?= /usr/local
|
||||
TARGET = treebird
|
||||
# For tests
|
||||
OBJ_NO_MAIN = $(filter-out src/main.o,$(OBJ))
|
||||
|
||||
MASTODONT_URL = https://fossil.nekobit.net/mastodont-c
|
||||
|
||||
all: $(MASTODONT_DIR) dep_build $(TARGET)
|
||||
apache: all apache_start
|
||||
# Not parallel friendly
|
||||
#all: $(MASTODONT_DIR) dep_build $(TARGET)
|
||||
|
||||
$(TARGET): filec $(PAGES_CMP) $(OBJ) $(HEADERS)
|
||||
$(CC) -o $(TARGET) $(OBJ) $(LDFLAGS)
|
||||
ifneq ($(strip $(SINGLE_THREADED)),)
|
||||
CFLAGS += -DSINGLE_THREADED
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(SINGLE_THREADED)),)
|
||||
CFLAGS += -DDEBUG
|
||||
endif
|
||||
|
||||
all:
|
||||
$(MAKE) dep_build
|
||||
$(MAKE) filec
|
||||
$(MAKE) make_tmpls
|
||||
$(MAKE) $(TARGET)
|
||||
|
||||
install_deps:
|
||||
cpan Template::Toolkit
|
||||
|
||||
$(TARGET): $(HEADERS) $(OBJ)
|
||||
$(CC) -o $(TARGET) $(OBJ) $(PAGES_C_OBJ) $(LDFLAGS)
|
||||
|
||||
filec: src/file-to-c/main.o
|
||||
$(CC) -o filec $<
|
||||
$(CC) $(LDFLAGS) -o filec $<
|
||||
|
||||
emojitoc: scripts/emoji-to.o
|
||||
$(CC) -o emojitoc $< $(LDFLAGS)
|
||||
./emojitoc meta/emoji.json > src/emoji_codes.h
|
||||
|
||||
# PAGES
|
||||
$(PAGES_DIR)/index.chtml: $(PAGES_DIR)/index.html
|
||||
./filec $< data_index_html > $@
|
||||
$(PAGES_DIR)/status.chtml: $(PAGES_DIR)/status.html
|
||||
./filec $< data_status_html > $@
|
||||
$(PAGES_DIR)/account.chtml: $(PAGES_DIR)/account.html
|
||||
./filec $< data_account_html > $@
|
||||
$(PAGES_DIR)/login.chtml: $(PAGES_DIR)/login.html
|
||||
./filec $< data_login_html > $@
|
||||
$(PAGES_DIR)/post.chtml: $(PAGES_DIR)/post.html
|
||||
./filec $< data_post_html > $@
|
||||
$(PAGES_DIR)/list.chtml: $(PAGES_DIR)/list.html
|
||||
./filec $< data_list_html > $@
|
||||
$(PAGES_DIR)/lists.chtml: $(PAGES_DIR)/lists.html
|
||||
./filec $< data_lists_html > $@
|
||||
$(PAGES_DIR)/error.chtml: $(PAGES_DIR)/error.html
|
||||
./filec $< data_error_html > $@
|
||||
$(PAGES_DIR)/attachments.chtml: $(PAGES_DIR)/attachments.html
|
||||
./filec $< data_attachments_html > $@
|
||||
$(PAGES_DIR)/attachment_image.chtml: $(PAGES_DIR)/attachment_image.html
|
||||
./filec $< data_attachment_image_html > $@
|
||||
$(PAGES_DIR)/attachment_video.chtml: $(PAGES_DIR)/attachment_video.html
|
||||
./filec $< data_attachment_video_html > $@
|
||||
$(PAGES_DIR)/attachment_link.chtml: $(PAGES_DIR)/attachment_link.html
|
||||
./filec $< data_attachment_link_html > $@
|
||||
$(PAGES_DIR)/attachment_audio.chtml: $(PAGES_DIR)/attachment_audio.html
|
||||
./filec $< data_attachment_audio_html > $@
|
||||
$(PAGES_DIR)/attachment_gifv.chtml: $(PAGES_DIR)/attachment_gifv.html
|
||||
./filec $< data_attachment_gifv_html > $@
|
||||
$(PAGES_DIR)/emoji_reactions.chtml: $(PAGES_DIR)/emoji_reactions.html
|
||||
./filec $< data_emoji_reactions_html > $@
|
||||
$(PAGES_DIR)/emoji_reaction.chtml: $(PAGES_DIR)/emoji_reaction.html
|
||||
./filec $< data_emoji_reaction_html > $@
|
||||
$(PAGES_DIR)/test.chtml: $(PAGES_DIR)/test.html
|
||||
./filec $< data_test_html > $@
|
||||
$(PAGES_DIR)/notifications_page.chtml: $(PAGES_DIR)/notifications_page.html
|
||||
./filec $< data_notifications_page_html > $@
|
||||
$(PAGES_DIR)/notifications.chtml: $(PAGES_DIR)/notifications.html
|
||||
./filec $< data_notifications_html > $@
|
||||
$(PAGES_DIR)/notification.chtml: $(PAGES_DIR)/notification.html
|
||||
./filec $< data_notification_html > $@
|
||||
$(PAGES_DIR)/notification_compact.chtml: $(PAGES_DIR)/notification_compact.html
|
||||
./filec $< data_notification_compact_html > $@
|
||||
$(PAGES_DIR)/error_404.chtml: $(PAGES_DIR)/error_404.html
|
||||
./filec $< data_error_404_html > $@
|
||||
$(PAGES_DIR)/navigation.chtml: $(PAGES_DIR)/navigation.html
|
||||
./filec $< data_navigation_html > $@
|
||||
$(PAGES_DIR)/config_sidebar.chtml: $(PAGES_DIR)/config_sidebar.html
|
||||
./filec $< data_config_sidebar_html > $@
|
||||
$(PAGES_DIR)/like_svg.chtml: $(PAGES_DIR)/like_svg.html
|
||||
./filec $< data_like_svg_html > $@
|
||||
$(PAGES_DIR)/repeat_svg.chtml: $(PAGES_DIR)/repeat_svg.html
|
||||
./filec $< data_repeat_svg_html > $@
|
||||
$(PAGES_DIR)/follow_svg.chtml: $(PAGES_DIR)/follow_svg.html
|
||||
./filec $< data_follow_svg_html > $@
|
||||
$(PAGES_DIR)/notification_action.chtml: $(PAGES_DIR)/notification_action.html
|
||||
./filec $< data_notification_action_html > $@
|
||||
$(PAGES_DIR)/config_general.chtml: $(PAGES_DIR)/config_general.html
|
||||
./filec $< data_config_general_html > $@
|
||||
$(PAGES_DIR)/config_appearance.chtml: $(PAGES_DIR)/config_appearance.html
|
||||
./filec $< data_config_appearance_html > $@
|
||||
$(PAGES_DIR)/in_reply_to.chtml: $(PAGES_DIR)/in_reply_to.html
|
||||
./filec $< data_in_reply_to_html > $@
|
||||
$(PAGES_DIR)/account_info.chtml: $(PAGES_DIR)/account_info.html
|
||||
./filec $< data_account_info_html > $@
|
||||
$(PAGES_DIR)/search.chtml: $(PAGES_DIR)/search.html
|
||||
./filec $< data_search_html > $@
|
||||
$(PAGES_DIR)/scrobble.chtml: $(PAGES_DIR)/scrobble.html
|
||||
./filec $< data_scrobble_html > $@
|
||||
$(PAGES_DIR)/directs_page.chtml: $(PAGES_DIR)/directs_page.html
|
||||
./filec $< data_directs_page_html > $@
|
||||
$(PAGES_DIR)/status_interactions.chtml: $(PAGES_DIR)/status_interactions.html
|
||||
./filec $< data_status_interactions_html > $@
|
||||
$(PAGES_DIR)/status_interactions_label.chtml: $(PAGES_DIR)/status_interactions_label.html
|
||||
./filec $< data_status_interactions_label_html > $@
|
||||
$(PAGES_DIR)/status_interaction_profile.chtml: $(PAGES_DIR)/status_interaction_profile.html
|
||||
./filec $< data_status_interaction_profile_html > $@
|
||||
$(PAGES_DIR)/account_follow_btn.chtml: $(PAGES_DIR)/account_follow_btn.html
|
||||
./filec $< data_account_follow_btn_html > $@
|
||||
$(PAGES_DIR)/bookmarks_page.chtml: $(PAGES_DIR)/bookmarks_page.html
|
||||
./filec $< data_bookmarks_page_html > $@
|
||||
$(PAGES_DIR)/favourites_page.chtml: $(PAGES_DIR)/favourites_page.html
|
||||
./filec $< data_favourites_page_html > $@
|
||||
$(PAGES_DIR)/account_stub.chtml: $(PAGES_DIR)/account_stub.html
|
||||
./filec $< data_account_stub_html > $@
|
||||
$(PAGES_DIR)/hashtag.chtml: $(PAGES_DIR)/hashtag.html
|
||||
./filec $< data_hashtag_html > $@
|
||||
$(PAGES_DIR)/hashtag_page.chtml: $(PAGES_DIR)/hashtag_page.html
|
||||
./filec $< data_hashtag_page_html > $@
|
||||
$(PAGES_DIR)/bar_graph.chtml: $(PAGES_DIR)/bar_graph.html
|
||||
./filec $< data_bar_graph_html > $@
|
||||
$(PAGES_DIR)/bar.chtml: $(PAGES_DIR)/bar.html
|
||||
./filec $< data_bar_html > $@
|
||||
$(PAGES_DIR)/emoji_picker.chtml: $(PAGES_DIR)/emoji_picker.html
|
||||
./filec $< data_emoji_picker_html > $@
|
||||
$(PAGES_DIR)/emoji.chtml: $(PAGES_DIR)/emoji.html
|
||||
./filec $< data_emoji_html > $@
|
||||
$(PAGES_DIR)/likeboost.chtml: $(PAGES_DIR)/likeboost.html
|
||||
./filec $< data_likeboost_html > $@
|
||||
$(PAGES_DIR)/reactions_btn.chtml: $(PAGES_DIR)/reactions_btn.html
|
||||
./filec $< data_reactions_btn_html > $@
|
||||
$(PAGES_DIR)/interaction_buttons.chtml: $(PAGES_DIR)/interaction_buttons.html
|
||||
./filec $< data_interaction_buttons_html > $@
|
||||
$(PAGES_DIR)/license.chtml: $(PAGES_DIR)/license.html
|
||||
./filec $< data_license_html > $@
|
||||
# Login panel
|
||||
$(PAGES_DIR)/quick_login.chtml: $(PAGES_DIR)/quick_login.html
|
||||
./filec $< data_quick_login_html > $@
|
||||
$(PAGES_DIR)/account_sidebar.chtml: $(PAGES_DIR)/account_sidebar.html
|
||||
./filec $< data_account_sidebar_html > $@
|
||||
$(PAGES_DIR)/about.chtml: $(PAGES_DIR)/about.html
|
||||
./filec $< data_about_html > $@
|
||||
$(TMPL_DIR)/%.ctt: $(TMPL_DIR)/%.tt
|
||||
./filec $< data_$(notdir $*)_tt > $@
|
||||
|
||||
make_tmpls: $(TMPLS_C)
|
||||
|
||||
$(MASTODONT_DIR):
|
||||
cd ..; fossil clone $(MASTODONT_URL) || true
|
||||
|
@ -151,11 +66,9 @@ install: $(TARGET)
|
|||
install -d $(PREFIX)/share/treebird/
|
||||
cp -r dist/ $(PREFIX)/share/treebird/
|
||||
|
||||
test:
|
||||
make -C test
|
||||
|
||||
apache_start:
|
||||
./scripts/fcgistarter.sh
|
||||
test: all $(UNIT_TESTS)
|
||||
@echo " ... Tests ready"
|
||||
@./test/test.pl
|
||||
|
||||
dep_build:
|
||||
make -C $(MASTODONT_DIR)
|
||||
|
@ -163,10 +76,17 @@ dep_build:
|
|||
%.o: %.c %.h $(PAGES)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
# For tests
|
||||
%.bin: %.c
|
||||
@$(CC) $(CFLAGS) $< -o $@ $(OBJ_NO_MAIN) $(PAGES_C_OBJ) $(LDFLAGS)
|
||||
@echo -n " $@"
|
||||
|
||||
clean:
|
||||
rm -f $(OBJ) src/file-to-c/main.o
|
||||
rm -f $(PAGES_CMP)
|
||||
rm -f filec
|
||||
rm -f $(TMPLS_C)
|
||||
rm -f test/unit/*.bin
|
||||
rm -f filec ctemplate
|
||||
rm $(TARGET) || true
|
||||
make -C $(MASTODONT_DIR) clean
|
||||
|
||||
clean_deps:
|
||||
|
@ -174,4 +94,4 @@ clean_deps:
|
|||
|
||||
clean_all: clean clean_deps
|
||||
|
||||
.PHONY: all filec clean update clean clean_deps clean_all test
|
||||
.PHONY: all filec clean update clean clean_deps clean_all test install_deps
|
||||
|
|
|
@ -10,7 +10,9 @@ The goal is to create a frontend that's lightweight enough to be viewed without
|
|||
usable enough to improve the experience with JS.
|
||||
|
||||
Treebird uses C with FCGI, mastodont-c (library designed for Treebird, but can be used
|
||||
for other applications as well), and plain JavaScript for the frontend (100% optional).
|
||||
for other applications as well), Perl, and **optional** JavaScript for the frontend (100% functional without
|
||||
javascript, it only helps). Uses [RE:DOM](https://redom.js.org/) (2kb JS library) to assist with DOM
|
||||
creation and native JS apis. (Already bundled)
|
||||
|
||||
## Why?
|
||||
|
||||
|
@ -24,7 +26,7 @@ This led me to one choice, to develop my own frontend.
|
|||
|
||||
Treebird respects compatibility with old browsers, and thus uses HTML table layouts, which are
|
||||
supported even by most modern terminal web browsers. The core browser we aim to at least maintain compatibility
|
||||
with is Netsurf, but most other browsers like GNU Emacs EWW, elinks, render Treebird wonderfully.
|
||||
with is Netsurf, but most other browsers like GNU Emacs EWW, elinks, render Treebird just alright.
|
||||
|
||||
## Credits
|
||||
|
||||
|
|
|
@ -9,8 +9,10 @@
|
|||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
#include <mastodont.h>
|
||||
#if !(defined(FALSE) && defined(TRUE))
|
||||
#define FALSE 0
|
||||
#define TRUE 1
|
||||
#endif
|
||||
#define UNSET NULL
|
||||
|
||||
/*
|
||||
|
|
After Width: | Height: | Size: 939 B |
After Width: | Height: | Size: 788 B |
After Width: | Height: | Size: 676 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 454 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 752 B |
After Width: | Height: | Size: 752 B |
After Width: | Height: | Size: 326 B |
After Width: | Height: | Size: 645 B |
After Width: | Height: | Size: 507 B |
After Width: | Height: | Size: 876 B |
After Width: | Height: | Size: 703 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 979 B |
After Width: | Height: | Size: 904 B |
After Width: | Height: | Size: 827 B |
After Width: | Height: | Size: 2.0 KiB |
|
@ -0,0 +1,15 @@
|
|||
// Get emojis from file
|
||||
function get_emojo_picker(callback)
|
||||
{
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "/emoji_picker");
|
||||
xhr.onreadystatechange = function() {
|
||||
if (this.readyState === XMLHttpRequest.DONE)
|
||||
callback(this.response);
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
get_emojo_picker((emojo_picker_str) => {
|
||||
document.body.insertAdjacentHTML('beforeend', emojo_picker_str);
|
||||
});
|
|
@ -0,0 +1,351 @@
|
|||
"use strict";
|
||||
|
||||
// Helper function for manipulating the DOM / creating DOM elements
|
||||
function em(tag, values, child)
|
||||
{
|
||||
let element = document.createElement(tag);
|
||||
|
||||
// Can either be a "append"-able type, or properties
|
||||
// If it's properties, then child must be set
|
||||
if ((typeof values === 'string' ||
|
||||
values instanceof HTMLElement) && !child)
|
||||
{
|
||||
element.append(element);
|
||||
}
|
||||
else if (typeof values === 'number') {
|
||||
element.append(values.toString());
|
||||
}
|
||||
else if (typeof values === 'object' && child) {
|
||||
for (const prop in values)
|
||||
{
|
||||
element[prop] = values[prop];
|
||||
}
|
||||
} // ???
|
||||
|
||||
// Type doesn't matter, just append whatever is in child
|
||||
if (child)
|
||||
{
|
||||
// Well, except if it's a string
|
||||
if (typeof values === 'number')
|
||||
element.append(child.toString());
|
||||
else
|
||||
element.append(child);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
function get_cookie(cookiestr)
|
||||
{
|
||||
return document.cookie
|
||||
.split(';')
|
||||
.find(row => row.startsWith(cookiestr+'='))
|
||||
.split('=')[1];
|
||||
}
|
||||
|
||||
// TODO Check if logged in .acct value is the same
|
||||
function reply_get_mentions(reply, content)
|
||||
{
|
||||
const regexpr = /<a target="_parent" class="mention" href="https?:\/\/.*?\/@(.*?)@(.*?)">.*?<\/a>/g;
|
||||
const arr = [...content.matchAll(regexpr)];
|
||||
let res = reply ? `@${reply} ` : "";
|
||||
const matches = content.matchAll(regexpr);
|
||||
|
||||
for (let x of matches)
|
||||
{
|
||||
res += `@${x[1]}@${x[2]} `;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
function form_enter_submit(e, that)
|
||||
{
|
||||
if ((e.ctrlKey || e.metaKey) && e.keyCode === 13)
|
||||
that.closest('form').submit();
|
||||
}
|
||||
|
||||
// Submit form entry on enter when in textbox/textarea
|
||||
document.querySelectorAll("input[type=text], input[type=url], input[type=email], input[type=password], textarea").forEach((i) => {
|
||||
i.addEventListener("keydown", e => form_enter_submit(e, i));
|
||||
});
|
||||
|
||||
function construct_query(query)
|
||||
{
|
||||
let query_string = "";
|
||||
let keys = Object.keys(query);
|
||||
let vals = Object.values(query);
|
||||
const len = keys.length;
|
||||
|
||||
for (let i = 0; i < keys.length; ++i)
|
||||
{
|
||||
query_string += keys[i] + "=" + vals[i];
|
||||
if (i !== keys.length-1)
|
||||
query_string += "&";
|
||||
}
|
||||
return query_string;
|
||||
}
|
||||
|
||||
function send_request(url, query, type, cb, cb_args)
|
||||
{
|
||||
let query_str = construct_query(query);
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open(type, url);
|
||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
xhr.onreadystatechange = function() {
|
||||
if (this.readyState === XMLHttpRequest.DONE)
|
||||
cb(this, cb_args);
|
||||
};
|
||||
xhr.send(query_str);
|
||||
return xhr;
|
||||
}
|
||||
|
||||
function upload_file(url, file_name, file, cb, cb_args, onprogress, onload)
|
||||
{
|
||||
let xhr = new XMLHttpRequest();
|
||||
let form_data = new FormData();
|
||||
xhr.open("post", url);
|
||||
form_data.append(file_name, file);
|
||||
// xhr.setRequestHeader("Content-Type", "multipart/form-data");
|
||||
xhr.onreadystatechange = function() {
|
||||
if (this.readyState === XMLHttpRequest.DONE)
|
||||
cb(this, cb_args);
|
||||
};
|
||||
xhr.upload.onprogress = onprogress;
|
||||
xhr.upload.onload = onload;
|
||||
xhr.send(form_data);
|
||||
return xhr;
|
||||
}
|
||||
|
||||
function change_count_text(val, sum)
|
||||
{
|
||||
if (val === "")
|
||||
val = 0
|
||||
else
|
||||
val = parseInt(val);
|
||||
val += sum;
|
||||
return val > 0 ? val.toString() : "";
|
||||
}
|
||||
|
||||
function interact_action(status, type)
|
||||
{
|
||||
let like = status.querySelector(".statbtn .like");
|
||||
let repeat = status.querySelector(".statbtn .repeat");
|
||||
|
||||
let svg;
|
||||
if (type.value === "like" || type.value === "unlike")
|
||||
svg = [ like ];
|
||||
else if (type.value === "repeat" || type.value === "unrepeat")
|
||||
svg = [ repeat ];
|
||||
else if (type.value === "likeboost")
|
||||
svg = [ like, repeat ];
|
||||
|
||||
if (svg)
|
||||
svg.forEach(that => {
|
||||
let label = that.parentNode;
|
||||
let counter = label.querySelector(".count");
|
||||
let is_interacted = label.classList.contains("interacted");
|
||||
|
||||
if (counter)
|
||||
{
|
||||
counter.innerHTML = change_count_text(counter.innerHTML, is_interacted ? -1 : 1);
|
||||
}
|
||||
else {
|
||||
// Nobody interacted with this yet, create counter
|
||||
const counter = em("span", { className: "count" }, 1)
|
||||
label.append(counter);
|
||||
is_interacted = false;
|
||||
}
|
||||
|
||||
if (is_interacted)
|
||||
{
|
||||
// Animation
|
||||
that.classList.remove("interacted-anim");
|
||||
label.classList.remove("interacted");
|
||||
|
||||
// Flip itype value
|
||||
type.value = type.value.replace("un", "");
|
||||
}
|
||||
else {
|
||||
that.classList.add("interacted-anim");
|
||||
label.classList.add("interacted");
|
||||
type.value = "un" + type.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function status_event(e)
|
||||
{
|
||||
let target = e.target.closest(".statbtn");
|
||||
if (target)
|
||||
{
|
||||
console.log('huh');
|
||||
// Don't JS these
|
||||
if (target.classList.contains("reply-btn") ||
|
||||
target.classList.contains("view-btn") ||
|
||||
target.classList.contains("statbtn-last"))
|
||||
return true;
|
||||
let type = target.parentNode.querySelector(".itype");
|
||||
let status = e.target.closest(".status");
|
||||
|
||||
send_request("/treebird_api/v1/interact",
|
||||
{
|
||||
id: status.id,
|
||||
itype: type.value
|
||||
},
|
||||
"POST",
|
||||
(xhr, args) => {
|
||||
if (xhr.status !== 200)
|
||||
{
|
||||
// Undo action if failure
|
||||
interact_action(status, type);
|
||||
}
|
||||
}, null);
|
||||
|
||||
interact_action(status, type);
|
||||
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function frame_resize()
|
||||
{
|
||||
let rightbar_frame = document.querySelector("#rightbar .sidebar-frame");
|
||||
let rbar_frame_win = rightbar_frame.contentWindow;
|
||||
rightbar_frame.height = rbar_frame_win.document.body.scrollHeight;
|
||||
}
|
||||
|
||||
function filesize_to_str(bs)
|
||||
{
|
||||
const val = bs === 0 ? 0 : Math.floor(Math.log(bs) / Math.log(1024));
|
||||
return (bs / 1024**val).toFixed(1) * 1 + ['B', 'kB', 'MB', 'GB', 'TB'][val];
|
||||
}
|
||||
|
||||
function html_encode(str)
|
||||
{
|
||||
let en = document.createElement("span");
|
||||
en.textContent = str;
|
||||
return en.innerHTML;
|
||||
}
|
||||
|
||||
function construct_file_upload(file, file_content)
|
||||
{
|
||||
let container = document.createElement("div");
|
||||
let content = document.createElement("img");
|
||||
let info = document.createElement("span");
|
||||
container.className = "file-upload";
|
||||
|
||||
info.className = "upload-info";
|
||||
info.innerHTML = `<span class="filesize">${filesize_to_str(file.size)}</span> • <span class="filename">${html_encode(file.name)}</span>`;
|
||||
let progress_div = document.createElement("div");
|
||||
progress_div.className = "file-progress";
|
||||
info.appendChild(progress_div);
|
||||
|
||||
content.src = file_content;
|
||||
content.className = "upload-content";
|
||||
|
||||
container.appendChild(content);
|
||||
container.appendChild(info);
|
||||
return container;
|
||||
}
|
||||
|
||||
function update_uploads_json(dom)
|
||||
{
|
||||
let root = dom.parentNode;
|
||||
let items = root.getElementsByClassName("file-upload");
|
||||
let ids = [];
|
||||
|
||||
for (let i of items)
|
||||
{
|
||||
if (i.dataset.id)
|
||||
ids.push(i.dataset.id);
|
||||
}
|
||||
|
||||
// Goto statusbox
|
||||
root = root.parentNode;
|
||||
let file_ids = root.querySelector(".file-ids-json");
|
||||
if (!file_ids)
|
||||
{
|
||||
// Create if doesn't exist
|
||||
file_ids = document.createElement("input");
|
||||
file_ids.type = "hidden";
|
||||
file_ids.className = "file-ids-json";
|
||||
file_ids.name = "fileids";
|
||||
root.appendChild(file_ids);
|
||||
}
|
||||
|
||||
file_ids.value = JSON.stringify(ids);
|
||||
}
|
||||
|
||||
function evt_file_upload(e)
|
||||
{
|
||||
let target = e.target;
|
||||
let file_upload_dom = this.closest("form").querySelector(".file-uploads-container");
|
||||
file_upload_dom.className = "file-uploads-container";
|
||||
const files = [...this.files];
|
||||
|
||||
let reader;
|
||||
|
||||
// Clear file input
|
||||
this.value = '';
|
||||
|
||||
// Create file upload
|
||||
for (let file of files)
|
||||
{
|
||||
reader = new FileReader();
|
||||
reader.onload = (() => {
|
||||
return (e) => {
|
||||
let file_dom = construct_file_upload(file, e.target.result);
|
||||
|
||||
file_upload_dom.appendChild(file_dom);
|
||||
|
||||
let xhr = upload_file("/treebird_api/v1/attachment",
|
||||
"file",
|
||||
file,
|
||||
(xhr, args) => {
|
||||
// TODO errors
|
||||
file_dom.dataset.id = JSON.parse(xhr.response).id;
|
||||
update_uploads_json(file_dom);
|
||||
}, null,
|
||||
(e) => {
|
||||
let upload_file_progress = file_dom
|
||||
.querySelector(".file-progress");
|
||||
// Add offset of 3
|
||||
upload_file_progress.style.width = 3+((e.loaded/e.total)*97);
|
||||
},
|
||||
(e) => {
|
||||
file_dom.querySelector(".upload-content").style.opacity = "1.0";
|
||||
file_dom.querySelector(".file-progress").remove();
|
||||
});
|
||||
}
|
||||
})(file);
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Main (when loaded)
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
let interact_btn = document.getElementsByClassName("status");
|
||||
|
||||
// Add event listener to add specificied buttons
|
||||
for (let i = 0; i < interact_btn.length; ++i)
|
||||
{
|
||||
interact_btn[i].addEventListener('click', status_event);
|
||||
}
|
||||
|
||||
// Resize notifications iFrame to full height
|
||||
let rightbar_frame = document.querySelector("#rightbar .sidebar-frame");
|
||||
if (rightbar_frame)
|
||||
{
|
||||
rightbar_frame.contentWindow.addEventListener('DOMContentLoaded', frame_resize);
|
||||
}
|
||||
|
||||
// File upload
|
||||
let file_inputs = document.querySelectorAll(".statusbox input[type=file]");
|
||||
for (let file_input of file_inputs)
|
||||
{
|
||||
file_input.addEventListener('change', evt_file_upload);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,412 @@
|
|||
// enum Direction
|
||||
const UP = 0;
|
||||
const RIGHT = 1;
|
||||
const DOWN = 2;
|
||||
const LEFT = 3;
|
||||
|
||||
// enum State
|
||||
const STATE_START = 0;
|
||||
const STATE_PLAYING = 1;
|
||||
const STATE_GAME_OVER = 2;
|
||||
const STATE_GAME_WIN = 3;
|
||||
|
||||
let started = false;
|
||||
|
||||
class Wormle
|
||||
{
|
||||
constructor(view, title, score, opts)
|
||||
{
|
||||
this.view = view;
|
||||
this.title = title;
|
||||
this.score_text = score;
|
||||
|
||||
// State
|
||||
this.state = STATE_START;
|
||||
|
||||
this.grid_size = opts.grid_size;
|
||||
this.view_size = opts.view_size;
|
||||
this.tile_size = [ this.view_size[0] / this.grid_size[0],
|
||||
this.view_size[1] / this.grid_size[1] ];
|
||||
|
||||
// Apply class to get properties
|
||||
this.view.className = "wormle-view";
|
||||
this.apples = [];
|
||||
|
||||
this.state_handle();
|
||||
this.loop = null;
|
||||
}
|
||||
|
||||
reset()
|
||||
{
|
||||
// Player
|
||||
this.body = [];
|
||||
this.body_len = 8;
|
||||
this.score = 0;
|
||||
this.level = 0;
|
||||
this.tickspeed = 280;
|
||||
|
||||
// Controls
|
||||
this.pos = this.spawn_pos = { x: 3, y: 3 };
|
||||
this.direction = RIGHT;
|
||||
this.direction_queue = [];
|
||||
this.clear_apples();
|
||||
this.apples = [];
|
||||
this.add_apple();
|
||||
}
|
||||
|
||||
clear_apples()
|
||||
{
|
||||
const len = this.apples.length
|
||||
for (let i = 0; i < len; ++i)
|
||||
{
|
||||
this.apples[this.apples.length-1].elem.remove();
|
||||
this.apples.pop();
|
||||
}
|
||||
}
|
||||
|
||||
obj_rand_position()
|
||||
{
|
||||
let x = 0, y = 0;
|
||||
do
|
||||
{
|
||||
x = Math.floor(Math.random() * this.grid_size[0]);
|
||||
y = Math.floor(Math.random() * this.grid_size[1]);
|
||||
} while (this.check_collision(x, y));
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
create_body(name, x, y)
|
||||
{
|
||||
let body = document.createElement("div");
|
||||
body.className = "wormle-body "+(name ? "wormle-"+name : "");
|
||||
body.style.left = x * this.tile_size[0] + "px";
|
||||
body.style.top = y * this.tile_size[1] + "px";
|
||||
body.style.width = this.tile_size[0] + "px";
|
||||
body.style.height = this.tile_size[1] + "px";
|
||||
return body;
|
||||
}
|
||||
|
||||
worm_grow()
|
||||
{
|
||||
// If anything else, assume we are starting
|
||||
let { x, y } = this.spawn_pos;
|
||||
|
||||
if (this.body.length)
|
||||
{
|
||||
x = this.body[this.body.length-1].pos.x;
|
||||
y = this.body[this.body.length-1].pos.y;
|
||||
}
|
||||
|
||||
let body = this.create_body("player", x, y);
|
||||
this.view.appendChild(body);
|
||||
|
||||
this.body.push({
|
||||
pos: { x, y },
|
||||
elem: body,
|
||||
});
|
||||
}
|
||||
|
||||
add_apple()
|
||||
{
|
||||
let pos = this.obj_rand_position();
|
||||
|
||||
let body = this.create_body("apple", pos[0], pos[1]);
|
||||
this.view.appendChild(body);
|
||||
|
||||
this.apples.push({
|
||||
pos: {
|
||||
x: pos[0],
|
||||
y: pos[1],
|
||||
},
|
||||
elem: body,
|
||||
});
|
||||
}
|
||||
|
||||
move(dir)
|
||||
{
|
||||
const DIR_QUEUE_MAX = 4;
|
||||
if (this.direction_queue.length < DIR_QUEUE_MAX)
|
||||
{
|
||||
this.direction_queue.push(dir);
|
||||
}
|
||||
}
|
||||
|
||||
last_dir()
|
||||
{
|
||||
if (this.direction_queue.length)
|
||||
return this.direction_queue[this.direction_queue-1];
|
||||
else
|
||||
return this.direction;
|
||||
}
|
||||
|
||||
worm_update()
|
||||
{
|
||||
// Yeah this is ugly and i don't care its just an easter egg...
|
||||
for (let i = this.body.length-1; i >= 1; --i)
|
||||
{
|
||||
// Shift position forward
|
||||
this.body[i].elem.style.left =
|
||||
this.body[i-1].pos.x * this.tile_size[0] + "px";
|
||||
this.body[i].elem.style.top =
|
||||
this.body[i-1].pos.y * this.tile_size[1] + "px";
|
||||
|
||||
this.body[i].pos.x = this.body[i-1].pos.x;
|
||||
this.body[i].pos.y = this.body[i-1].pos.y;
|
||||
}
|
||||
|
||||
this.body[0].pos.x = this.pos.x;
|
||||
this.body[0].pos.y = this.pos.y;
|
||||
this.body[0].elem.style.left = this.pos.x * this.tile_size[0] + "px";
|
||||
this.body[0].elem.style.top = this.pos.y * this.tile_size[1] + "px";
|
||||
|
||||
}
|
||||
|
||||
handle_movement()
|
||||
{
|
||||
// Update queue
|
||||
if (this.direction_queue.length)
|
||||
{
|
||||
this.direction = this.direction_queue[0];
|
||||
}
|
||||
this.direction_queue.shift();
|
||||
|
||||
|
||||
// Move in direction
|
||||
switch (this.direction)
|
||||
{
|
||||
case LEFT: {
|
||||
this.pos.x--;
|
||||
break;
|
||||
}
|
||||
case RIGHT: {
|
||||
this.pos.x++;
|
||||
break;
|
||||
}
|
||||
case UP: {
|
||||
this.pos.y--;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
case DOWN: {
|
||||
this.pos.y++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
game_over()
|
||||
{
|
||||
this.title.innerHTML = `<h1>You lost! Score: ${this.score}</h1>
|
||||
<p>Press any key...</p>`;
|
||||
}
|
||||
|
||||
game_win()
|
||||
{
|
||||
this.title.innerHTML = `<h1>You won Wormle!</h1>
|
||||
<p>Pat yourself on the back...</p>
|
||||
<img src="../img/noname.png"> <img src="../img/noname.png"> <img src="../img/noname.png"> `;
|
||||
}
|
||||
|
||||
start()
|
||||
{
|
||||
for (let i of document.querySelectorAll(".wormle-body"))
|
||||
{
|
||||
i.remove();
|
||||
}
|
||||
|
||||
this.title.innerHTML = `<h1>Wormle</h1>
|
||||
<input type="button" value="Play" class="wormle-play-btn">`;
|
||||
this.reset();
|
||||
|
||||
// Play button
|
||||
let btn = this.title.querySelector(".wormle-play-btn");
|
||||
let that = this;
|
||||
if (btn)
|
||||
btn.addEventListener("click", () => {
|
||||
that.next_state();
|
||||
this.view.focus();
|
||||
});
|
||||
}
|
||||
|
||||
next_state()
|
||||
{
|
||||
this.state++;
|
||||
if (this.state >= STATE_GAME_OVER)
|
||||
this.state = 0;
|
||||
this.state_handle();
|
||||
}
|
||||
|
||||
update_score(amount = 1)
|
||||
{
|
||||
this.score += amount;
|
||||
this.score_text.innerHTML = this.score;
|
||||
}
|
||||
|
||||
state_handle()
|
||||
{
|
||||
switch (this.state)
|
||||
{
|
||||
case STATE_START:
|
||||
this.start();
|
||||
break;
|
||||
|
||||
case STATE_GAME_OVER:
|
||||
this.game_over();
|
||||
break;
|
||||
|
||||
case STATE_GAME_WIN:
|
||||
this.game_win();
|
||||
break;
|
||||
|
||||
default:
|
||||
this.score_text.innerHTML = this.score;
|
||||
this.title.innerHTML = '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
get_head()
|
||||
{
|
||||
let index = this.body.length-1 >= 0 ? this.body.length-1 : 0;
|
||||
return this.body[index];
|
||||
}
|
||||
|
||||
next_level()
|
||||
{
|
||||
if (this.level > 6) return;
|
||||
this.level++;
|
||||
this.tickspeed -= 25;
|
||||
if (this.level % 2 == 0) this.add_apple();
|
||||
}
|
||||
|
||||
check_collision(x = this.body[0].pos.x, y = this.body[0].pos.y)
|
||||
{
|
||||
for (let i = 1; i < this.body.length; ++i)
|
||||
{
|
||||
if (x === this.body[i].pos.x &&
|
||||
y === this.body[i].pos.y ||
|
||||
x >= this.grid_size[0] || x < 0 ||
|
||||
y >= this.grid_size[1] || y < 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
update()
|
||||
{
|
||||
const APPLE_MULTIPLIER = 1;
|
||||
if (this.state == STATE_PLAYING)
|
||||
{
|
||||
if (this.body.length < this.body_len)
|
||||
this.worm_grow();
|
||||
|
||||
this.handle_movement();
|
||||
this.worm_update();
|
||||
|
||||
// Apple collision
|
||||
for (let i = 0; i < this.apples.length; ++i)
|
||||
{
|
||||
if (this.body[0].pos.x === this.apples[i].pos.x &&
|
||||
this.body[0].pos.y === this.apples[i].pos.y)
|
||||
{
|
||||
// Delete apple
|
||||
this.apples[i].elem.remove();
|
||||
this.apples.splice(i, 1);
|
||||
|
||||
this.update_score();
|
||||
this.body_len += APPLE_MULTIPLIER;
|
||||
this.add_apple();
|
||||
|
||||
// Check if max size
|
||||
if (this.body_len >= this.grid_size[0] * this.grid_size[1])
|
||||
{
|
||||
this.state = STATE_GAME_WIN;
|
||||
this.state_handle();
|
||||
}
|
||||
|
||||
if (this.score % 10 === 0)
|
||||
{
|
||||
this.next_level();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Game over?
|
||||
if (this.check_collision())
|
||||
{
|
||||
this.state = STATE_GAME_OVER;
|
||||
this.state_handle();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function start_wormle()
|
||||
{
|
||||
if (started)
|
||||
return;
|
||||
started = true;
|
||||
let wormle_view = document.createElement("div");
|
||||
wormle_view.tabIndex = 1;
|
||||
let title = document.createElement("span");
|
||||
title.className = "wormle-title";
|
||||
let score = document.createElement("span");
|
||||
score.className = "wormle-score";
|
||||
wormle_view.appendChild(score);
|
||||
wormle_view.appendChild(title);
|
||||
let game = new Wormle(wormle_view, title, score, {
|
||||
grid_size: [16, 16], // Tiles
|
||||
view_size: [400, 400], // Px
|
||||
});
|
||||
|
||||
let callback = () => {
|
||||
game.update();
|
||||
game.loop = setTimeout(callback, game.tickspeed);
|
||||
};
|
||||
|
||||
game.loop = setTimeout(callback, game.tickspeed);
|
||||
|
||||
document.addEventListener("keydown", (e) => {
|
||||
// Fallback incase
|
||||
e = e || window.event;
|
||||
|
||||
if (e.target.classList.contains("wormle-view"))
|
||||
{
|
||||
if (game.state !== STATE_PLAYING)
|
||||
game.next_state();
|
||||
else switch (e.keyCode)
|
||||
{
|
||||
// Left
|
||||
case 37: {
|
||||
if (game.last_dir() !== RIGHT) game.move(LEFT);
|
||||
break;
|
||||
}
|
||||
// Up
|
||||
case 38: {
|
||||
if (game.last_dir() !== DOWN) game.move(UP);
|
||||
break;
|
||||
}
|
||||
// Right
|
||||
case 39: {
|
||||
if (game.last_dir() !== LEFT) game.move(RIGHT);
|
||||
break;
|
||||
}
|
||||
// Down
|
||||
case 40: {
|
||||
if (game.last_dir() !== UP)
|
||||
game.move(DOWN);
|
||||
break;
|
||||
}
|
||||
}
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
let view_place = document.querySelector(".about-content");
|
||||
document.querySelector(".simple-page").insertBefore(wormle_view, view_place);
|
||||
}
|
||||
|
||||
(function() {
|
||||
document.getElementById("about-icon").addEventListener("click", start_wormle);
|
||||
})();
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="8" y1="12" x2="16" y2="12"></line></svg>
|
||||
<svg width="20" height="20" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10"/><g><line x1="9" x2="9" y1="6.9367" y2="11.755" stroke-width="1.7916"/><line x1="15" x2="15" y1="6.9367" y2="11.755" stroke-width="1.7916"/><path d="m7.0891 15.099s4.7206 4.7543 9.7109 0" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="miter" stroke-width="1.9764"/></g></svg>
|
||||
|
|
Before Width: | Height: | Size: 266 B After Width: | Height: | Size: 516 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-globe"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>
|
After Width: | Height: | Size: 409 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>
|
After Width: | Height: | Size: 287 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>
|
After Width: | Height: | Size: 313 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></svg>
|
After Width: | Height: | Size: 313 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.5 2v6h-6M21.34 15.57a10 10 0 1 1-.57-8.38"/></svg>
|
After Width: | Height: | Size: 239 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
After Width: | Height: | Size: 272 B |
|
@ -0,0 +1 @@
|
|||
<svg width="20" height="20" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="10.107" cy="8.7642" r="6.7391" stroke-width="1.6848"/><line x1="18.531" x2="14.866" y1="17.188" y2="13.524" stroke-width="1.6848"/><path d="m14.756 20.738 1.9607 1.9607 1.9091-1.9297" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.6554"/></svg>
|
After Width: | Height: | Size: 446 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 9.9-1"></path></svg>
|
After Width: | Height: | Size: 286 B |
|
@ -0,0 +1,501 @@
|
|||
:root
|
||||
{
|
||||
--account-overlay-gradient-top: rgba(50, 50, 50, 0.6);
|
||||
--account-overlay-gradient-bottom: #303030;
|
||||
}
|
||||
|
||||
/* Dark color overrides */
|
||||
html
|
||||
{
|
||||
background-color: #202020;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
color: #fcfcfc;
|
||||
}
|
||||
|
||||
#main-page
|
||||
{
|
||||
border: 1px solid rgba(60, 60, 60, 0.8);
|
||||
}
|
||||
|
||||
#navbar
|
||||
{
|
||||
border-top: 2px solid #4e4e4e;
|
||||
background: linear-gradient(#3e3e3e, #262626);
|
||||
border-bottom: 1px solid #101010;
|
||||
}
|
||||
|
||||
a, a:visited, a:hover, a:active
|
||||
{
|
||||
color: #eaeaea;
|
||||
}
|
||||
|
||||
#navbar img
|
||||
{
|
||||
filter: contrast(0);
|
||||
}
|
||||
|
||||
.navbar-btn
|
||||
{
|
||||
stroke: #aaa;
|
||||
}
|
||||
|
||||
#navbar span.info
|
||||
{
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.mini-links a,
|
||||
.mini-links .bullet-separate
|
||||
{
|
||||
color: #606060;
|
||||
}
|
||||
|
||||
.sidebar
|
||||
{
|
||||
background-color: rgba(34, 34, 34, var(--sidebar-opacity));
|
||||
}
|
||||
|
||||
.btn
|
||||
{
|
||||
background: linear-gradient(#303030, #252525);
|
||||
color: #bbb;
|
||||
text-decoration: none;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
#main
|
||||
{
|
||||
background-color: #202020;
|
||||
}
|
||||
|
||||
.btn:hover,
|
||||
.btn:active,
|
||||
.btn-menu:hover,
|
||||
.sidebarbtn:hover
|
||||
{
|
||||
background: linear-gradient(#424242, #363636);
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sidebarbtn
|
||||
{
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.sidebarbtn:hover
|
||||
{
|
||||
border: 1px solid #505050;
|
||||
}
|
||||
|
||||
.sidebarbtn, .sidebarbtn:visited
|
||||
{
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
#leftbar, #rightbar
|
||||
{
|
||||
border-color: #333;
|
||||
}
|
||||
|
||||
.btn.active
|
||||
{
|
||||
background: linear-gradient(#606060, #d3d3d3);
|
||||
box-shadow: inset 0px -2px 5px rgba(0, 0, 0, 0.2);
|
||||
color: #fff;
|
||||
text-shadow: 0px 1px 2px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
|
||||
.btn-alt.active,
|
||||
.btn-alt:active
|
||||
{
|
||||
background: linear-gradient(#ee0000, #aa0000);
|
||||
border-color: #800000;
|
||||
}
|
||||
|
||||
.btn-alt:hover
|
||||
{
|
||||
background: linear-gradient(#aa0000, #ee0000);
|
||||
}
|
||||
|
||||
.statusbox
|
||||
{
|
||||
background-color: #303030;
|
||||
border-color: #606060;
|
||||
}
|
||||
|
||||
.menubar
|
||||
{
|
||||
background: linear-gradient(#404040, #303030);
|
||||
border-color: #505050;
|
||||
}
|
||||
|
||||
.statusbox textarea,
|
||||
input[type=text]
|
||||
{
|
||||
background-color: #444;
|
||||
border-color: #666;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.status.focused,
|
||||
.status:target
|
||||
{
|
||||
padding-left: 7px;
|
||||
background-color: #371b1b;
|
||||
border-left: 3px solid #aa0000;
|
||||
}
|
||||
|
||||
input[type=submit]
|
||||
{
|
||||
border-color: #666;
|
||||
}
|
||||
|
||||
select
|
||||
{
|
||||
background: linear-gradient(#555, #404040);
|
||||
color: #ccc;
|
||||
border-color: #666;
|
||||
}
|
||||
|
||||
select:hover
|
||||
{
|
||||
background: linear-gradient(#666, #505050);
|
||||
}
|
||||
|
||||
.account-sidebar
|
||||
{
|
||||
background-color: #404040;
|
||||
border-bottom: 1px dashed #505050;
|
||||
}
|
||||
|
||||
.header-btn
|
||||
{
|
||||
background: unset;
|
||||
}
|
||||
|
||||
.header-btn span,
|
||||
.header-btn .btn-header,
|
||||
.header-btn .btn-content,
|
||||
.username
|
||||
{
|
||||
color: #dadada;
|
||||
}
|
||||
|
||||
.status-interact svg
|
||||
{
|
||||
stroke: #aaa;
|
||||
}
|
||||
|
||||
.status .instance-info
|
||||
{
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.poster-stats .alignend
|
||||
{
|
||||
background-color: #202020;
|
||||
}
|
||||
|
||||
.status-view
|
||||
{
|
||||
background-color: #404040;
|
||||
border: 1px solid #505050;
|
||||
}
|
||||
|
||||
.menu
|
||||
{
|
||||
background-color: #454545;
|
||||
color: #ddd;
|
||||
border-color: #585858;
|
||||
}
|
||||
|
||||
.menu .btn-menu
|
||||
{
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.menu .btn-menu:hover
|
||||
{
|
||||
box-shadow: 0px 0px 0px 1px #505050;
|
||||
}
|
||||
|
||||
.time
|
||||
{
|
||||
color: #6a6a6a;
|
||||
}
|
||||
|
||||
.status
|
||||
{
|
||||
border-color: #454545;
|
||||
}
|
||||
|
||||
.in-reply-to svg
|
||||
{
|
||||
stroke: #8c8c8c;
|
||||
}
|
||||
|
||||
.in-reply-to, .in-reply-to a
|
||||
{
|
||||
color: #8c8c8c;
|
||||
}
|
||||
|
||||
.group-inputbox
|
||||
{
|
||||
border-color: #707070;
|
||||
}
|
||||
|
||||
#instance-panel
|
||||
{
|
||||
background: repeating-linear-gradient(-45deg, transparent, transparent 10px, #303030 10px, #303030 20px);
|
||||
border-top: 1px dashed #505050;
|
||||
border-bottom: 1px dashed #505050;
|
||||
}
|
||||
|
||||
ul.large-list
|
||||
{
|
||||
border: 1px solid #404040;
|
||||
}
|
||||
|
||||
.lists-view-container ul li:not(:last-child)
|
||||
{
|
||||
border-bottom: 1px solid #505050;
|
||||
}
|
||||
|
||||
ul.large-list li .edit-list-btn
|
||||
{
|
||||
background: linear-gradient(#606060, #505050);
|
||||
color: #fff;
|
||||
border-left: 1px solid #404040;
|
||||
}
|
||||
|
||||
.list-edit-content
|
||||
{
|
||||
background-color: #404040;
|
||||
border-top: #404040;
|
||||
}
|
||||
|
||||
.static.focused .poster-stats .alignend, .status:target .poster-stats .alignend
|
||||
{
|
||||
background-color: #371b1b;
|
||||
}
|
||||
|
||||
.statusbox .post-group input[type=radio].hidden:checked + .visibility-icon svg
|
||||
{
|
||||
stroke: #fff;
|
||||
}
|
||||
|
||||
.statusbox .post-group .visibility-icon:hover
|
||||
{
|
||||
background-color: #4f4f4f;
|
||||
}
|
||||
|
||||
.emoji-react-box
|
||||
{
|
||||
border: 1px solid #515151;
|
||||
padding: 3px 7px;
|
||||
}
|
||||
|
||||
.sidebarbtn-sub
|
||||
{
|
||||
color: unset;
|
||||
}
|
||||
|
||||
|
||||
#leftbar ul.sidebar-config
|
||||
{
|
||||
background: linear-gradient(#303030, #202020);
|
||||
color: #cacaca;
|
||||
border-radius: 6px;
|
||||
padding: 4px;
|
||||
margin: 8px;
|
||||
border: 1px solid #505050;
|
||||
box-shadow: inset 0px 2px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.sidebarbtn-sub:active, .sidebarbtn-sub:hover
|
||||
{
|
||||
background-color: #505050;
|
||||
box-shadow: 0px 1px 0px #606060;
|
||||
}
|
||||
|
||||
input[type=submit].post-btn
|
||||
{
|
||||
|
||||
background: linear-gradient(#404040, #303030);
|
||||
border: 1px solid #505050;
|
||||
}
|
||||
|
||||
input[type=submit].post-btn:hover
|
||||
{
|
||||
|
||||
background: linear-gradient(#505050, #404040);
|
||||
border: 1px solid #505050;
|
||||
}
|
||||
|
||||
.notification-compact
|
||||
{
|
||||
scrollbar-color: #404040 transparent;
|
||||
}
|
||||
|
||||
.notification-compact .notification-content.is-mention
|
||||
{
|
||||
color: #f3f3f3;
|
||||
}
|
||||
|
||||
.notification
|
||||
{
|
||||
border-bottom: 1px solid #505050;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sidebar-embed-container .navigation
|
||||
{
|
||||
background-color: rgba(60, 62, 64, .5);
|
||||
}
|
||||
|
||||
.navigation tr td:not(:last-child), .tabs tr td:not(:last-child)
|
||||
{
|
||||
border-right: 1px solid #606060;
|
||||
}
|
||||
|
||||
.navigation, .tabs
|
||||
{
|
||||
box-shadow: 0px 1px 0px #606060;
|
||||
}
|
||||
|
||||
input[type=range] {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
height: 12px;
|
||||
background: linear-gradient(#404040, #303030);
|
||||
border: 1px solid #505050;
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
input[type=range]::-webkit-slider-thumb,
|
||||
input[type=range]::-moz-range-thumb
|
||||
{
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 1px solid #303236;
|
||||
background: linear-gradient(#75777c, #505256);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Accounts */
|
||||
.acct-header
|
||||
{
|
||||
z-index: 1;
|
||||
padding-left: 160px;
|
||||
background: linear-gradient(#505053, #303033);
|
||||
border-bottom: 1px solid #202020;
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
|
||||
.account-info
|
||||
{
|
||||
background: linear-gradient(#28282b, #37373a);
|
||||
border-bottom: 1px solid #202020;
|
||||
color: #cacaca;
|
||||
}
|
||||
|
||||
.tabs .tab-btn:hover, .tabs .tab-btn:active
|
||||
{
|
||||
background: linear-gradient(#404040, #303032);
|
||||
}
|
||||
|
||||
.tabs .tab-btn.active, .tabs .tab-btn:checked
|
||||
{
|
||||
color: #eee;
|
||||
background: linear-gradient(#505052, #404045);
|
||||
}
|
||||
|
||||
.tabs .tab-btn
|
||||
{
|
||||
color: #808080;
|
||||
border-bottom: 2px solid #303030;
|
||||
}
|
||||
|
||||
.btn-disabled, .btn-disabled:hover
|
||||
{
|
||||
background: #252525;
|
||||
color: #606060;
|
||||
}
|
||||
|
||||
.header-btn:hover, .header-btn:active
|
||||
{
|
||||
background: linear-gradient(#5c5c5c,#444);
|
||||
}
|
||||
|
||||
html, body {
|
||||
scrollbar-color: #303030 #101010;
|
||||
}
|
||||
|
||||
.file-uploads-container .file-upload
|
||||
{
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
width: 200px;
|
||||
margin: 2px 4px;
|
||||
background: linear-gradient(#, #f7f7f7);
|
||||
border: 1px solid #606060;
|
||||
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.file-upload .upload-info {
|
||||
background: linear-gradient(#202020, #101010);
|
||||
border-top: 1px solid #505050;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.btn-icon
|
||||
{
|
||||
border: 1px solid #5f6376;
|
||||
background-color: #757991;
|
||||
text-align: middle;
|
||||
vertical-align: middle;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-icon svg
|
||||
{
|
||||
stroke: #ddd;
|
||||
}
|
||||
|
||||
.btn-icon:hover
|
||||
{
|
||||
background-color: #696d82;
|
||||
}
|
||||
|
||||
.btn-icon:active
|
||||
{
|
||||
background-color: #4e5161;
|
||||
}
|
||||
|
||||
.nav-symbol
|
||||
{
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.account-sidebar
|
||||
{
|
||||
border-bottom: 1px solid #404040;
|
||||
}
|
||||
|
||||
.account-sidebar .acct-info .acct
|
||||
{
|
||||
color: #ababab;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# Developing Treebird
|
||||
|
||||
Treebird development is a bit hacky. There are better ways to work with development
|
||||
|
||||
### Compiler flags
|
||||
|
||||
You can compile Treebird with some helpful flags, such as single_threaded to improve debugging for Treebird.
|
||||
|
||||
```
|
||||
make SINGLE_THREADED=1 all
|
||||
```
|
|
@ -1,62 +1,85 @@
|
|||
# Installing Treebird
|
||||
|
||||
This is a stub and isn't complete, please update it!
|
||||
|
||||
## Compiling
|
||||
|
||||
For the following GNU/Linux distributions, you will need the following libraries/packages:
|
||||
|
||||
- libcurl
|
||||
- Debian: `libcurl4-gnutls-dev`
|
||||
- Arch: `curl`
|
||||
- Void: `libcurl libcurl-devel`
|
||||
- libcjson
|
||||
- Debian: `libcjson-dev`
|
||||
- Arch: `cjson`
|
||||
- Void: `cJSON cJSON-devel`
|
||||
- libpcre
|
||||
- Debian: `libpcre3-dev`
|
||||
- Void: `libpcre2`
|
||||
- libfcgi
|
||||
- Debian: `libfcgi-dev`
|
||||
- Void: `fcgi fcgi-devel`
|
||||
###### Debian
|
||||
|
||||
`# apt install libcurl4-gnutls-dev libpcre2-dev libfcgi-dev build-essential perl libperl-dev libtemplate-perl`
|
||||
|
||||
###### Void GNU/Linux
|
||||
|
||||
`# xbps-install libcurl libcurl-devel base-devel pcre2 pcre2-devel fcgi fcgi-devel perl-Template-Toolkit`
|
||||
|
||||
###### Arch
|
||||
|
||||
`# pacman -S curl base-devel perl perl-template-toolkit`
|
||||
|
||||
###### Gentoo
|
||||
|
||||
TODO
|
||||
|
||||
Create a copy of `config.def.h` at `config.h`, edit the file with your information
|
||||
|
||||
Run `make`. This will also clone mastodont-c, and compile both it and Treebird.
|
||||
Run `make`. (**hint:** Pass -j3 to speed up compilation). This will also clone mastodont-c, and compile both it and Treebird.
|
||||
|
||||
If you `git pull` any changes, `make update` should be run after updating, as it ensures
|
||||
that mastodont-c is up to date.
|
||||
If you `fossil update` any changes, `make update` should be run after updating
|
||||
|
||||
## Perl dependencies manual install
|
||||
|
||||
**Note:** You **WONT** need to do this if your distribution above included all the deps (Template Toolkit)
|
||||
|
||||
At the moment, all of them listed above do, but if your distro is nonstandard, keep reading:
|
||||
|
||||
---
|
||||
|
||||
Treebird renders most of the content that you see in Perl using the Template Toolkit.
|
||||
|
||||
You can install it by running `make install_deps`
|
||||
|
||||
If that doesn't work, you can open a CPAN shell
|
||||
|
||||
```
|
||||
perl -MCPAN -e shell
|
||||
install Template::Toolkit
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
- TODO?
|
||||
|
||||
Run `make install`, this might require root.
|
||||
Run `# make install`
|
||||
|
||||
If this succeeds (assuming you used default variables), you can now find Treebird at the following
|
||||
|
||||
- `/usr/local/share/treebird/` - Contains CSS, images, and other meta files
|
||||
- `/usr/local/bin/treebird` - Regular executable CGI file, test it by running it as is, it'll spit HTML out!
|
||||
- `/usr/local/bin/treebird` - Regular executable CGI file, test it by running it as is, it shouldn't spit anything out
|
||||
|
||||
### Using NGINX (and fcgiwrap)
|
||||
## Development
|
||||
|
||||
Treebird can be served over nginx by using a FastCGI server such as fcgiwrap.
|
||||
For developing Treebird, see `DEVELOP.md`.
|
||||
|
||||
The example static files will be in `/usr/local/share/treebird/`, with `treebird.cgi` at `/usr/local/bin/treebird`.
|
||||
## Nginx
|
||||
|
||||
After running `make`, Treebird's files will be in the `dist/` directory. _Copy_, ***DO NOT MOVE***, **everything but treebird.cgi** of this folder to your web server. Copy `treebird.cgi` to another directory of your choosing.
|
||||
Treebird can be served over nginx by using a FastCGI daemon such as spawn-fcgi.
|
||||
|
||||
#### nginx
|
||||
An example nginx configuration is available in [treebird.nginx.conf](./sample/treebird.nginx.conf).
|
||||
An example Nginx configuration is available in [treebird.nginx.conf](./sample/treebird.nginx.conf).
|
||||
* Make sure to change `example.com` to your instance's domain.
|
||||
* Make sure to change the `root` to wherever the static files are being stored
|
||||
|
||||
#### fcgiwrap
|
||||
fcgiwrap can be installed on debian with `sudo apt install fcgiwrap`.
|
||||
## Using Apache and mod_proxy_fcgi
|
||||
|
||||
The example is using the default configuration included with the `fcgiwrap` package on Debian.
|
||||
Apache hasn't caused many troubles, and is in fact, what I use for development. You can see how to start
|
||||
spawn-fcgi in `scripts/fcgistarter.sh`.
|
||||
|
||||
### Using Apache and mod_proxy_fcgi
|
||||
An example Apache configuration is available in [treebird.apache.conf](./sample/treebird.apache.conf).
|
||||
|
||||
TODO
|
||||
#### spawn-fcgi
|
||||
|
||||
Example Apache configuration is available in [treebird.apache.conf](./sample/treebird.apache.conf).
|
||||
`spawn-fcgi` can be used for both Apache and Nginx. Read the manual for it to see how to work it, or view
|
||||
the testing script at `scripts/fcgistarter.sh`
|
||||
|
||||
- Please, at all costs, avoid FCGIWrap. It's caused nothing but headaches and has proved no real use other than
|
||||
spitting `Cannot get script name, are DOCUMENT_ROOT and SCRIPT_NAME (or SCRIPT_FILENAME) set and is the script executable?`
|
||||
at you (even if those variables are set and the script is executable)
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
# TODO a lot of things
|
||||
# THIS IS UNTESTED AND INCOMPLETE, ITS JUST FOR REFERENCE
|
||||
|
||||
# Give access to our directory
|
||||
<Directory "/usr/local/share/treebird/dist">
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
@ -13,6 +9,9 @@
|
|||
RewriteEngine on
|
||||
RewriteRule ^/(.*).css$ /$1.css [L]
|
||||
RewriteRule ^/svg/(.*).svg$ /svg/$1.svg [L]
|
||||
RewriteRule ^/js/(.*).js /js/$1.js [L]
|
||||
RewriteRule ^/img/(.*)$ /img/$1 [L]
|
||||
RewriteRule ^/emoji.json$ /emoji.json [L]
|
||||
RewriteRule ^/(treebird\_logo|favicon).png$ /$1.png [L]
|
||||
|
||||
# Set PATH_INFO variable
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
waifuism.life {
|
||||
reverse_proxy 127.0.0.1:4000
|
||||
}
|
||||
|
||||
treebird.waifuism.life {
|
||||
route {
|
||||
handle /js/* {
|
||||
root * /usr/local/share/treebird
|
||||
try_files {path}
|
||||
file_server
|
||||
}
|
||||
handle /img/* {
|
||||
root * /usr/local/share/treebird
|
||||
try_files {path}
|
||||
file_server
|
||||
}
|
||||
handle /svg/* {
|
||||
root * /usr/local/share/treebird
|
||||
try_files {path}
|
||||
file_server
|
||||
}
|
||||
handle /*.css {
|
||||
root * /usr/local/share/treebird
|
||||
try_files {path}
|
||||
file_server
|
||||
}
|
||||
handle /*.png {
|
||||
root * /usr/local/share/treebird
|
||||
try_files {path}
|
||||
file_server
|
||||
}
|
||||
handle /* {
|
||||
reverse_proxy * unix//var/run/fcgiwrap.socket {
|
||||
transport fastcgi {
|
||||
env SCRIPT_FILENAME /usr/local/bin/treebird.cgi
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +1,35 @@
|
|||
server {
|
||||
server_name treebird.example.com;
|
||||
|
||||
server_name treebird.example.com;
|
||||
location @treebird {
|
||||
fastcgi_param SCRIPT_FILENAME /usr/local/bin/treebird; # change this to the location of your treebird executable
|
||||
fastcgi_param PATH_INFO $uri;
|
||||
fastcgi_pass unix:/var/run/fcgiwrap.socket;
|
||||
include fastcgi.conf; #Check your nginx installation for fastcgi.conf or fastcgi_param
|
||||
}
|
||||
|
||||
location @treebird {
|
||||
include fastcgi.conf; #Check your nginx installation for fastcgi.conf or fastcgi_param
|
||||
fastcgi_param SCRIPT_FILENAME /usr/local/bin/treebird; # change this to the location of your treebird executable
|
||||
fastcgi_param PATH_INFO $uri;
|
||||
fastcgi_pass unix:/var/run/fcgiwrap.socket;
|
||||
}
|
||||
|
||||
location / {
|
||||
root /usr/local/share/treebird; #Change this to the location of the static files
|
||||
try_files $uri @treebird;
|
||||
}
|
||||
|
||||
listen [::]:443 ssl;
|
||||
listen 443 ssl;
|
||||
ssl_certificate /etc/letsencrypt/live/treebird.example.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/treebird.example.com/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
location / {
|
||||
root /usr/local/share/treebird; #Change this to the location of the static files
|
||||
try_files $uri @treebird;
|
||||
}
|
||||
|
||||
listen [::]:443 ssl;
|
||||
listen 443 ssl;
|
||||
ssl_certificate /etc/letsencrypt/live/treebird.example.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/treebird.example.com/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
}
|
||||
|
||||
server {
|
||||
if ($host = treebird.example.com) {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
if ($host = treebird.example.com) {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
server_name treebird.example.com;
|
||||
return 404;
|
||||
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
server_name treebird.example.com;
|
||||
return 404;
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
package account;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( account content_statuses generate_account_list generate_account_item status_interactions );
|
||||
|
||||
use template_helpers 'to_template';
|
||||
use l10n 'lang';
|
||||
use status 'generate_status';
|
||||
use string_helpers qw( simple_escape emojify random_error_kaomoji format_username );
|
||||
use navigation 'generate_navigation';
|
||||
|
||||
sub generate_account
|
||||
{
|
||||
my ($ssn, $data, $acct, $relationship, $content) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
lang => \&lang,
|
||||
relationship => $relationship,
|
||||
content => $content,
|
||||
acct => $acct,
|
||||
escape => \&simple_escape,
|
||||
emojify => \&emojify,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'account.tt'});
|
||||
}
|
||||
|
||||
sub content_statuses
|
||||
{
|
||||
my ($ssn, $data, $acct, $relationship, $statuses) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
acct => $acct,
|
||||
statuses => $statuses,
|
||||
create_status => sub { generate_status($ssn, $data, shift); },
|
||||
# Make subroutine so Perl doesn't autovivify
|
||||
nav => sub { generate_navigation($ssn, $data, $statuses->[0]->{id}, $statuses->[-1]->{id}) },
|
||||
random_error_kaomoji => \&random_error_kaomoji,
|
||||
);
|
||||
|
||||
generate_account($ssn, $data, $acct, $relationship, to_template(\%vars, \$data->{'account_statuses.tt'}));
|
||||
}
|
||||
|
||||
sub generate_account_item
|
||||
{
|
||||
my ($ssn, $data, $account) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
account => $account,
|
||||
# Functions
|
||||
icon => \&get_icon,
|
||||
lang => \&lang,
|
||||
format_username => \&format_username,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'account_item.tt'});
|
||||
}
|
||||
|
||||
sub generate_account_list
|
||||
{
|
||||
my ($ssn, $data, $accounts, $title) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
accounts => $accounts,
|
||||
title => $title,
|
||||
create_account => sub { generate_account_item($ssn, $data, shift); },
|
||||
nav => sub { generate_navigation($ssn, $data, $accounts->[0]->{id}, $accounts->[-1]->{id}) },
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'accounts.tt'});
|
||||
}
|
||||
|
||||
sub status_interactions
|
||||
{
|
||||
my ($ssn, $data, $accounts, $label) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
data => $data,
|
||||
accounts => $accounts,
|
||||
label => $label,
|
||||
# Functions
|
||||
create_account => sub { generate_account_item($ssn, $data, shift); },
|
||||
);
|
||||
|
||||
|
||||
to_template(\%vars, \$data->{'status_interactions.tt'});
|
||||
}
|
||||
|
||||
sub content_accounts
|
||||
{
|
||||
my ($ssn, $data, $acct, $relationship, $accounts, $title) = @_;
|
||||
|
||||
my $acct_list_page = generate_account_list($ssn, $data, $accounts, $title);
|
||||
|
||||
# Should we create a full accounts view?
|
||||
if ($acct)
|
||||
{
|
||||
generate_account($ssn, $data, $acct, $relationship, $acct_list_page);
|
||||
}
|
||||
else {
|
||||
return $acct_list_page;
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
|
@ -0,0 +1,26 @@
|
|||
package attachments;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( generate_attachment );
|
||||
|
||||
use template_helpers 'to_template';
|
||||
use icons 'get_icon';
|
||||
|
||||
sub generate_attachment
|
||||
{
|
||||
my ($ssn, $data, $att, $sensitive) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
attachment => $att,
|
||||
sensitive => $sensitive,
|
||||
icon => \&get_icon,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'attachment.tt'});
|
||||
}
|
||||
|
||||
1;
|
|
@ -0,0 +1,42 @@
|
|||
package chat;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORTS = qw( content_chats construct_chat );
|
||||
|
||||
use template_helpers 'to_template';
|
||||
use string_helpers qw( format_username emojify reltime_to_str );
|
||||
|
||||
sub construct_chat
|
||||
{
|
||||
my ($ssn, $data, $chat, $messages) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
chat => $chat,
|
||||
messages => $messages,
|
||||
format_username => \&format_username,
|
||||
emojify => \&emojify,
|
||||
reltime => \&reltime_to_str,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'chat.tt'});
|
||||
}
|
||||
|
||||
sub content_chats
|
||||
{
|
||||
my ($ssn, $data, $chats) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
chats => $chats,
|
||||
format_username => \&format_username,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'content_chats.tt'});
|
||||
}
|
||||
|
||||
1;
|
|
@ -0,0 +1,20 @@
|
|||
package config;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
our @EXPORT = qw( general appearance );
|
||||
use Exporter 'import';
|
||||
|
||||
use template_helpers 'simple_page';
|
||||
|
||||
sub general
|
||||
{
|
||||
simple_page @_, 'config_general.tt';
|
||||
}
|
||||
|
||||
sub appearance
|
||||
{
|
||||
simple_page @_, 'config_appearance.tt';
|
||||
}
|
||||
|
||||
1;
|
|
@ -0,0 +1,25 @@
|
|||
package embed;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( generate_embedded_page );
|
||||
|
||||
use template_helpers 'to_template';
|
||||
|
||||
sub generate_embedded_page
|
||||
{
|
||||
my ($ssn, $data, $content) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
content => $content,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'embed.tt'});
|
||||
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package emojis;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( generate_emoji );
|
||||
|
||||
use template_helpers 'to_template';
|
||||
|
||||
sub generate_emoji
|
||||
{
|
||||
my ($ssn, $data, $status_id, $emoji) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
status_id => $status_id,
|
||||
emoji => $emoji
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'emoji.tt'});
|
||||
}
|
||||
|
||||
sub emoji_picker
|
||||
{
|
||||
my ($data, $emojis) = @_;
|
||||
|
||||
my %vars = (
|
||||
e => $emojis
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'emoji_picker.tt'});
|
||||
}
|
||||
|
||||
1;
|
|
@ -0,0 +1,75 @@
|
|||
package icons;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Scalar::Util 'looks_like_number';
|
||||
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( &get_icon &get_icon_svg &get_icon_png &visibility_to_icon );
|
||||
|
||||
sub get_icon
|
||||
{
|
||||
my ($ico, $is_png) = @_;
|
||||
$is_png ||= 0;
|
||||
|
||||
$is_png ? get_icon_png($ico) : get_icon_svg($ico);
|
||||
}
|
||||
|
||||
sub get_icon_svg
|
||||
{
|
||||
my %res = (
|
||||
repeat => '<svg class="repeat" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 2.1l4 4-4 4"/><path d="M3 12.2v-2a4 4 0 0 1 4-4h12.8M7 21.9l-4-4 4-4"/><path d="M21 11.8v2a4 4 0 0 1-4 4H4.2"/></svg>',
|
||||
|
||||
like => '<svg class="like" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon></svg>',
|
||||
|
||||
expand => '<svg class="expand" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6M14 10l6.1-6.1M9 21H3v-6M10 14l-6.1 6.1"/></svg>',
|
||||
|
||||
reply => '<svg class="reply" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 9l6 6-6 6"/><path d="M4 4v7a4 4 0 0 0 4 4h11"/></svg>',
|
||||
|
||||
emoji => '<svg class="emoji-btn" width="20" height="20" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10"/><g><line x1="9" x2="9" y1="6.9367" y2="11.755" stroke-width="1.7916"/><line x1="15" x2="15" y1="6.9367" y2="11.755" stroke-width="1.7916"/><path d="m7.0891 15.099s4.7206 4.7543 9.7109 0" stroke-linecap="round" stroke-linejoin="miter" stroke-width="1.9764"/></g></svg>',
|
||||
|
||||
likeboost => '<svg class="one-click-software" width="20" height="20" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g><g stroke-width="1.98"><path d="m19.15 8.5061 2.7598 2.7598-2.7598 2.7598"/><path d="m14.756 11.325s2.5484-0.05032 6.3258 0.01026m-15.639 10.807-2.7598-2.7598 2.7598-2.7598"/><path d="m22.4 15.327v1.2259c0 1.156-1.2356 2.7598-2.7598 2.7598h-16.664"/></g><polygon transform="matrix(.60736 0 0 .60736 .60106 .63577)" points="18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2 15.09 8.26 22 9.27 17 14.14" stroke-width="2.9656"/></g></svg>',
|
||||
|
||||
fileclip => '<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></svg>',
|
||||
|
||||
'local' => '<svg class="visibility vis-local" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 9v11a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V9"/><path d="M9 22V12h6v10M2 10.6L12 2l10 8.6"/></svg>',
|
||||
|
||||
direct => '<svg class="visibility vis-direct xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>',
|
||||
|
||||
private => '<svg class="visibility vis-private" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>',
|
||||
|
||||
list => '<svg class="visibility vis-list" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"></line><line x1="8" y1="12" x2="21" y2="12"></line><line x1="8" y1="18" x2="21" y2="18"></line><line x1="3" y1="6" x2="3.01" y2="6"></line><line x1="3" y1="12" x2="3.01" y2="12"></line><line x1="3" y1="18" x2="3.01" y2="18"></line></svg>',
|
||||
|
||||
unlisted => '<svg class="visibility vis-unlisted" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 9.9-1"></path></svg>',
|
||||
|
||||
public => '<svg class="visibility vis-public" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>',
|
||||
|
||||
follow => '<svg class="follow" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="8.5" cy="7" r="4"></circle><line x1="20" y1="8" x2="20" y2="14"></line><line x1="23" y1="11" x2="17" y2="11"></line></svg>',
|
||||
|
||||
search => '<svg class="search" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
|
||||
|
||||
'search-menu' => '<svg class="search-menu" width="20" height="20" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="10.107" cy="8.7642" r="6.7391" stroke-width="1.6848"/><line x1="18.531" x2="14.866" y1="17.188" y2="13.524" stroke-width="1.6848"/><path d="m14.756 20.738 1.9607 1.9607 1.9091-1.9297" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.6554"/></svg>',
|
||||
);
|
||||
|
||||
$res{$_[0]};
|
||||
}
|
||||
|
||||
sub visibility_to_icon
|
||||
{
|
||||
# I thought of an array, but I don't want to call get_icon UNLESS
|
||||
# we know the visibility
|
||||
return unless looks_like_number($_[0]);
|
||||
my $vis = $_[0];
|
||||
|
||||
return get_icon('public') if $vis == 1;
|
||||
return get_icon('unlisted') if $vis == 2;
|
||||
return get_icon('private') if $vis == 3;
|
||||
return get_icon('list') if $vis == 4;
|
||||
return get_icon('direct') if $vis == 5;
|
||||
return get_icon('local') if $vis == 6;
|
||||
|
||||
# Assume local for anything else, because well... I'm not sure
|
||||
get_icon('local');
|
||||
}
|
||||
|
||||
1;
|
|
@ -0,0 +1,96 @@
|
|||
package l10n;
|
||||
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( &lang %L10N );
|
||||
|
||||
our %L10N = (
|
||||
EN_US => {
|
||||
APP_NAME => 'Treebird',
|
||||
HOME => 'Home',
|
||||
LOCAL => 'Local',
|
||||
FEDERATED => 'Federated',
|
||||
NOTIFICATIONS => 'Notifications',
|
||||
LISTS => 'Lists',
|
||||
FAVOURITES => 'Favorites',
|
||||
BOOKMARKS => 'Bookmarks',
|
||||
DIRECT => 'Direct',
|
||||
CONFIG => 'Config',
|
||||
SEARCH_PLACEHOLDER => 'Search',
|
||||
SEARCH_BUTTON => 'Search',
|
||||
GENERAL => 'General',
|
||||
ACCOUNT => 'Account',
|
||||
JAVASCRIPT => 'JavaScript',
|
||||
CFG_QUICK_ACTIONS => 'Quick actions - Likes, Boosts, etc done in the background',
|
||||
CFG_QUICK_REPLY => 'Quick reply - Replies don\'t require redirects',
|
||||
LIVE_STATUSES => 'Live statuses - Statuses fetch on the fly',
|
||||
APPEARANCE => 'Appearance',
|
||||
VARIANT => 'Variant',
|
||||
THEME_TREEBIRD20 => 'Treebird - Default, simple theme',
|
||||
THEME_TREEBIRD30 => 'Treebird 3.0 - Flat, modern theme',
|
||||
COLOR_SCHEME => 'Color Scheme',
|
||||
LIGHT => 'Light',
|
||||
DARK => 'Dark',
|
||||
SAVE => 'Save',
|
||||
ACCT_MENU => 'Menu',
|
||||
SUBSCRIBE => 'Subscribe',
|
||||
UNSUBSCRIBE => 'Unsubscribe',
|
||||
BLOCK => 'Block',
|
||||
UNBLOCK => 'Unblock',
|
||||
MUTE => 'Mute',
|
||||
UNMUTE => 'Unmute',
|
||||
TAB_STATUSES => 'Statuses',
|
||||
TAB_FOLLOWING => 'Following',
|
||||
TAB_FOLLOWERS => 'Followers',
|
||||
TAB_SCROBBLES => 'Scrobbles',
|
||||
TAB_MEDIA => 'Media',
|
||||
TAB_PINNED => 'Pinned',
|
||||
FOLLOWS_YOU => 'Follows you!',
|
||||
FOLLOW => 'Follow',
|
||||
FOLLOW_PENDING => 'Follow pending',
|
||||
FOLLOWING => 'Following!',
|
||||
BLOCKED => 'You are blocked by this user.',
|
||||
REPLY => 'Reply',
|
||||
REPEAT => 'Repeat',
|
||||
LIKE => 'Like',
|
||||
QUICK => 'Quick',
|
||||
VIEW => 'View',
|
||||
IN_REPLY_TO => 'In reply to',
|
||||
PAGE_NOT_FOUND => 'Content not found',
|
||||
STATUS_NOT_FOUND => 'Status not found',
|
||||
ACCOUNT_NOT_FOUND => 'Account not found',
|
||||
VIS_PUBLIC => 'Public',
|
||||
VIS_UNLISTED => 'Unlisted',
|
||||
VIS_PRIVATE => 'Private',
|
||||
VIS_DIRECT => 'Direct',
|
||||
VIS_LOCAL => 'Local',
|
||||
VIS_LIST => 'List',
|
||||
LOGIN => 'Login',
|
||||
REGISTER => 'Register',
|
||||
USERNAME => 'Username',
|
||||
PASSWORD => 'Password',
|
||||
LOGIN_BTN => 'Login',
|
||||
LOGIN_HEADER => 'Login / Register',
|
||||
LOGIN_FAIL => 'Couldn\'t login',
|
||||
NOTIF_LIKED => 'liked your status',
|
||||
NOTIF_REACTED_WITH => 'reacted with',
|
||||
NOTIF_REPEATED => 'repeated your status',
|
||||
NOTIF_FOLLOW => 'followed you',
|
||||
NOTIF_FOLLOW_REQUEST => 'wants to follow you',
|
||||
NOTIF_POLL => 'poll results',
|
||||
NOTIF_COMPACT_LIKED => 'liked',
|
||||
NOTIF_COMPACT_REACTED_WITH => 'reacted',
|
||||
NOTIF_COMPACT_REPEATED => 'repeated',
|
||||
NOTIF_COMPACT_FOLLOW => 'followed',
|
||||
COMPACT_FOLLOW_REQUEST => 'wants to follow',
|
||||
NOTIF_COMPACT_POLL => 'poll',
|
||||
},
|
||||
# TODO bring over Spanish and Chinese
|
||||
);
|
||||
|
||||
sub lang
|
||||
{
|
||||
$L10N{'EN_US'}->{shift(@_)}
|
||||
}
|
||||
|
||||
return 1;
|
|
@ -0,0 +1,19 @@
|
|||
package lists;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORTS = qw( content_lists );
|
||||
|
||||
use template_helpers 'to_template';
|
||||
|
||||
sub content_lists
|
||||
{
|
||||
my ($ssn, $data, $lists) = @_;
|
||||
|
||||
my %vars = (
|
||||
lists => $lists
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'content_lists.tt'});
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package login;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( content_login );
|
||||
|
||||
use l10n 'lang';
|
||||
use template_helpers 'to_template';
|
||||
|
||||
sub content_login
|
||||
{
|
||||
my ($ssn, $data, $error) = @_;
|
||||
|
||||
my %vars = (
|
||||
error => $error,
|
||||
lang => \&lang,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'login.tt'});
|
||||
}
|
||||
|
||||
1;
|
|
@ -0,0 +1,59 @@
|
|||
use strict;
|
||||
use warnings;
|
||||
# Modules
|
||||
use Template;
|
||||
use l10n qw( &lang %L10N );
|
||||
use notifications qw( generate_notification content_notifications );
|
||||
use template_helpers qw( &to_template );
|
||||
use timeline;
|
||||
use icons 'get_icon';
|
||||
use status;
|
||||
use account;
|
||||
use lists;
|
||||
use search;
|
||||
use chat;
|
||||
use config;
|
||||
use embed;
|
||||
use meta;
|
||||
use login;
|
||||
|
||||
# use Devel::Leak;
|
||||
|
||||
# my $handle;
|
||||
# Devel::Leak::NoteSV($handle);
|
||||
|
||||
# sub leaky_friend
|
||||
# {
|
||||
# $count = Devel::Leak::CheckSV($handle);
|
||||
# my $leakstr = "Memory: $count SVs\n";
|
||||
# print STDERR $leakstr;
|
||||
# }
|
||||
|
||||
sub base_page
|
||||
{
|
||||
my ($ssn,
|
||||
$data,
|
||||
$main,
|
||||
$notifs) = @_;
|
||||
my $result;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
title => $L10N{'EN_US'}->{'APP_NAME'},
|
||||
lang => \&lang,
|
||||
main => $main,
|
||||
icon => \&get_icon,
|
||||
sidebar_opacity => $ssn->{config}->{sidebar_opacity} / 255,
|
||||
acct => $ssn->{account},
|
||||
data => $data,
|
||||
notifs => $notifs,
|
||||
notification => \&generate_notification,
|
||||
);
|
||||
|
||||
my $ret = to_template(\%vars, \$data->{'main.tt'});
|
||||
|
||||
# leaky_friend();
|
||||
|
||||
return $ret;
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package meta;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
our @EXPORT = qw( );
|
||||
|
||||
use Exporter 'import';
|
||||
|
||||
use template_helpers qw( simple_page to_template );
|
||||
|
||||
sub about
|
||||
{
|
||||
simple_page @_, 'about.tt';
|
||||
}
|
||||
|
||||
sub license
|
||||
{
|
||||
simple_page @_, 'license.tt';
|
||||
}
|
||||
|
||||
sub memory
|
||||
{
|
||||
my ($ssn, $data, $mem_alloc_est, $mem_alloc, $mem_free, $mem_time_alloc, $mem_time_freed) = @_;
|
||||
|
||||
my %vars = (
|
||||
ssn => $ssn,
|
||||
mem_alloc_est => $mem_alloc_est,
|
||||
mem_alloc => $mem_alloc,
|
||||
mem_free => $mem_free,
|
||||
mem_time_alloc => $mem_time_alloc,
|
||||
mem_time_freed => $mem_time_freed,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'memory.tt'});
|
||||
}
|
||||
|
||||
1;
|
|
@ -0,0 +1,25 @@
|
|||
package navigation;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( generate_navigation );
|
||||
|
||||
use template_helpers 'to_template';
|
||||
use l10n 'lang';
|
||||
|
||||
sub generate_navigation
|
||||
{
|
||||
my ($ssn, $data, $first_id, $last_id) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
lang => \&lang,
|
||||
start_id => $ssn->{post}->{start_id} || $first_id,
|
||||
prev_id => $first_id,
|
||||
next_id => $last_id,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'navigation.tt'});
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package notifications;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( generate_notification content_notifications embed_notifications );
|
||||
|
||||
use template_helpers 'to_template';
|
||||
use status 'generate_status';
|
||||
use string_helpers qw( random_error_kaomoji );
|
||||
use icons 'get_icon';
|
||||
use embed 'generate_embedded_page';
|
||||
use navigation 'generate_navigation';
|
||||
|
||||
sub generate_notification
|
||||
{
|
||||
my ($ssn, $data, $notif, $is_compact) = @_;
|
||||
|
||||
$is_compact ||= 0;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
notif => $notif,
|
||||
compact => $is_compact,
|
||||
create_status => sub { generate_status($ssn, $data, shift, shift, $is_compact); },
|
||||
icon => \&get_icon,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'notification.tt'});
|
||||
}
|
||||
|
||||
sub content_notifications
|
||||
{
|
||||
my ($ssn, $data, $notifs) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
notifs => $notifs,
|
||||
notification => sub { generate_notification($ssn, $data, shift); },
|
||||
random_error_kaomoji => \&random_error_kaomoji,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'content_notifs.tt'});
|
||||
}
|
||||
|
||||
sub embed_notifications
|
||||
{
|
||||
my ($ssn, $data, $notifs) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
notifs => $notifs,
|
||||
notification => sub { generate_notification($ssn, $data, shift, 1); },
|
||||
nav => sub { generate_navigation($ssn, $data, $notifs->[0]->{id}, $notifs->[-1]->{id}) },
|
||||
);
|
||||
|
||||
generate_embedded_page($ssn, $data, to_template(\%vars, \$data->{'notifs_embed.tt'}));
|
||||
}
|
||||
|
||||
1;
|
|
@ -0,0 +1,26 @@
|
|||
package postbox;
|
||||
use strict;
|
||||
use warnings;
|
||||
use template_helpers 'to_template';
|
||||
use string_helpers qw( get_mentions_from_content );
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( generate_postbox );
|
||||
|
||||
use icons 'get_icon';
|
||||
|
||||
sub generate_postbox
|
||||
{
|
||||
my ($ssn, $data, $status) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
data => $data,
|
||||
status => $status,
|
||||
icon => \&get_icon,
|
||||
mentionify => \&get_mentions_from_content,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'postbox.tt'});
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package search;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORTS = qw( content_search content_search_tags content_search_accounts content_search_statuses search_tags search_accounts search_statuses );
|
||||
|
||||
use template_helpers 'to_template';
|
||||
use status 'generate_status';
|
||||
use account 'generate_account_item';
|
||||
|
||||
use constant
|
||||
{
|
||||
SEARCH_CAT_STATUSES => 0,
|
||||
SEARCH_CAT_ACCOUNTS => 1,
|
||||
SEARCH_CAT_TAGS => 2
|
||||
};
|
||||
|
||||
sub search_page
|
||||
{
|
||||
my ($ssn, $data, $tab, $content) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
tab => $tab,
|
||||
content => $content,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'search.tt'});
|
||||
}
|
||||
|
||||
# CONTENT
|
||||
sub search_accounts
|
||||
{
|
||||
my ($ssn, $data, $search) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
search => $search,
|
||||
create_account => sub { generate_account_item($ssn, $data, shift); },
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'search_accounts.tt'})
|
||||
}
|
||||
|
||||
sub search_statuses
|
||||
{
|
||||
my ($ssn, $data, $search) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
search => $search,
|
||||
create_status => sub { generate_status($ssn, $data, shift); },
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'search_statuses.tt'})
|
||||
}
|
||||
|
||||
sub search_tags
|
||||
{
|
||||
my ($ssn, $data, $search) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
search => $search,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'search_tags.tt'})
|
||||
}
|
||||
|
||||
sub content_search_accounts
|
||||
{
|
||||
search_page($_[0], $_[1], SEARCH_CAT_ACCOUNTS, search_accounts(@_));
|
||||
}
|
||||
|
||||
sub content_search_statuses
|
||||
{
|
||||
search_page($_[0], $_[1], SEARCH_CAT_STATUSES, search_statuses(@_));
|
||||
}
|
||||
|
||||
sub content_search_tags
|
||||
{
|
||||
search_page($_[0], $_[1], SEARCH_CAT_TAGS, search_tags(@_));
|
||||
}
|
||||
|
||||
sub content_search
|
||||
{
|
||||
my ($ssn, $data, $search) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
search => $search,
|
||||
|
||||
statuses => search_statuses(@_),
|
||||
accounts => search_accounts(@_),
|
||||
hashtags => search_tags(@_),
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'content_search.tt'});
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
package status;
|
||||
use strict;
|
||||
use warnings;
|
||||
use string_helpers qw( reltime_to_str greentextify emojify format_username localize_mentions simple_escape );
|
||||
use icons qw( get_icon visibility_to_icon );
|
||||
use attachments 'generate_attachment';
|
||||
use postbox 'generate_postbox';
|
||||
use emojis 'generate_emoji';
|
||||
use Exporter 'import';
|
||||
use l10n 'lang';
|
||||
|
||||
our @EXPORT = qw( content_status generate_status );
|
||||
|
||||
use template_helpers 'to_template';
|
||||
|
||||
# Useful variable to prevent collisions
|
||||
my $rel_context = 0;
|
||||
|
||||
sub generate_status
|
||||
{
|
||||
my ($ssn, $data, $status, $notif, $is_compact, $picker) = @_;
|
||||
my $boost_acct;
|
||||
|
||||
# Move status reference for boosts and keep account
|
||||
# I hate this design but blame MastoAPI, not me.
|
||||
if ($status->{reblog})
|
||||
{
|
||||
$boost_acct = $status->{account};
|
||||
$status = $status->{reblog};
|
||||
}
|
||||
|
||||
my $is_statusey_notif = ($notif && ($notif->{type} eq 'mention' || $notif->{type} eq 'status'));
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
status => $status,
|
||||
boost => $boost_acct, # May be undef
|
||||
data => $data,
|
||||
emoji_picker => $picker,
|
||||
notif => $notif, # May be undef
|
||||
compact => $is_compact, # May be undef
|
||||
is_statusey_notif => $is_statusey_notif,
|
||||
unique_toggle_id => $rel_context++,
|
||||
interacted_with => $boost_acct || ($notif && !$is_statusey_notif),
|
||||
|
||||
# Functions
|
||||
|
||||
action_to_string => sub {
|
||||
return lang('NOTIF_LIKED') if $notif && $notif->{type} eq 'favourite';
|
||||
return lang('NOTIF_REPEATED') if $boost_acct || $notif->{type} eq 'reblog';
|
||||
return lang('NOTIF_REACTED_WITH') .' '. $notif->{emoji} if $notif->{type} eq 'emoji reaction';
|
||||
},
|
||||
|
||||
action_to_icon => sub {
|
||||
return get_icon('like') if $notif && $notif->{type} eq 'favourite';
|
||||
return get_icon('repeat') if $boost_acct || $notif->{type} eq 'reblog';
|
||||
return $notif->{emoji} if $notif && $notif->{type} eq 'emoji reaction';
|
||||
},
|
||||
|
||||
icon => \&get_icon,
|
||||
lang => \&lang,
|
||||
rel_to_str => \&reltime_to_str,
|
||||
vis_to_icon => \&visibility_to_icon,
|
||||
make_att => \&generate_attachment,
|
||||
make_emoji => \&generate_emoji,
|
||||
greentextify => \&greentextify,
|
||||
emojify => \&emojify,
|
||||
escape => \&simple_escape,
|
||||
fix_mentions => \&localize_mentions,
|
||||
format_username => \&format_username,
|
||||
make_postbox => \&generate_postbox,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'status.tt'});
|
||||
}
|
||||
|
||||
sub content_status
|
||||
{
|
||||
my ($ssn, $data, $status, $statuses_before, $statuses_after, $picker) = @_;
|
||||
|
||||
$rel_context = 0;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
status => $status,
|
||||
picker => $picker,
|
||||
statuses_before => $statuses_before,
|
||||
statuses_after => $statuses_after,
|
||||
# Functions
|
||||
create_status => sub { generate_status($ssn, $data, shift, 0, 0, shift) },
|
||||
);
|
||||
|
||||
|
||||
to_template(\%vars, \$data->{'content_status.tt'});
|
||||
}
|
||||
|
||||
1;
|
|
@ -0,0 +1,103 @@
|
|||
package string_helpers;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter 'import';
|
||||
use Scalar::Util 'looks_like_number';
|
||||
|
||||
our @EXPORT = qw( reltime_to_str greentextify emojify format_username get_mentions_from_content localize_mentions simple_escape random_error_kaomoji );
|
||||
|
||||
my $re_mentions = '(?=<a .*?mention.*?)<a .*?href="https?:\/\/(.*?)\/(?:@|users\/|\/u)?(.*?)?".*?>';
|
||||
|
||||
sub reltime_to_str
|
||||
{
|
||||
return unless looks_like_number($_[0]);
|
||||
my $since = time() - $_[0];
|
||||
|
||||
return $since . 's' if $since < 60;
|
||||
return int($since / 60) . 'm' if $since < 60 * 60;
|
||||
return int($since / (60 * 60)) . 'h' if $since < 60 * 60 * 24;
|
||||
return int($since / (60 * 60 * 24)) . 'd' if $since < 60 * 60 * 24 * 31;
|
||||
return int($since / (60 * 60 * 24 * 31)) . 'mon' if $since < 60 * 60 * 24 * 365;
|
||||
return int($since / (60 * 60 * 24 * 365)) . 'yr';
|
||||
}
|
||||
|
||||
sub simple_escape
|
||||
{
|
||||
my $text = shift;
|
||||
$text =~ s/&/&/gs;
|
||||
$text =~ s/</</gs;
|
||||
$text =~ s/>/>/gs;
|
||||
$text =~ s/"/"/gs;
|
||||
$text;
|
||||
}
|
||||
|
||||
sub greentextify
|
||||
{
|
||||
my $text = shift;
|
||||
$text =~ s/(>.*?)(?=<|$)/<span class="greentext">$1<\/span>/gs;
|
||||
$text =~ s/(<.*?)(?=<|$)/<span class="bluetext">$1<\/span>/gs;
|
||||
$text =~ s/(?:^|>| )(\^.*?)(?=<|$)/<span class="yellowtext">$1<\/span>/gs;
|
||||
$text;
|
||||
}
|
||||
|
||||
sub emojify
|
||||
{
|
||||
my ($text, $emojis) = @_;
|
||||
if ($emojis)
|
||||
{
|
||||
foreach my $emoji (@{$emojis})
|
||||
{
|
||||
my $emo = $emoji->{shortcode};
|
||||
my $url = $emoji->{url};
|
||||
$text =~ s/:$emo:/<img class="emoji" src="$url" loading="lazy">/gsi;
|
||||
}
|
||||
}
|
||||
$text;
|
||||
}
|
||||
|
||||
sub format_username
|
||||
{
|
||||
my $account = shift;
|
||||
return unless $account;
|
||||
|
||||
#TODO ESCAPE DISPLAY NAME
|
||||
emojify(simple_escape($account->{display_name}), $account->{emojis});
|
||||
}
|
||||
|
||||
sub localize_mentions
|
||||
{
|
||||
my $text = shift;
|
||||
# idk how to work around this
|
||||
my $at = '@';
|
||||
|
||||
$text =~ s/$re_mentions/<a target="_parent" class="mention" href="\/$at$2$at$1">/gs;
|
||||
$text;
|
||||
}
|
||||
|
||||
sub get_mentions_from_content
|
||||
{
|
||||
my ($ssn, $status) = @_;
|
||||
my $result = '';
|
||||
my $acct;
|
||||
while ($status->{'content'} =~
|
||||
/<a .*?href=\"https?:\/\/(.*?)\/(?:@|users\/|u\/)?(.*?)?\".*?>@(?:<span>)?.*?(?:<\/span>)?/gs)
|
||||
{
|
||||
$acct = $2 . '@' . $1;
|
||||
# TODO this does not account for the domain (alt interference)
|
||||
$result .= '@' . $acct unless $ssn->{account}->{acct} eq $2;
|
||||
}
|
||||
($status->{account}->{acct} eq $ssn->{account}->{acct})
|
||||
? $result : '@' . $status->{account}->{acct} . ' ' . $result;
|
||||
}
|
||||
|
||||
sub random_error_kaomoji
|
||||
{
|
||||
my @messages = (
|
||||
"(; ̄Д ̄)",
|
||||
"(`Δ´)!",
|
||||
"¯\\_(ツ)_/¯",
|
||||
"(ノ´・ω・)ノ ミ ┸━┸",
|
||||
"(╯°□°)╯︵ ┻━┻",
|
||||
);
|
||||
@messages[rand(scalar @messages)];
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package template_helpers;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( to_template simple_page );
|
||||
|
||||
use string_helpers 'simple_escape';
|
||||
|
||||
my $template = Template->new(
|
||||
{
|
||||
INTERPOLATE => 1,
|
||||
POST_CHOMP => 1,
|
||||
EVAL_PERL => 1,
|
||||
TRIM => 1
|
||||
});
|
||||
|
||||
sub pretty_error($)
|
||||
{
|
||||
my $error = simple_escape(shift);
|
||||
<< "END_ERROR";
|
||||
<span class="e-error error-pad">
|
||||
$error
|
||||
</span>
|
||||
END_ERROR
|
||||
}
|
||||
|
||||
sub to_template
|
||||
{
|
||||
my ($vars, $data) = @_;
|
||||
my $result;
|
||||
|
||||
return 0 unless ref $data;
|
||||
return 0 unless ref $vars;
|
||||
|
||||
# TODO HTML error formatting
|
||||
$template->process($data, $vars, \$result) ||
|
||||
return pretty_error($template->error());
|
||||
|
||||
$result;
|
||||
}
|
||||
|
||||
# Generic simple page with only session data and pages.
|
||||
# Pretty commonly done, so useful function.
|
||||
sub simple_page
|
||||
{
|
||||
my ($ssn, $data, $page) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{$page});
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package timeline;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( content_timeline );
|
||||
|
||||
use template_helpers 'to_template';
|
||||
use icons 'get_icon';
|
||||
use postbox 'generate_postbox';
|
||||
use status 'generate_status';
|
||||
use navigation 'generate_navigation';
|
||||
|
||||
sub content_timeline
|
||||
{
|
||||
my ($ssn, $data, $statuses, $title, $show_post_box, $fake_timeline) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
data => $data,
|
||||
statuses => $statuses,
|
||||
title => $title,
|
||||
fake_timeline => $fake_timeline,
|
||||
show_post_box => $show_post_box,
|
||||
postbox => \&generate_postbox,
|
||||
create_status => sub { generate_status($ssn, $data, shift); },
|
||||
# Don't autovivify statuses
|
||||
nav => sub { generate_navigation($ssn, $data, $statuses->[0]->{id}, $statuses->[-1]->{id}) },
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'timeline.tt'});
|
||||
}
|
|
@ -111,10 +111,11 @@ int main(int argc, char** argv)
|
|||
printf("#define EMOJO_CAT_SYMBOLS %d\n", i);
|
||||
}
|
||||
else if (strcmp(group->valuestring, "Flags") == 0 && cat == 6) {
|
||||
cat = 7;
|
||||
printf("#define EMOJO_CAT_FLAGS %d\n", i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
printf("#define EMOJO_CAT_MAX %d\n", i);
|
||||
|
||||
printf("static struct emoji_info emojos[] = {");
|
||||
i = 0;
|
||||
|
|
41
src/about.c
|
@ -16,36 +16,51 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "base_page.h"
|
||||
#include "about.h"
|
||||
|
||||
#include "../static/about.chtml"
|
||||
#include "../static/license.chtml"
|
||||
|
||||
void content_about(struct session* ssn, mastodont_t* api, char** data)
|
||||
void content_about(PATH_ARGS)
|
||||
{
|
||||
PERL_STACK_INIT;
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
mXPUSHs(newRV_inc((SV*)session_hv));
|
||||
mXPUSHs(newRV_inc((SV*)template_files));
|
||||
|
||||
PERL_STACK_SCALAR_CALL("meta::about");
|
||||
char* dup = PERL_GET_STACK_EXIT;
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_NONE,
|
||||
.locale = L10N_EN_US,
|
||||
.content = data_about_html,
|
||||
.content = dup,
|
||||
.session = session_hv,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
// Output
|
||||
render_base_page(&b, ssn, api);
|
||||
render_base_page(&b, req, ssn, api);
|
||||
tb_free(dup);
|
||||
}
|
||||
|
||||
|
||||
void content_about_license(struct session* ssn, mastodont_t* api, char** data)
|
||||
void content_about_license(PATH_ARGS)
|
||||
{
|
||||
PERL_STACK_INIT;
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
XPUSHs(newRV_noinc((SV*)session_hv));
|
||||
XPUSHs(newRV_noinc((SV*)template_files));
|
||||
|
||||
PERL_STACK_SCALAR_CALL("meta::license");
|
||||
|
||||
char* dup = PERL_GET_STACK_EXIT;
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_NONE,
|
||||
.locale = L10N_EN_US,
|
||||
.content = data_license_html,
|
||||
.content = dup,
|
||||
.session = session_hv,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
// Output
|
||||
render_base_page(&b, ssn, api);
|
||||
render_base_page(&b, req, ssn, api);
|
||||
tb_free(dup);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,8 +20,9 @@
|
|||
#define ABOUT_H
|
||||
#include <mastodont.h>
|
||||
#include "session.h"
|
||||
#include "path.h"
|
||||
|
||||
void content_about_license(struct session* ssn, mastodont_t* api, char** data);
|
||||
void content_about(struct session* ssn, mastodont_t* api, char** data);
|
||||
void content_about_license(PATH_ARGS);
|
||||
void content_about(PATH_ARGS);
|
||||
|
||||
#endif /* ABOUT_H */
|
||||
|
|
779
src/account.c
|
@ -18,6 +18,8 @@
|
|||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "global_perl.h"
|
||||
#include "helpers.h"
|
||||
#include "base_page.h"
|
||||
#include "error.h"
|
||||
#include "../config.h"
|
||||
|
@ -25,19 +27,12 @@
|
|||
#include "easprintf.h"
|
||||
#include "status.h"
|
||||
#include "http.h"
|
||||
#include "string.h"
|
||||
#include "base_page.h"
|
||||
#include "scrobble.h"
|
||||
#include "string_helpers.h"
|
||||
#include "navigation.h"
|
||||
|
||||
// Files
|
||||
#include "../static/account.chtml"
|
||||
#include "../static/account_info.chtml"
|
||||
#include "../static/account_follow_btn.chtml"
|
||||
#include "../static/favourites_page.chtml"
|
||||
#include "../static/bookmarks_page.chtml"
|
||||
#include "../static/account_stub.chtml"
|
||||
#include "../static/account_sidebar.chtml"
|
||||
#include "emoji.h"
|
||||
#include "timeline.h"
|
||||
|
||||
#define FOLLOWS_YOU_HTML "<span class=\"acct-badge\">%s</span>"
|
||||
|
||||
|
@ -48,92 +43,154 @@ struct account_args
|
|||
uint8_t flags;
|
||||
};
|
||||
|
||||
char* load_account_info(struct mstdnt_account* acct,
|
||||
size_t* size)
|
||||
static char* accounts_page(HV* session_hv,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_account* acct,
|
||||
struct mstdnt_relationship* rel,
|
||||
char* header,
|
||||
struct mstdnt_storage* storage,
|
||||
struct mstdnt_account* accts,
|
||||
size_t accts_len)
|
||||
{
|
||||
char* acct_info_html;
|
||||
size_t s;
|
||||
|
||||
s = easprintf(&acct_info_html, data_account_info_html,
|
||||
acct->note);
|
||||
|
||||
if (size) *size = s;
|
||||
return acct_info_html;
|
||||
}
|
||||
|
||||
char* construct_account_sidebar(struct mstdnt_account* acct, size_t* size)
|
||||
{
|
||||
char* acct_sidebar_html;
|
||||
size_t s;
|
||||
|
||||
s = easprintf(&acct_sidebar_html, data_account_sidebar_html,
|
||||
acct->avatar,
|
||||
acct->display_name,
|
||||
L10N[L10N_EN_US][L10N_TAB_STATUSES],
|
||||
acct->statuses_count,
|
||||
L10N[L10N_EN_US][L10N_TAB_FOLLOWING],
|
||||
acct->following_count,
|
||||
L10N[L10N_EN_US][L10N_TAB_FOLLOWERS],
|
||||
acct->followers_count);
|
||||
|
||||
if (size) *size = s;
|
||||
return acct_sidebar_html;
|
||||
}
|
||||
|
||||
static char* account_statuses_cb(struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_account* acct,
|
||||
void* _args)
|
||||
|
||||
{
|
||||
(void)ssn;
|
||||
struct mstdnt_account_statuses_args* args = _args;
|
||||
char* statuses_html = NULL, *navigation_box = NULL;
|
||||
char* output;
|
||||
struct mstdnt_storage storage;
|
||||
struct mstdnt_status* statuses = NULL;
|
||||
size_t statuses_len = 0;
|
||||
char* start_id;
|
||||
|
||||
PERL_STACK_INIT;
|
||||
XPUSHs(newRV_noinc((SV*)session_hv));
|
||||
XPUSHs(newRV_noinc((SV*)template_files));
|
||||
if (acct)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_account(acct)));
|
||||
else ARG_UNDEFINED();
|
||||
if (rel)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_relationship(rel)));
|
||||
else ARG_UNDEFINED();
|
||||
|
||||
if (mastodont_get_account_statuses(api, acct->id, args, &storage, &statuses, &statuses_len))
|
||||
{
|
||||
statuses_html = construct_error(storage.error, E_ERROR, 1, NULL);
|
||||
}
|
||||
else {
|
||||
statuses_html = construct_statuses(ssn, api, statuses, statuses_len, NULL, NULL);
|
||||
if (!statuses_html)
|
||||
statuses_html = construct_error("No statuses", E_NOTICE, 1, NULL);
|
||||
}
|
||||
if (accts && accts_len)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_accounts(accts, accts_len)));
|
||||
else ARG_UNDEFINED();
|
||||
|
||||
if (statuses)
|
||||
{
|
||||
// If not set, set it
|
||||
start_id = keystr(ssn->post.start_id) ? keystr(ssn->post.start_id) : statuses[0].id;
|
||||
navigation_box = construct_navigation_box(start_id,
|
||||
statuses[0].id,
|
||||
statuses[statuses_len-1].id,
|
||||
NULL);
|
||||
}
|
||||
easprintf(&output, "%s%s",
|
||||
STR_NULL_EMPTY(statuses_html),
|
||||
STR_NULL_EMPTY(navigation_box));
|
||||
// perlapi doesn't specify if a string length of 0 calls strlen so calling just to be safe...
|
||||
if (header)
|
||||
mXPUSHp(header, strlen(header));
|
||||
|
||||
mastodont_storage_cleanup(&storage);
|
||||
mstdnt_cleanup_statuses(statuses, statuses_len);
|
||||
if (statuses_html) free(statuses_html);
|
||||
if (navigation_box) free(navigation_box);
|
||||
PERL_STACK_SCALAR_CALL("account::content_accounts");
|
||||
|
||||
output = PERL_GET_STACK_EXIT;
|
||||
|
||||
mstdnt_storage_cleanup(storage);
|
||||
mstdnt_cleanup_accounts(accts, accts_len);
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
static char* account_scrobbles_cb(struct session* ssn, mastodont_t* api, struct mstdnt_account* acct, void* _args)
|
||||
static char* account_followers_cb(HV* session_hv,
|
||||
struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_account* acct,
|
||||
struct mstdnt_relationship* rel,
|
||||
void* _args)
|
||||
{
|
||||
(void)ssn;
|
||||
(void)_args;
|
||||
char* scrobbles_html = NULL;
|
||||
struct mstdnt_storage storage;
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
struct mstdnt_account* accounts = NULL;
|
||||
size_t accts_len = 0;
|
||||
char* result;
|
||||
|
||||
struct mstdnt_account_args args = {
|
||||
.max_id = keystr(ssn->post.max_id),
|
||||
.since_id = NULL,
|
||||
.min_id = keystr(ssn->post.min_id),
|
||||
.offset = 0,
|
||||
.limit = 20,
|
||||
.with_relationships = 0,
|
||||
};
|
||||
|
||||
mstdnt_get_followers(api, &m_args, acct->id, &args, &storage, &accounts, &accts_len);
|
||||
|
||||
return accounts_page(session_hv, api, acct, rel, NULL, &storage, accounts, accts_len);
|
||||
}
|
||||
|
||||
static char* account_following_cb(HV* session_hv,
|
||||
struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_account* acct,
|
||||
struct mstdnt_relationship* rel,
|
||||
void* _args)
|
||||
{
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
struct mstdnt_account* accounts = NULL;
|
||||
size_t accts_len = 0;
|
||||
char* result;
|
||||
|
||||
struct mstdnt_account_args args = {
|
||||
.max_id = keystr(ssn->post.max_id),
|
||||
.since_id = NULL,
|
||||
.min_id = keystr(ssn->post.min_id),
|
||||
.offset = 0,
|
||||
.limit = 20,
|
||||
.with_relationships = 0,
|
||||
};
|
||||
|
||||
mstdnt_get_following(api, &m_args, acct->id, &args, &storage, &accounts, &accts_len);
|
||||
|
||||
return accounts_page(session_hv, api, acct, rel, NULL, &storage, accounts, accts_len);
|
||||
}
|
||||
|
||||
static char* account_statuses_cb(HV* session_hv,
|
||||
struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_account* acct,
|
||||
struct mstdnt_relationship* rel,
|
||||
void* _args)
|
||||
|
||||
{
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
struct mstdnt_account_statuses_args* args = _args;
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
struct mstdnt_status* statuses = NULL;
|
||||
size_t statuses_len = 0;
|
||||
char* result;
|
||||
|
||||
mstdnt_get_account_statuses(api, &m_args, acct->id, args, &storage, &statuses, &statuses_len);
|
||||
|
||||
PERL_STACK_INIT;
|
||||
XPUSHs(newRV_noinc((SV*)session_hv));
|
||||
XPUSHs(newRV_noinc((SV*)template_files));
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_account(acct)));
|
||||
if (rel)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_relationship(rel)));
|
||||
else ARG_UNDEFINED();
|
||||
|
||||
if (statuses && statuses_len)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_statuses(statuses, statuses_len)));
|
||||
else ARG_UNDEFINED();
|
||||
|
||||
PERL_STACK_SCALAR_CALL("account::content_statuses");
|
||||
|
||||
result = PERL_GET_STACK_EXIT;
|
||||
|
||||
mstdnt_storage_cleanup(&storage);
|
||||
mstdnt_cleanup_statuses(statuses, statuses_len);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static char* account_scrobbles_cb(HV* session_hv,
|
||||
struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_account* acct,
|
||||
struct mstdnt_relationship* rel,
|
||||
void* _args)
|
||||
{
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
struct mstdnt_scrobble* scrobbles = NULL;
|
||||
size_t scrobbles_len = 0;
|
||||
char* result;
|
||||
|
||||
struct mstdnt_get_scrobbles_args args = {
|
||||
.max_id = NULL,
|
||||
.min_id = NULL,
|
||||
|
@ -141,261 +198,96 @@ static char* account_scrobbles_cb(struct session* ssn, mastodont_t* api, struct
|
|||
.offset = 0,
|
||||
.limit = 20
|
||||
};
|
||||
|
||||
if (mastodont_get_scrobbles(api, acct->id, &args, &storage, &scrobbles, &scrobbles_len))
|
||||
{
|
||||
scrobbles_html = construct_error(storage.error, E_ERROR, 1, NULL);
|
||||
}
|
||||
else {
|
||||
scrobbles_html = construct_scrobbles(scrobbles, scrobbles_len, NULL);
|
||||
if (!scrobbles_html)
|
||||
scrobbles_html = construct_error("No scrobbles", E_NOTICE, 1, NULL);
|
||||
}
|
||||
mstdnt_get_scrobbles(api, &m_args, acct->id, &args, &storage, &scrobbles, &scrobbles_len);
|
||||
|
||||
mastodont_storage_cleanup(&storage);
|
||||
free(scrobbles);
|
||||
return scrobbles_html;
|
||||
PERL_STACK_INIT;
|
||||
XPUSHs(newRV_noinc((SV*)session_hv));
|
||||
XPUSHs(newRV_noinc((SV*)template_files));
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_account(acct)));
|
||||
if (rel)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_relationship(rel)));
|
||||
else ARG_UNDEFINED();
|
||||
|
||||
if (scrobbles && scrobbles_len)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_scrobbles(scrobbles, scrobbles_len)));
|
||||
else ARG_UNDEFINED();
|
||||
|
||||
PERL_STACK_SCALAR_CALL("account::content_scrobbles");
|
||||
|
||||
result = PERL_GET_STACK_EXIT;
|
||||
|
||||
mstdnt_storage_cleanup(&storage);
|
||||
return result;
|
||||
}
|
||||
|
||||
void get_account_info(mastodont_t* api, struct session* ssn)
|
||||
{
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
if (ssn->cookies.access_token.is_set && mstdnt_verify_credentials(api, &m_args, &(ssn->acct), &(ssn->acct_storage)) == 0)
|
||||
{
|
||||
ssn->logged_in = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void fetch_account_page(struct session* ssn,
|
||||
/**
|
||||
* Fetches the account information, and then calls a callback on the information received which
|
||||
* passes the account information
|
||||
*
|
||||
* @param req The request context
|
||||
* @param ssn The session, which will get transcribed into Perl
|
||||
* @param api Initiated mstdnt API
|
||||
* @param id User's ID to fetch
|
||||
* @param args The arguments to pass into the callback
|
||||
* @param tab Current tab to focus
|
||||
* @param callback Calls back with a perlified session, session and api as you passed in, the account,
|
||||
* the relationship, and additional arguments passed
|
||||
*/
|
||||
static void fetch_account_page(FCGX_Request* req,
|
||||
struct session* ssn,
|
||||
mastodont_t* api,
|
||||
char* id,
|
||||
void* args,
|
||||
enum account_tab tab,
|
||||
char* (*callback)(struct session* ssn, mastodont_t* api, struct mstdnt_account* acct, void* args))
|
||||
char* (*callback)(HV* ssn_hv, struct session* ssn, mastodont_t* api, struct mstdnt_account* acct, struct mstdnt_relationship* rel, void* args))
|
||||
{
|
||||
char* account_page;
|
||||
char* data;
|
||||
struct mstdnt_storage storage = { 0 },
|
||||
relations_storage = { 0 };
|
||||
struct mstdnt_account acct = { 0 };
|
||||
struct mstdnt_relationship* relationships = NULL;
|
||||
size_t relationships_len = 0;
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
|
||||
int lookup_type = config_experimental_lookup ? MSTDNT_LOOKUP_ACCT : MSTDNT_LOOKUP_ID;
|
||||
|
||||
if (mastodont_get_account(api, lookup_type, id, &acct, &storage))
|
||||
{
|
||||
account_page = construct_error(storage.error, E_ERROR, 1, NULL);
|
||||
}
|
||||
else {
|
||||
// Relationships may fail
|
||||
mastodont_get_relationships(api, &(acct.id), 1, &relations_storage, &relationships, &relationships_len);
|
||||
|
||||
data = callback(ssn, api,
|
||||
&acct, args);
|
||||
account_page = load_account_page(api,
|
||||
&acct,
|
||||
relationships,
|
||||
tab,
|
||||
data,
|
||||
NULL);
|
||||
if (!account_page)
|
||||
account_page = construct_error("Couldn't load page", E_ERROR, 1, NULL);
|
||||
|
||||
free(data);
|
||||
}
|
||||
mstdnt_get_account(api, &m_args, lookup_type, id, &acct, &storage);
|
||||
// Relationships may fail
|
||||
mstdnt_get_relationships(api, &m_args, &(acct.id), 1, &relations_storage, &relationships, &relationships_len);
|
||||
|
||||
struct base_page b = {
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
|
||||
char* data = callback(session_hv, ssn, api, &acct, relationships, args);
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_NONE,
|
||||
.locale = L10N_EN_US,
|
||||
.content = account_page,
|
||||
.content = data,
|
||||
.session = session_hv,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
render_base_page(&b, ssn, api);
|
||||
render_base_page(&b, req, ssn, api);
|
||||
|
||||
/* Output */
|
||||
mstdnt_cleanup_account(&acct);
|
||||
mstdnt_cleanup_relationships(relationships);
|
||||
mastodont_storage_cleanup(&storage);
|
||||
mastodont_storage_cleanup(&relations_storage);
|
||||
free(account_page);
|
||||
mstdnt_storage_cleanup(&storage);
|
||||
mstdnt_storage_cleanup(&relations_storage);
|
||||
tb_free(data);
|
||||
}
|
||||
|
||||
size_t construct_account_page(char** result, struct account_page* page, char* content)
|
||||
void content_account_statuses(PATH_ARGS)
|
||||
{
|
||||
int size;
|
||||
struct mstdnt_relationship* rel = page->relationship;
|
||||
char* follow_btn = NULL, *follow_btn_text = NULL;
|
||||
char* follows_you = NULL;
|
||||
char* info_html = NULL;
|
||||
char* is_blocked = NULL;
|
||||
|
||||
// Check if note is not empty
|
||||
if (page->note && strcmp(page->note, "") != 0)
|
||||
{
|
||||
info_html = load_account_info(page->account, NULL);
|
||||
}
|
||||
|
||||
if (rel)
|
||||
{
|
||||
if (MSTDNT_FLAG_ISSET(rel->flags, MSTDNT_RELATIONSHIP_FOLLOWED_BY))
|
||||
easprintf(&follows_you, FOLLOWS_YOU_HTML, L10N[page->locale][L10N_FOLLOWS_YOU]);
|
||||
|
||||
if (MSTDNT_FLAG_ISSET(rel->flags, MSTDNT_RELATIONSHIP_BLOCKED_BY))
|
||||
is_blocked = construct_error(L10N[page->locale][L10N_BLOCKED], E_NOTICE, 0, NULL);
|
||||
|
||||
if (MSTDNT_FLAG_ISSET(rel->flags, MSTDNT_RELATIONSHIP_REQUESTED))
|
||||
follow_btn_text = L10N[page->locale][L10N_FOLLOW_PENDING];
|
||||
else if (MSTDNT_FLAG_ISSET(rel->flags, MSTDNT_RELATIONSHIP_FOLLOWING))
|
||||
follow_btn_text = L10N[page->locale][L10N_FOLLOWING];
|
||||
else
|
||||
follow_btn_text = L10N[page->locale][L10N_FOLLOW];
|
||||
|
||||
easprintf(&follow_btn, data_account_follow_btn_html,
|
||||
config_url_prefix,
|
||||
page->id,
|
||||
(rel && (MSTDNT_FLAG_ISSET(rel->flags, MSTDNT_RELATIONSHIP_FOLLOWING) ||
|
||||
MSTDNT_FLAG_ISSET(rel->flags, MSTDNT_RELATIONSHIP_REQUESTED))
|
||||
? "un" : ""),
|
||||
(rel && MSTDNT_FLAG_ISSET(rel->flags, MSTDNT_RELATIONSHIP_FOLLOWING)
|
||||
? "active" : ""),
|
||||
follow_btn_text);
|
||||
}
|
||||
|
||||
size = easprintf(result, data_account_html,
|
||||
STR_NULL_EMPTY(is_blocked),
|
||||
page->header_image,
|
||||
STR_NULL_EMPTY(follows_you),
|
||||
page->display_name,
|
||||
page->acct,
|
||||
config_url_prefix,
|
||||
page->id,
|
||||
(rel && MSTDNT_FLAG_ISSET(rel->flags,
|
||||
MSTDNT_RELATIONSHIP_NOTIFYING)
|
||||
? "un" : ""),
|
||||
(rel && MSTDNT_FLAG_ISSET(rel->flags,
|
||||
MSTDNT_RELATIONSHIP_NOTIFYING)
|
||||
? L10N[page->locale][L10N_UNSUBSCRIBE] : L10N[page->locale][L10N_SUBSCRIBE]),
|
||||
config_url_prefix,
|
||||
page->id,
|
||||
(rel && MSTDNT_FLAG_ISSET(rel->flags,
|
||||
MSTDNT_RELATIONSHIP_BLOCKING)
|
||||
? "un" : ""),
|
||||
(rel && MSTDNT_FLAG_ISSET(rel->flags,
|
||||
MSTDNT_RELATIONSHIP_BLOCKING)
|
||||
? L10N[page->locale][L10N_UNBLOCK] : L10N[page->locale][L10N_BLOCK]),
|
||||
config_url_prefix,
|
||||
page->id,
|
||||
(rel && MSTDNT_FLAG_ISSET(rel->flags,
|
||||
MSTDNT_RELATIONSHIP_MUTING)
|
||||
? "un" : ""),
|
||||
(rel && MSTDNT_FLAG_ISSET(rel->flags,
|
||||
MSTDNT_RELATIONSHIP_MUTING)
|
||||
? L10N[page->locale][L10N_UNMUTE] : L10N[page->locale][L10N_MUTE]),
|
||||
L10N[page->locale][L10N_TAB_STATUSES],
|
||||
page->statuses_count,
|
||||
L10N[page->locale][L10N_TAB_FOLLOWING],
|
||||
page->following_count,
|
||||
L10N[page->locale][L10N_TAB_FOLLOWERS],
|
||||
page->followers_count,
|
||||
STR_NULL_EMPTY(follow_btn),
|
||||
page->profile_image,
|
||||
STR_NULL_EMPTY(info_html),
|
||||
config_url_prefix,
|
||||
page->acct,
|
||||
MAKE_FOCUSED_IF(page->tab, ACCT_TAB_STATUSES),
|
||||
L10N[page->locale][L10N_TAB_STATUSES],
|
||||
config_url_prefix,
|
||||
page->acct,
|
||||
MAKE_FOCUSED_IF(page->tab, ACCT_TAB_SCROBBLES),
|
||||
L10N[page->locale][L10N_TAB_SCROBBLES],
|
||||
config_url_prefix,
|
||||
page->acct,
|
||||
MAKE_FOCUSED_IF(page->tab, ACCT_TAB_MEDIA),
|
||||
L10N[page->locale][L10N_TAB_MEDIA],
|
||||
config_url_prefix,
|
||||
page->acct,
|
||||
MAKE_FOCUSED_IF(page->tab, ACCT_TAB_PINNED),
|
||||
L10N[page->locale][L10N_TAB_PINNED],
|
||||
content);
|
||||
|
||||
if (info_html) free(info_html);
|
||||
if (follows_you) free(follows_you);
|
||||
if (follow_btn) free(follow_btn);
|
||||
if (is_blocked) free(is_blocked);
|
||||
return size;
|
||||
}
|
||||
|
||||
char* construct_account(mastodont_t* api,
|
||||
struct mstdnt_account* acct,
|
||||
uint8_t flags,
|
||||
int* size)
|
||||
{
|
||||
char* acct_html;
|
||||
|
||||
size_t s = easprintf(&acct_html, data_account_stub_html,
|
||||
config_url_prefix,
|
||||
acct->acct,
|
||||
acct->avatar,
|
||||
config_url_prefix,
|
||||
acct->acct,
|
||||
acct->display_name,
|
||||
acct->acct);
|
||||
|
||||
if (size) *size = s;
|
||||
return acct_html;
|
||||
}
|
||||
|
||||
static char* construct_account_voidwrap(void* passed, size_t index, int* res)
|
||||
{
|
||||
struct account_args* args = passed;
|
||||
return construct_account(args->api, args->accts + index, args->flags, res);
|
||||
}
|
||||
|
||||
char* construct_accounts(mastodont_t* api,
|
||||
struct mstdnt_account* accounts,
|
||||
size_t size,
|
||||
uint8_t flags,
|
||||
size_t* ret_size)
|
||||
{
|
||||
if (!(accounts && size)) return NULL;
|
||||
struct account_args acct_args = {
|
||||
.api = api,
|
||||
.accts = accounts,
|
||||
.flags = flags,
|
||||
};
|
||||
return construct_func_strings(construct_account_voidwrap, &acct_args, size, ret_size);
|
||||
}
|
||||
|
||||
char* load_account_page(mastodont_t* api,
|
||||
struct mstdnt_account* acct,
|
||||
struct mstdnt_relationship* relationship,
|
||||
enum account_tab tab,
|
||||
char* content,
|
||||
size_t* res_size)
|
||||
{
|
||||
size_t size;
|
||||
char* result;
|
||||
struct account_page page = {
|
||||
.locale = L10N_EN_US,
|
||||
.account = acct,
|
||||
.header_image = acct->header,
|
||||
.profile_image = acct->avatar,
|
||||
.acct = acct->acct,
|
||||
.display_name = acct->display_name,
|
||||
.statuses_count = acct->statuses_count,
|
||||
.following_count = acct->following_count,
|
||||
.followers_count = acct->followers_count,
|
||||
.note = acct->note,
|
||||
.id = acct->id,
|
||||
.tab = tab,
|
||||
.relationship = relationship,
|
||||
};
|
||||
|
||||
size = construct_account_page(&result, &page, content);
|
||||
|
||||
if (res_size) *res_size = size;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void content_account_statuses(struct session* ssn, mastodont_t* api, char** data)
|
||||
{
|
||||
|
||||
struct mstdnt_account_statuses_args args = {
|
||||
.pinned = 0,
|
||||
.only_media = 0,
|
||||
|
@ -406,19 +298,29 @@ void content_account_statuses(struct session* ssn, mastodont_t* api, char** data
|
|||
.max_id = keystr(ssn->post.max_id),
|
||||
.min_id = keystr(ssn->post.min_id),
|
||||
.since_id = NULL,
|
||||
.offset = 0,
|
||||
.offset = keyint(ssn->query.offset),
|
||||
.limit = 20,
|
||||
};
|
||||
fetch_account_page(ssn, api, data[0], &args, ACCT_TAB_STATUSES, account_statuses_cb);
|
||||
|
||||
fetch_account_page(req, ssn, api, data[0], &args, ACCT_TAB_STATUSES, account_statuses_cb);
|
||||
}
|
||||
|
||||
void content_account_scrobbles(struct session* ssn, mastodont_t* api, char** data)
|
||||
void content_account_followers(PATH_ARGS)
|
||||
{
|
||||
fetch_account_page(ssn, api, data[0], NULL, ACCT_TAB_SCROBBLES, account_scrobbles_cb);
|
||||
fetch_account_page(req, ssn, api, data[0], NULL, ACCT_TAB_NONE, account_followers_cb);
|
||||
}
|
||||
|
||||
void content_account_following(PATH_ARGS)
|
||||
{
|
||||
fetch_account_page(req, ssn, api, data[0], NULL, ACCT_TAB_NONE, account_following_cb);
|
||||
}
|
||||
|
||||
void content_account_pinned(struct session* ssn, mastodont_t* api, char** data)
|
||||
void content_account_scrobbles(PATH_ARGS)
|
||||
{
|
||||
fetch_account_page(req, ssn, api, data[0], NULL, ACCT_TAB_SCROBBLES, account_scrobbles_cb);
|
||||
}
|
||||
|
||||
void content_account_pinned(PATH_ARGS)
|
||||
{
|
||||
struct mstdnt_account_statuses_args args = {
|
||||
.pinned = 1,
|
||||
|
@ -434,11 +336,10 @@ void content_account_pinned(struct session* ssn, mastodont_t* api, char** data)
|
|||
.limit = 20,
|
||||
};
|
||||
|
||||
fetch_account_page(ssn, api, data[0], &args, ACCT_TAB_PINNED, account_statuses_cb);
|
||||
fetch_account_page(req, ssn, api, data[0], &args, ACCT_TAB_PINNED, account_statuses_cb);
|
||||
}
|
||||
|
||||
|
||||
void content_account_media(struct session* ssn, mastodont_t* api, char** data)
|
||||
void content_account_media(PATH_ARGS)
|
||||
{
|
||||
struct mstdnt_account_statuses_args args = {
|
||||
.pinned = 0,
|
||||
|
@ -454,152 +355,188 @@ void content_account_media(struct session* ssn, mastodont_t* api, char** data)
|
|||
.limit = 20,
|
||||
};
|
||||
|
||||
fetch_account_page(ssn, api, data[0], &args, ACCT_TAB_MEDIA, account_statuses_cb);
|
||||
fetch_account_page(req, ssn, api, data[0], &args, ACCT_TAB_MEDIA, account_statuses_cb);
|
||||
}
|
||||
|
||||
void content_account_action(struct session* ssn, mastodont_t* api, char** data)
|
||||
void content_account_action(PATH_ARGS)
|
||||
{
|
||||
char* referer = getenv("HTTP_REFERER");
|
||||
char* referer = GET_ENV("HTTP_REFERER", req);
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
struct mstdnt_relationship acct = { 0 };
|
||||
|
||||
if (strcmp(data[1], "follow") == 0)
|
||||
mastodont_follow_account(api, data[0], &storage, &acct);
|
||||
mstdnt_follow_account(api, &m_args, data[0], &storage, &acct);
|
||||
else if (strcmp(data[1], "unfollow") == 0)
|
||||
mastodont_unfollow_account(api, data[0], &storage, &acct);
|
||||
mstdnt_unfollow_account(api, &m_args, data[0], &storage, &acct);
|
||||
else if (strcmp(data[1], "mute") == 0)
|
||||
mastodont_mute_account(api, data[0], &storage, &acct);
|
||||
mstdnt_mute_account(api, &m_args, data[0], &storage, &acct);
|
||||
else if (strcmp(data[1], "unmute") == 0)
|
||||
mastodont_unmute_account(api, data[0], &storage, &acct);
|
||||
mstdnt_unmute_account(api, &m_args, data[0], &storage, &acct);
|
||||
else if (strcmp(data[1], "block") == 0)
|
||||
mastodont_block_account(api, data[0], &storage, &acct);
|
||||
mstdnt_block_account(api, &m_args, data[0], &storage, &acct);
|
||||
else if (strcmp(data[1], "unblock") == 0)
|
||||
mastodont_unblock_account(api, data[0], &storage, &acct);
|
||||
mstdnt_unblock_account(api, &m_args, data[0], &storage, &acct);
|
||||
else if (strcmp(data[1], "subscribe") == 0)
|
||||
mastodont_subscribe_account(api, data[0], &storage, &acct);
|
||||
mstdnt_subscribe_account(api, &m_args, data[0], &storage, &acct);
|
||||
else if (strcmp(data[1], "unsubscribe") == 0)
|
||||
mastodont_unsubscribe_account(api, data[0], &storage, &acct);
|
||||
mstdnt_unsubscribe_account(api, &m_args, data[0], &storage, &acct);
|
||||
|
||||
mastodont_storage_cleanup(&storage);
|
||||
mstdnt_storage_cleanup(&storage);
|
||||
|
||||
redirect(REDIRECT_303, referer);
|
||||
redirect(req, REDIRECT_303, referer);
|
||||
}
|
||||
|
||||
void content_account_bookmarks(struct session* ssn, mastodont_t* api, char** data)
|
||||
void content_account_bookmarks(PATH_ARGS)
|
||||
{
|
||||
size_t status_count = 0, statuses_html_count = 0;
|
||||
size_t statuses_len = 0;
|
||||
struct mstdnt_status* statuses = NULL;
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
char* status_format = NULL,
|
||||
*navigation_box = NULL,
|
||||
*output = NULL;
|
||||
char* start_id;
|
||||
|
||||
struct mstdnt_bookmarks_args args = {
|
||||
.max_id = keystr(ssn->post.max_id),
|
||||
.since_id = NULL,
|
||||
.min_id = keystr(ssn->post.min_id),
|
||||
.limit = 20,
|
||||
};
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
|
||||
if (mastodont_get_bookmarks(api, &args, &storage, &statuses, &status_count))
|
||||
{
|
||||
status_format = construct_error(storage.error, E_ERROR, 1, NULL);
|
||||
}
|
||||
else {
|
||||
// Construct statuses into HTML
|
||||
status_format = construct_statuses(ssn, api, statuses, status_count, NULL, &statuses_html_count);
|
||||
if (!status_format)
|
||||
status_format = construct_error("Couldn't load posts", E_ERROR, 1, NULL);
|
||||
}
|
||||
mstdnt_get_bookmarks(api, &m_args, &args, &storage, &statuses, &statuses_len);
|
||||
|
||||
// Create post box
|
||||
if (statuses)
|
||||
{
|
||||
// If not set, set it
|
||||
start_id = keystr(ssn->post.start_id) ? keystr(ssn->post.start_id) : statuses[0].id;
|
||||
navigation_box = construct_navigation_box(start_id,
|
||||
statuses[0].id,
|
||||
statuses[status_count-1].id,
|
||||
NULL);
|
||||
}
|
||||
content_timeline(req, ssn, api, &storage, statuses, statuses_len, BASE_CAT_BOOKMARKS, "Bookmarks", 0, 1);
|
||||
}
|
||||
|
||||
void content_account_blocked(PATH_ARGS)
|
||||
{
|
||||
struct mstdnt_account_args args = {
|
||||
.max_id = keystr(ssn->post.max_id),
|
||||
.since_id = NULL,
|
||||
.min_id = keystr(ssn->post.min_id),
|
||||
.offset = 0,
|
||||
.limit = 20,
|
||||
.with_relationships = 0,
|
||||
};
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
struct mstdnt_account* accts = NULL;
|
||||
size_t accts_len = 0;
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
|
||||
easprintf(&output, data_bookmarks_page_html, status_format,
|
||||
STR_NULL_EMPTY(navigation_box));
|
||||
mstdnt_get_blocks(api, &m_args, &args, &storage, &accts, &accts_len);
|
||||
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
char* result = accounts_page(session_hv, api, NULL, NULL, "Blocked users", &storage, accts, accts_len);
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_BOOKMARKS,
|
||||
.locale = L10N_EN_US,
|
||||
.content = output,
|
||||
.category = BASE_CAT_NONE,
|
||||
.content = result,
|
||||
.session = session_hv,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
// Output
|
||||
render_base_page(&b, ssn, api);
|
||||
|
||||
// Cleanup
|
||||
mastodont_storage_cleanup(&storage);
|
||||
mstdnt_cleanup_statuses(statuses, status_count);
|
||||
if (status_format) free(status_format);
|
||||
if (navigation_box) free(navigation_box);
|
||||
if (output) free(output);
|
||||
render_base_page(&b, req, ssn, api);
|
||||
tb_free(result);
|
||||
}
|
||||
|
||||
void content_account_favourites(struct session* ssn, mastodont_t* api, char** data)
|
||||
void content_account_muted(PATH_ARGS)
|
||||
{
|
||||
size_t status_count = 0, statuses_html_count = 0;
|
||||
struct mstdnt_account_args args = {
|
||||
.max_id = keystr(ssn->post.max_id),
|
||||
.since_id = NULL,
|
||||
.min_id = keystr(ssn->post.min_id),
|
||||
.offset = 0,
|
||||
.limit = 20,
|
||||
.with_relationships = 0,
|
||||
};
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
struct mstdnt_account* accts = NULL;
|
||||
size_t accts_len = 0;
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
|
||||
mstdnt_get_mutes(api, &m_args, &args, &storage, &accts, &accts_len);
|
||||
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
char* result = accounts_page(session_hv, api, NULL, NULL, "Muted users", &storage, accts, accts_len);
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_NONE,
|
||||
.content = result,
|
||||
.session = session_hv,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
render_base_page(&b, req, ssn, api);
|
||||
tb_free(result);
|
||||
}
|
||||
|
||||
void content_account_favourites(PATH_ARGS)
|
||||
{
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
size_t statuses_len = 0;
|
||||
struct mstdnt_status* statuses = NULL;
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
char* status_format = NULL,
|
||||
*navigation_box = NULL,
|
||||
*output = NULL,
|
||||
*page = NULL;
|
||||
char* start_id;
|
||||
|
||||
struct mstdnt_favourites_args args = {
|
||||
.max_id = keystr(ssn->post.max_id),
|
||||
.min_id = keystr(ssn->post.min_id),
|
||||
.limit = 20,
|
||||
};
|
||||
|
||||
if (mastodont_get_favourites(api, &args, &storage, &statuses, &status_count))
|
||||
{
|
||||
status_format = construct_error(storage.error, E_ERROR, 1, NULL);
|
||||
}
|
||||
else {
|
||||
// Construct statuses into HTML
|
||||
status_format = construct_statuses(ssn, api, statuses, status_count, NULL, &statuses_html_count);
|
||||
if (!status_format)
|
||||
status_format = construct_error("Couldn't load posts", E_ERROR, 1, NULL);
|
||||
}
|
||||
|
||||
// Create post box
|
||||
if (statuses)
|
||||
{
|
||||
// If not set, set it
|
||||
start_id = keystr(ssn->post.start_id) ? keystr(ssn->post.start_id) : statuses[0].id;
|
||||
navigation_box = construct_navigation_box(start_id,
|
||||
statuses[0].id,
|
||||
statuses[status_count-1].id,
|
||||
NULL);
|
||||
}
|
||||
easprintf(&output, data_favourites_page_html, status_format,
|
||||
navigation_box ? navigation_box : "");
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_FAVOURITES,
|
||||
.locale = L10N_EN_US,
|
||||
.content = output,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
// Output
|
||||
render_base_page(&b, ssn, api);
|
||||
|
||||
// Cleanup
|
||||
mastodont_storage_cleanup(&storage);
|
||||
mstdnt_cleanup_statuses(statuses, status_count);
|
||||
if (status_format) free(status_format);
|
||||
if (navigation_box) free(navigation_box);
|
||||
if (output) free(output);
|
||||
mstdnt_get_favourites(api, &m_args, &args, &storage, &statuses, &statuses_len);
|
||||
|
||||
content_timeline(req, ssn, api, &storage, statuses, statuses_len, BASE_CAT_BOOKMARKS, "Favorites", 0, 1);
|
||||
}
|
||||
|
||||
PERLIFY_MULTI(account, accounts, mstdnt_account)
|
||||
|
||||
HV* perlify_account(const struct mstdnt_account* acct)
|
||||
{
|
||||
if (!acct) return NULL;
|
||||
HV* acct_hv = newHV();
|
||||
|
||||
hvstores_str(acct_hv, "id", acct->id);
|
||||
hvstores_str(acct_hv, "username", acct->username);
|
||||
hvstores_str(acct_hv, "acct", acct->acct);
|
||||
hvstores_str(acct_hv, "display_name", acct->display_name);
|
||||
hvstores_str(acct_hv, "note", acct->note);
|
||||
hvstores_str(acct_hv, "avatar", acct->avatar);
|
||||
hvstores_str(acct_hv, "avatar_static", acct->avatar_static);
|
||||
hvstores_str(acct_hv, "header", acct->header);
|
||||
hvstores_str(acct_hv, "header_static", acct->header_static);
|
||||
hvstores_int(acct_hv, "created_at", acct->created_at);
|
||||
hvstores_str(acct_hv, "last_status_at", acct->last_status_at);
|
||||
hvstores_str(acct_hv, "mute_expires_at", acct->mute_expires_at);
|
||||
hvstores_int(acct_hv, "statuses_count", acct->statuses_count);
|
||||
hvstores_int(acct_hv, "followers_count", acct->followers_count);
|
||||
hvstores_int(acct_hv, "following_count", acct->following_count);
|
||||
hvstores_int(acct_hv, "bot", acct->bot);
|
||||
hvstores_int(acct_hv, "suspended", acct->suspended);
|
||||
hvstores_int(acct_hv, "locked", acct->locked);
|
||||
hvstores_int(acct_hv, "discoverable", acct->discoverable);
|
||||
hvstores_ref(acct_hv, "emojis", perlify_emojis(acct->emojis, acct->emojis_len));
|
||||
|
||||
return acct_hv;
|
||||
}
|
||||
|
||||
HV* perlify_relationship(const struct mstdnt_relationship* rel)
|
||||
{
|
||||
if (!rel) return NULL;
|
||||
HV* rel_hv = newHV();
|
||||
|
||||
hvstores_str(rel_hv, "id", rel->id);
|
||||
hvstores_int(rel_hv, "following", MSTDNT_T_FLAG_ISSET(rel, MSTDNT_RELATIONSHIP_FOLLOWING));
|
||||
hvstores_int(rel_hv, "requested", MSTDNT_T_FLAG_ISSET(rel, MSTDNT_RELATIONSHIP_REQUESTED));
|
||||
hvstores_int(rel_hv, "endoresed", MSTDNT_T_FLAG_ISSET(rel, MSTDNT_RELATIONSHIP_ENDORSED));
|
||||
hvstores_int(rel_hv, "followed_by", MSTDNT_T_FLAG_ISSET(rel, MSTDNT_RELATIONSHIP_FOLLOWED_BY));
|
||||
hvstores_int(rel_hv, "muting", MSTDNT_T_FLAG_ISSET(rel, MSTDNT_RELATIONSHIP_MUTING));
|
||||
hvstores_int(rel_hv, "muting_notifs", MSTDNT_T_FLAG_ISSET(rel, MSTDNT_RELATIONSHIP_MUTING_NOTIFS));
|
||||
hvstores_int(rel_hv, "showing_reblogs", MSTDNT_T_FLAG_ISSET(rel, MSTDNT_RELATIONSHIP_SHOWING_REBLOGS));
|
||||
hvstores_int(rel_hv, "notifying", MSTDNT_T_FLAG_ISSET(rel, MSTDNT_RELATIONSHIP_NOTIFYING));
|
||||
hvstores_int(rel_hv, "blocking", MSTDNT_T_FLAG_ISSET(rel, MSTDNT_RELATIONSHIP_BLOCKING));
|
||||
hvstores_int(rel_hv, "domain_blocking", MSTDNT_T_FLAG_ISSET(rel, MSTDNT_RELATIONSHIP_DOMAIN_BLOCKING));
|
||||
hvstores_int(rel_hv, "blocked_by", MSTDNT_T_FLAG_ISSET(rel, MSTDNT_RELATIONSHIP_BLOCKED_BY));
|
||||
|
||||
return rel_hv;
|
||||
}
|
||||
|
|
|
@ -18,9 +18,11 @@
|
|||
|
||||
#ifndef ACCOUNT_H
|
||||
#define ACCOUNT_H
|
||||
#include "global_perl.h"
|
||||
#include <stddef.h>
|
||||
#include <mastodont.h>
|
||||
#include "session.h"
|
||||
#include "path.h"
|
||||
#include "l10n.h"
|
||||
|
||||
#define ACCOUNT_NOP 0
|
||||
|
@ -29,10 +31,11 @@
|
|||
|
||||
enum account_tab
|
||||
{
|
||||
ACCT_TAB_NONE,
|
||||
ACCT_TAB_STATUSES,
|
||||
ACCT_TAB_SCROBBLES,
|
||||
ACCT_TAB_PINNED,
|
||||
ACCT_TAB_MEDIA
|
||||
ACCT_TAB_MEDIA,
|
||||
};
|
||||
|
||||
struct account_page
|
||||
|
@ -53,37 +56,22 @@ struct account_page
|
|||
struct mstdnt_relationship* relationship;
|
||||
};
|
||||
|
||||
void get_account_info(mastodont_t* api, struct session* ssn);
|
||||
|
||||
char* construct_account_sidebar(struct mstdnt_account* acct, size_t* size);
|
||||
void content_account_followers(PATH_ARGS);
|
||||
void content_account_following(PATH_ARGS);
|
||||
void content_account_statuses(PATH_ARGS);
|
||||
void content_account_scrobbles(PATH_ARGS);
|
||||
void content_account_pinned(PATH_ARGS);
|
||||
void content_account_blocked(PATH_ARGS);
|
||||
void content_account_muted(PATH_ARGS);
|
||||
void content_account_media(PATH_ARGS);
|
||||
void content_account_action(PATH_ARGS);
|
||||
void content_account_favourites(PATH_ARGS);
|
||||
void content_account_bookmarks(PATH_ARGS);
|
||||
|
||||
char* construct_account(mastodont_t* api,
|
||||
struct mstdnt_account* account,
|
||||
uint8_t flags,
|
||||
int* size);
|
||||
char* construct_accounts(mastodont_t* api,
|
||||
struct mstdnt_account* accounts,
|
||||
size_t size,
|
||||
uint8_t flags,
|
||||
size_t* ret_size);
|
||||
|
||||
size_t construct_account_page(char** result, struct account_page* page, char* content);
|
||||
|
||||
char* load_account_page(mastodont_t* api,
|
||||
struct mstdnt_account* acct,
|
||||
struct mstdnt_relationship* relationship,
|
||||
enum account_tab tab,
|
||||
char* content,
|
||||
size_t* res_size);
|
||||
|
||||
char* load_account_info(struct mstdnt_account* acct,
|
||||
size_t* size);
|
||||
|
||||
void content_account_statuses(struct session* ssn, mastodont_t* api, char** data);
|
||||
void content_account_scrobbles(struct session* ssn, mastodont_t* api, char** data);
|
||||
void content_account_pinned(struct session* ssn, mastodont_t* api, char** data);
|
||||
void content_account_media(struct session* ssn, mastodont_t* api, char** data);
|
||||
void content_account_action(struct session* ssn, mastodont_t* api, char** data);
|
||||
void content_account_favourites(struct session* ssn, mastodont_t* api, char** data);
|
||||
void content_account_bookmarks(struct session* ssn, mastodont_t* api, char** data);
|
||||
HV* perlify_account(const struct mstdnt_account* acct);
|
||||
AV* perlify_accounts(const struct mstdnt_account* accounts, size_t len);
|
||||
HV* perlify_relationship(const struct mstdnt_relationship* rel);
|
||||
|
||||
#endif // ACCOUNT_H
|
||||
|
|
|
@ -16,18 +16,21 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "../test.h"
|
||||
#include "applications.h"
|
||||
|
||||
// Imports
|
||||
#include "mime_multipart.c"
|
||||
#include "string_test.c"
|
||||
int main()
|
||||
HV* perlify_application(const struct mstdnt_app* app)
|
||||
{
|
||||
struct test tests[] = {
|
||||
{ "Mime multipart parser", mime_multipart_test },
|
||||
{ "Strings", string_replace_test }
|
||||
};
|
||||
return iterate_tests(tests, sizeof(tests)/sizeof(tests[0]));
|
||||
if (!app) return NULL;
|
||||
|
||||
HV* app_hv = newHV();
|
||||
hvstores_str(app_hv, "id", app->id);
|
||||
hvstores_str(app_hv, "name", app->name);
|
||||
hvstores_str(app_hv, "website", app->website);
|
||||
hvstores_str(app_hv, "redirect_uri", app->redirect_uri);
|
||||
hvstores_str(app_hv, "client_id", app->client_id);
|
||||
hvstores_str(app_hv, "client_secret", app->client_secret);
|
||||
hvstores_str(app_hv, "vapid_key", app->vapid_key);
|
||||
|
||||
return app_hv;
|
||||
}
|
||||
|
|
@ -16,15 +16,11 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef REPLY_H
|
||||
#define REPLY_H
|
||||
#ifndef APPLICATIONS_H
|
||||
#define APPLICATIONS_H
|
||||
#include <mastodont.h>
|
||||
#include "global_perl.h"
|
||||
|
||||
char* construct_post_box(char* reply_id,
|
||||
char* default_content,
|
||||
int* size);
|
||||
HV* perlify_application(const struct mstdnt_app* app);
|
||||
|
||||
char* reply_status(char* id, struct mstdnt_status* status);
|
||||
|
||||
|
||||
#endif // REPLY_H
|
||||
#endif /* APPLICATIONS_H */
|
|
@ -18,20 +18,16 @@
|
|||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "base_page.h"
|
||||
#include "helpers.h"
|
||||
#include "easprintf.h"
|
||||
#include "attachments.h"
|
||||
#include "string_helpers.h"
|
||||
|
||||
// Pages
|
||||
#include "../static/attachments.chtml"
|
||||
#include "../static/attachment_image.chtml"
|
||||
#include "../static/attachment_gifv.chtml"
|
||||
#include "../static/attachment_video.chtml"
|
||||
#include "../static/attachment_link.chtml"
|
||||
#include "../static/attachment_audio.chtml"
|
||||
|
||||
struct attachments_args
|
||||
{
|
||||
struct session* ssn;
|
||||
struct mstdnt_attachment* atts;
|
||||
mstdnt_bool sensitive;
|
||||
};
|
||||
|
@ -42,15 +38,17 @@ int try_upload_media(struct mstdnt_storage** storage,
|
|||
struct mstdnt_attachment** attachments,
|
||||
char*** media_ids)
|
||||
{
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
size_t size = keyfile(ssn->post.files).array_size;
|
||||
if (!FILES_READY(ssn))
|
||||
return 1;
|
||||
|
||||
if (media_ids)
|
||||
*media_ids = malloc(sizeof(char*) * size);
|
||||
*media_ids = tb_malloc(sizeof(char*) * size);
|
||||
|
||||
*attachments = malloc(sizeof(struct mstdnt_attachment) * size);
|
||||
*storage = calloc(1, sizeof(struct mstdnt_storage) * size);
|
||||
*attachments = tb_malloc(sizeof(struct mstdnt_attachment) * size);
|
||||
*storage = tb_calloc(1, sizeof(struct mstdnt_storage) * size);
|
||||
|
||||
for (int i = 0; i < size; ++i)
|
||||
{
|
||||
|
@ -63,37 +61,37 @@ int try_upload_media(struct mstdnt_storage** storage,
|
|||
.filetype = content->filetype,
|
||||
},
|
||||
.thumbnail = NULL,
|
||||
.description = "Treebird image"
|
||||
.description = NULL,
|
||||
};
|
||||
|
||||
if (mastodont_upload_media(api,
|
||||
if (mstdnt_upload_media(api,
|
||||
&m_args,
|
||||
&args,
|
||||
*storage + i,
|
||||
*attachments + i))
|
||||
{
|
||||
// EPICFAIL
|
||||
for (size_t j = 0; j < i; ++j)
|
||||
{
|
||||
if (media_ids) free((*media_ids)[j]);
|
||||
mastodont_storage_cleanup(*storage + j);
|
||||
if (media_ids) tb_free((*media_ids)[j]);
|
||||
mstdnt_storage_cleanup(*storage + j);
|
||||
}
|
||||
|
||||
if (media_ids)
|
||||
{
|
||||
free(*media_ids);
|
||||
tb_free(*media_ids);
|
||||
*media_ids = NULL;
|
||||
}
|
||||
|
||||
free(*attachments);
|
||||
tb_free(*attachments);
|
||||
*attachments = NULL;
|
||||
free(*storage);
|
||||
tb_free(*storage);
|
||||
*storage = NULL;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (media_ids)
|
||||
{
|
||||
(*media_ids)[i] = malloc(strlen((*attachments)[i].id)+1);
|
||||
(*media_ids)[i] = tb_malloc(strlen((*attachments)[i].id)+1);
|
||||
strcpy((*media_ids)[i], (*attachments)[i].id);
|
||||
}
|
||||
}
|
||||
|
@ -105,8 +103,8 @@ void cleanup_media_storages(struct session* ssn, struct mstdnt_storage* storage)
|
|||
{
|
||||
if (!FILES_READY(ssn)) return;
|
||||
for (size_t i = 0; i < keyfile(ssn->post.files).array_size; ++i)
|
||||
mastodont_storage_cleanup(storage + i);
|
||||
free(storage);
|
||||
mstdnt_storage_cleanup(storage + i);
|
||||
tb_free(storage);
|
||||
}
|
||||
|
||||
void cleanup_media_ids(struct session* ssn, char** media_ids)
|
||||
|
@ -114,67 +112,55 @@ void cleanup_media_ids(struct session* ssn, char** media_ids)
|
|||
if (!FILES_READY(ssn)) return;
|
||||
if (!media_ids) return;
|
||||
for (size_t i = 0; i < keyfile(ssn->post.files).array_size; ++i)
|
||||
free(media_ids[i]);
|
||||
free(media_ids);
|
||||
tb_free(media_ids[i]);
|
||||
tb_free(media_ids);
|
||||
}
|
||||
|
||||
char* construct_attachment(mstdnt_bool sensitive, struct mstdnt_attachment* att, int* str_size)
|
||||
HV* perlify_attachment(const struct mstdnt_attachment* const attachment)
|
||||
{
|
||||
char* att_html;
|
||||
size_t s;
|
||||
const char* attachment_str;
|
||||
if (!att) return NULL;
|
||||
if (!attachment) return NULL;
|
||||
HV* attach_hv = newHV();
|
||||
hvstores_str(attach_hv, "id", attachment->id);
|
||||
hvstores_int(attach_hv, "type", attachment->type);
|
||||
hvstores_str(attach_hv, "url", attachment->url);
|
||||
hvstores_str(attach_hv, "preview_url", attachment->preview_url);
|
||||
hvstores_str(attach_hv, "remote_url", attachment->remote_url);
|
||||
hvstores_str(attach_hv, "hash", attachment->hash);
|
||||
hvstores_str(attach_hv, "description", attachment->description);
|
||||
hvstores_str(attach_hv, "blurhash", attachment->blurhash);
|
||||
return attach_hv;
|
||||
}
|
||||
|
||||
switch (att->type)
|
||||
{
|
||||
case MSTDNT_ATTACHMENT_IMAGE:
|
||||
attachment_str = data_attachment_image_html; break;
|
||||
case MSTDNT_ATTACHMENT_GIFV:
|
||||
attachment_str = data_attachment_gifv_html; break;
|
||||
case MSTDNT_ATTACHMENT_VIDEO:
|
||||
attachment_str = data_attachment_video_html; break;
|
||||
case MSTDNT_ATTACHMENT_AUDIO:
|
||||
attachment_str = data_attachment_audio_html; break;
|
||||
case MSTDNT_ATTACHMENT_UNKNOWN: // Fall through
|
||||
default:
|
||||
attachment_str = data_attachment_link_html; break;
|
||||
}
|
||||
PERLIFY_MULTI(attachment, attachments, mstdnt_attachment)
|
||||
|
||||
// Images/visible content displays sensitive placeholder after
|
||||
if (att->type == MSTDNT_ATTACHMENT_IMAGE ||
|
||||
att->type == MSTDNT_ATTACHMENT_GIFV ||
|
||||
att->type == MSTDNT_ATTACHMENT_VIDEO)
|
||||
{
|
||||
s = easprintf(&att_html, attachment_str,
|
||||
att->url,
|
||||
sensitive ? "<div class=\"sensitive-contain sensitive\"></div>" : "");
|
||||
}
|
||||
else {
|
||||
s = easprintf(&att_html, attachment_str,
|
||||
sensitive ? "sensitive" : "",
|
||||
att->url);
|
||||
}
|
||||
void api_attachment_create(PATH_ARGS)
|
||||
{
|
||||
struct mstdnt_storage *att_storage = NULL;
|
||||
struct mstdnt_attachment* attachments = NULL;
|
||||
char* string;
|
||||
char** media_ids = NULL;
|
||||
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
|
||||
// TODO If multiple attachments are submitted, this uploads all of them.
|
||||
// I don't think we want that, but it's just a minor issue and won't happen on TreebirdFE.
|
||||
try_upload_media(&att_storage, ssn, api, &attachments, &media_ids);
|
||||
|
||||
if (media_ids)
|
||||
cJSON_AddStringToObject(root, "id", media_ids[0]);
|
||||
|
||||
if (str_size) *str_size = s;
|
||||
return att_html;
|
||||
}
|
||||
if (media_ids)
|
||||
{
|
||||
string = cJSON_Print(root);
|
||||
send_result(req, NULL, "application/json", string, 0);
|
||||
tb_free(string);
|
||||
}
|
||||
else
|
||||
send_result(req, NULL, "application/json", "{\"status\":\"Couldn't\"}", 0);
|
||||
|
||||
static char* construct_attachments_voidwrap(void* passed, size_t index, int* res)
|
||||
{
|
||||
struct attachments_args* args = passed;
|
||||
return construct_attachment(args->sensitive, args->atts + index, res);
|
||||
}
|
||||
|
||||
char* construct_attachments(mstdnt_bool sensitive, struct mstdnt_attachment* atts, size_t atts_len, size_t* str_size)
|
||||
{
|
||||
size_t elements_size;
|
||||
struct attachments_args args = { atts, sensitive };
|
||||
char* elements = construct_func_strings(construct_attachments_voidwrap, &args, atts_len, &elements_size);
|
||||
char* att_view;
|
||||
|
||||
size_t s = easprintf(&att_view, data_attachments_html, elements);
|
||||
if (str_size) *str_size = s;
|
||||
// Cleanup
|
||||
free(elements);
|
||||
return att_view;
|
||||
// Cleanup media stuff
|
||||
cleanup_media_storages(ssn, att_storage);
|
||||
cleanup_media_ids(ssn, media_ids);
|
||||
tb_free(attachments);
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
|
|
|
@ -19,7 +19,9 @@
|
|||
#ifndef ATTACHMENTS_H
|
||||
#define ATTACHMENTS_H
|
||||
#include <mastodont.h>
|
||||
#include "path.h"
|
||||
#include "session.h"
|
||||
#include "global_perl.h"
|
||||
|
||||
#define FILES_READY(ssn) (ssn->post.files.type.f.array_size && \
|
||||
ssn->post.files.type.f.content && \
|
||||
|
@ -32,7 +34,10 @@ int try_upload_media(struct mstdnt_storage** storage,
|
|||
char*** media_ids);
|
||||
void cleanup_media_storages(struct session* ssn, struct mstdnt_storage* storage);
|
||||
void cleanup_media_ids(struct session* ssn, char** media_ids);
|
||||
char* construct_attachment(mstdnt_bool sensitive, struct mstdnt_attachment* att, int* str_size);
|
||||
char* construct_attachments(mstdnt_bool sensitive, struct mstdnt_attachment* atts, size_t atts_len, size_t* str_size);
|
||||
void api_attachment_create(PATH_ARGS);
|
||||
|
||||
// Perl
|
||||
HV* perlify_attachment(const struct mstdnt_attachment* const attachment);
|
||||
AV* perlify_attachments(const struct mstdnt_attachment* const attachments, size_t len);
|
||||
|
||||
#endif // ATTACHMENTS_H
|
||||
|
|
174
src/base_page.c
|
@ -16,9 +16,10 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <fcgi_stdio.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "helpers.h"
|
||||
#include "base_page.h"
|
||||
#include "easprintf.h"
|
||||
#include "cookie.h"
|
||||
|
@ -27,50 +28,26 @@
|
|||
#include "../config.h"
|
||||
#include "local_config_set.h"
|
||||
#include "account.h"
|
||||
#include "cgi.h"
|
||||
#include "global_cache.h"
|
||||
|
||||
// Files
|
||||
#include "../static/index.chtml"
|
||||
#include "../static/quick_login.chtml"
|
||||
|
||||
#define BODY_STYLE "style=\"background:url('%s');\""
|
||||
|
||||
void render_base_page(struct base_page* page, struct session* ssn, mastodont_t* api)
|
||||
|
||||
void render_base_page(struct base_page* page, FCGX_Request* req, struct session* ssn, mastodont_t* api)
|
||||
{
|
||||
char* cookie = getenv("HTTP_COOKIE");
|
||||
enum l10n_locale locale = page->locale;
|
||||
char* login_string = "<a href=\"login\" id=\"login-header\">Login / Register</a>";
|
||||
char* background_url_css = NULL;
|
||||
// Sidebar
|
||||
char* sidebar_str,
|
||||
* main_sidebar_str = NULL,
|
||||
* account_sidebar_str = NULL;
|
||||
// Mastodont, used for notifications sidebar
|
||||
struct mstdnt_account acct = { 0 };
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
struct mstdnt_notification* notifs = NULL;
|
||||
size_t notifs_len = 0;
|
||||
|
||||
if (ssn->config.logged_in)
|
||||
login_string = "";
|
||||
|
||||
if (ssn->config.background_url)
|
||||
// Fetch notification (if not iFrame)
|
||||
if (keystr(ssn->cookies.logged_in) && keystr(ssn->cookies.access_token) &&
|
||||
!ssn->config.notif_embed)
|
||||
{
|
||||
easprintf(&background_url_css, BODY_STYLE, ssn->config.background_url);
|
||||
}
|
||||
|
||||
// If user is logged in
|
||||
if (keystr(ssn->cookies.logged_in) && keystr(ssn->cookies.access_token))
|
||||
{
|
||||
if (mastodont_verify_credentials(api, &acct, &storage) == 0)
|
||||
{
|
||||
account_sidebar_str = construct_account_sidebar(&acct, NULL);
|
||||
}
|
||||
mstdnt_cleanup_account(&acct);
|
||||
mastodont_storage_cleanup(&storage); // reuse it later
|
||||
|
||||
// Get / Show notifications on sidebar
|
||||
struct mstdnt_get_notifications_args args = {
|
||||
struct mstdnt_notifications_args args = {
|
||||
.exclude_types = 0,
|
||||
.account_id = NULL,
|
||||
.exclude_visibilities = 0,
|
||||
|
@ -83,89 +60,60 @@ void render_base_page(struct base_page* page, struct session* ssn, mastodont_t*
|
|||
.limit = 8,
|
||||
};
|
||||
|
||||
if (mastodont_get_notifications(api, &args, &storage, ¬ifs, ¬ifs_len) == 0)
|
||||
{
|
||||
main_sidebar_str = construct_notifications_compact(ssn, api, notifs, notifs_len, NULL);
|
||||
}
|
||||
|
||||
mstdnt_cleanup_notifications(notifs, notifs_len);
|
||||
mastodont_storage_cleanup(&storage);
|
||||
}
|
||||
else {
|
||||
// Construct small login page
|
||||
easprintf(&main_sidebar_str, data_quick_login_html,
|
||||
config_url_prefix,
|
||||
L10N[L10N_EN_US][L10N_USERNAME],
|
||||
L10N[L10N_EN_US][L10N_PASSWORD],
|
||||
L10N[L10N_EN_US][L10N_LOGIN_BTN]);
|
||||
mstdnt_get_notifications(
|
||||
api,
|
||||
&m_args,
|
||||
&args,
|
||||
&storage,
|
||||
¬ifs,
|
||||
¬ifs_len
|
||||
);
|
||||
}
|
||||
|
||||
// Combine into sidebar
|
||||
easprintf(&sidebar_str, "%s%s",
|
||||
account_sidebar_str ? account_sidebar_str : "",
|
||||
main_sidebar_str ? main_sidebar_str : "");
|
||||
PERL_STACK_INIT;
|
||||
|
||||
char* data;
|
||||
int len = easprintf(&data, data_index_html,
|
||||
L10N[locale][L10N_APP_NAME],
|
||||
ssn->config.theme,
|
||||
ssn->config.themeclr ? "-dark" : "",
|
||||
background_url_css ? background_url_css : "",
|
||||
config_url_prefix,
|
||||
L10N[locale][L10N_APP_NAME],
|
||||
login_string,
|
||||
config_url_prefix,
|
||||
L10N[locale][L10N_SEARCH_PLACEHOLDER],
|
||||
L10N[locale][L10N_SEARCH_BUTTON],
|
||||
CAT_TEXT(page->category, BASE_CAT_HOME),
|
||||
config_url_prefix,
|
||||
L10N[locale][L10N_HOME],
|
||||
CAT_TEXT(page->category, BASE_CAT_LOCAL),
|
||||
config_url_prefix,
|
||||
L10N[locale][L10N_LOCAL],
|
||||
CAT_TEXT(page->category, BASE_CAT_FEDERATED),
|
||||
config_url_prefix,
|
||||
L10N[locale][L10N_FEDERATED],
|
||||
CAT_TEXT(page->category, BASE_CAT_NOTIFICATIONS),
|
||||
config_url_prefix,
|
||||
L10N[locale][L10N_NOTIFICATIONS],
|
||||
CAT_TEXT(page->category, BASE_CAT_LISTS),
|
||||
config_url_prefix,
|
||||
L10N[locale][L10N_LISTS],
|
||||
CAT_TEXT(page->category, BASE_CAT_FAVOURITES),
|
||||
config_url_prefix,
|
||||
L10N[locale][L10N_FAVOURITES],
|
||||
CAT_TEXT(page->category, BASE_CAT_BOOKMARKS),
|
||||
config_url_prefix,
|
||||
L10N[locale][L10N_BOOKMARKS],
|
||||
CAT_TEXT(page->category, BASE_CAT_DIRECT),
|
||||
config_url_prefix,
|
||||
L10N[locale][L10N_DIRECT],
|
||||
CAT_TEXT(page->category, BASE_CAT_CONFIG),
|
||||
config_url_prefix,
|
||||
L10N[locale][L10N_CONFIG],
|
||||
page->sidebar_left ? page->sidebar_left : "",
|
||||
(ssn->config.instance_panel && g_cache.panel_html.response ?
|
||||
g_cache.panel_html.response : ""),
|
||||
page->content,
|
||||
sidebar_str ? sidebar_str : "");
|
||||
|
||||
if (!data)
|
||||
HV* real_ssn = page->session ? page->session : perlify_session(ssn);
|
||||
mXPUSHs(newRV_noinc((SV*)real_ssn));
|
||||
mXPUSHs(newRV_inc((SV*)template_files));
|
||||
mXPUSHs(newSVpv(page->content, 0));
|
||||
|
||||
if (notifs && notifs_len)
|
||||
{
|
||||
perror("malloc");
|
||||
goto cleanup;
|
||||
mXPUSHs(newRV_noinc(perlify_notifications(notifs, notifs_len)));
|
||||
}
|
||||
|
||||
fputs("Content-type: text/html\r\n", stdout);
|
||||
printf("Content-Length: %d\r\n\r\n", len + 1);
|
||||
puts(data);
|
||||
else ARG_UNDEFINED();
|
||||
|
||||
// Cleanup
|
||||
/* cleanup_all: */
|
||||
free(data);
|
||||
cleanup:
|
||||
if (sidebar_str) free(sidebar_str);
|
||||
if (main_sidebar_str) free(main_sidebar_str);
|
||||
if (account_sidebar_str) free(account_sidebar_str);
|
||||
if (background_url_css) free(background_url_css);
|
||||
// Run function
|
||||
PERL_STACK_SCALAR_CALL("base_page");
|
||||
char* dup = PERL_GET_STACK_EXIT;
|
||||
|
||||
|
||||
send_result(req, NULL, "text/html", dup, 0);
|
||||
|
||||
mstdnt_cleanup_notifications(notifs, notifs_len);
|
||||
mstdnt_storage_cleanup(&storage);
|
||||
tb_free(dup);
|
||||
}
|
||||
|
||||
void send_result(FCGX_Request* req, char* status, char* content_type, char* data, size_t data_len)
|
||||
{
|
||||
if (data_len == 0) data_len = strlen(data);
|
||||
#ifdef SINGLE_THREADED
|
||||
printf(
|
||||
#else
|
||||
pthread_mutex_lock(&print_mutex);
|
||||
FCGX_FPrintF(req->out,
|
||||
#endif
|
||||
"Status: %s\r\n"
|
||||
"Content-type: %s\r\n"
|
||||
"Content-Length: %d\r\n\r\n",
|
||||
status ? status : "200 OK",
|
||||
content_type ? content_type : "text/html",
|
||||
data_len);
|
||||
#ifdef SINGLE_THREADED
|
||||
puts(data);
|
||||
#else
|
||||
FCGX_PutStr(data, data_len, req->out);
|
||||
pthread_mutex_unlock(&print_mutex);
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -18,10 +18,16 @@
|
|||
|
||||
#ifndef BASE_PAGE_H
|
||||
#define BASE_PAGE_H
|
||||
#include "global_perl.h"
|
||||
#include "session.h"
|
||||
#include <fcgi_stdio.h>
|
||||
#include <fcgiapp.h>
|
||||
#include <mastodont.h>
|
||||
#include "l10n.h"
|
||||
#include "local_config.h"
|
||||
#include "session.h"
|
||||
#include "path.h"
|
||||
#include <pthread.h>
|
||||
|
||||
|
||||
enum base_category
|
||||
{
|
||||
|
@ -34,17 +40,29 @@ enum base_category
|
|||
BASE_CAT_FAVOURITES,
|
||||
BASE_CAT_BOOKMARKS,
|
||||
BASE_CAT_DIRECT,
|
||||
BASE_CAT_CHATS,
|
||||
BASE_CAT_CONFIG,
|
||||
};
|
||||
|
||||
struct base_page
|
||||
{
|
||||
enum base_category category;
|
||||
enum l10n_locale locale;
|
||||
char* content;
|
||||
char* sidebar_left;
|
||||
HV* session;
|
||||
};
|
||||
|
||||
void render_base_page(struct base_page* page, struct session* ssn, mastodont_t* api);
|
||||
void render_base_page(struct base_page* page, FCGX_Request* req, struct session* ssn, mastodont_t* api);
|
||||
|
||||
/**
|
||||
* Outputs HTML in format for CGI. This can only be called once!
|
||||
*
|
||||
* @param req The FCGI request
|
||||
* @param status The full HTTP status. if NULL, then status is "200 OK"
|
||||
* @param content_type The Content-Type to display. if NULL, assume "text/html"
|
||||
* @param data HTML content
|
||||
* @param data_len Length of data. If 0, calls strlen(data)
|
||||
*/
|
||||
void send_result(FCGX_Request* req, char* status, char* content_type, char* data, size_t data_len);
|
||||
|
||||
#endif // BASE_PAGE_H
|
||||
|
|
|
@ -16,14 +16,15 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef NAVIGATION_H
|
||||
#define NAVIGATION_H
|
||||
#include <stddef.h>
|
||||
#include <mastodont.h>
|
||||
/* #ifndef IMPORT_CGI_H */
|
||||
/* #define IMPORT_CGI_H */
|
||||
|
||||
char* construct_navigation_box(char* start_id,
|
||||
char* prev_id,
|
||||
char* next_id,
|
||||
size_t* size);
|
||||
#ifndef NO_FCGI
|
||||
#include <fcgi_stdio.h>
|
||||
#endif // NO_FCGI
|
||||
|
||||
#endif // NAVIGATION_H
|
||||
#ifndef SINGLE_THREADED
|
||||
#include <fcgiapp.h>
|
||||
#endif // SINGLE_THREADED
|
||||
|
||||
// #endif
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* Treebird - Lightweight frontend for Pleroma
|
||||
* Copyright (C) 2022 Nekobit
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "account.h"
|
||||
#include "emoji.h"
|
||||
#include "../config.h"
|
||||
#include "conversations.h"
|
||||
#include "helpers.h"
|
||||
#include "string_helpers.h"
|
||||
#include "error.h"
|
||||
#include "base_page.h"
|
||||
|
||||
void content_chats(PATH_ARGS)
|
||||
{
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
struct mstdnt_chat* chats = NULL;
|
||||
size_t chats_len = 0;
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
|
||||
struct mstdnt_chats_args args = {
|
||||
.with_muted = MSTDNT_TRUE,
|
||||
.max_id = keystr(ssn->post.max_id),
|
||||
.since_id = NULL,
|
||||
.min_id = keystr(ssn->post.min_id),
|
||||
.offset = keyint(ssn->query.offset),
|
||||
.limit = 20,
|
||||
};
|
||||
|
||||
mstdnt_get_chats_v2(api, &m_args, &args, &storage, &chats, &chats_len);
|
||||
|
||||
PERL_STACK_INIT;
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
XPUSHs(newRV_noinc((SV*)session_hv));
|
||||
XPUSHs(newRV_noinc((SV*)template_files));
|
||||
if (chats)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_chats(chats, chats_len)));
|
||||
else ARG_UNDEFINED();
|
||||
|
||||
PERL_STACK_SCALAR_CALL("chat::content_chats");
|
||||
|
||||
// Duplicate so we can free the TMPs
|
||||
char* dup = PERL_GET_STACK_EXIT;
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_CHATS,
|
||||
.content = dup,
|
||||
.session = session_hv,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
// Outpuot
|
||||
render_base_page(&b, req, ssn, api);
|
||||
|
||||
// Cleanup
|
||||
mstdnt_storage_cleanup(&storage);
|
||||
mstdnt_cleanup_chats(chats, chats_len);
|
||||
tb_free(dup);
|
||||
}
|
||||
|
||||
void content_chat_view(PATH_ARGS)
|
||||
{
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
struct mstdnt_message* messages = NULL;
|
||||
/* struct mstdnt_account acct; */
|
||||
size_t messages_len = 0;
|
||||
struct mstdnt_storage storage = { 0 }, storage_chat = { 0 };
|
||||
struct mstdnt_chat chat;
|
||||
|
||||
struct mstdnt_chats_args args = {
|
||||
.with_muted = MSTDNT_TRUE,
|
||||
.max_id = keystr(ssn->post.max_id),
|
||||
.since_id = NULL,
|
||||
.min_id = keystr(ssn->post.min_id),
|
||||
.offset = keyint(ssn->query.offset),
|
||||
.limit = 20,
|
||||
};
|
||||
|
||||
mstdnt_get_chat_messages(api, &m_args, data[0], &args, &storage, &messages, &messages_len);
|
||||
int chat_code = mstdnt_get_chat(api, &m_args, data[0],
|
||||
&storage_chat, &chat);
|
||||
|
||||
PERL_STACK_INIT;
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
XPUSHs(newRV_noinc((SV*)session_hv));
|
||||
XPUSHs(newRV_noinc((SV*)template_files));
|
||||
if (chat_code == 0)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_chat(&chat)));
|
||||
else ARG_UNDEFINED();
|
||||
if (messages)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_messages(messages, messages_len)));
|
||||
else ARG_UNDEFINED();
|
||||
|
||||
PERL_STACK_SCALAR_CALL("chat::construct_chat");
|
||||
|
||||
// Duplicate so we can free the TMPs
|
||||
char* dup = PERL_GET_STACK_EXIT;
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_CHATS,
|
||||
.content = dup,
|
||||
.session = session_hv,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
// Output
|
||||
render_base_page(&b, req, ssn, api);
|
||||
|
||||
mstdnt_storage_cleanup(&storage);
|
||||
mstdnt_storage_cleanup(&storage_chat);
|
||||
mstdnt_cleanup_chat(&chat);
|
||||
mstdnt_cleanup_messages(messages);
|
||||
tb_free(dup);
|
||||
}
|
||||
|
||||
HV* perlify_chat(const struct mstdnt_chat* chat)
|
||||
{
|
||||
if (!chat) return NULL;
|
||||
|
||||
HV* chat_hv = newHV();
|
||||
hvstores_ref(chat_hv, "account", perlify_account(&(chat->account)));
|
||||
hvstores_str(chat_hv, "id", chat->id);
|
||||
hvstores_int(chat_hv, "unread", chat->unread);
|
||||
|
||||
return chat_hv;
|
||||
}
|
||||
|
||||
PERLIFY_MULTI(chat, chats, mstdnt_chat)
|
||||
|
||||
HV* perlify_message(const struct mstdnt_message* message)
|
||||
{
|
||||
if (!message) return NULL;
|
||||
|
||||
HV* message_hv = newHV();
|
||||
hvstores_str(message_hv, "account_id", message->account_id);
|
||||
hvstores_str(message_hv, "chat_id", message->chat_id);
|
||||
hvstores_str(message_hv, "id", message->id);
|
||||
hvstores_str(message_hv, "content", message->content);
|
||||
hvstores_int(message_hv, "created_at", message->created_at);
|
||||
hvstores_ref(message_hv, "emojis", perlify_emojis(message->emojis, message->emojis_len));
|
||||
hvstores_int(message_hv, "unread", message->unread);
|
||||
|
||||
return message_hv;
|
||||
}
|
||||
|
||||
PERLIFY_MULTI(message, messages, mstdnt_message)
|
|
@ -16,17 +16,20 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef GRAPHS_N_BARS_H
|
||||
#define GRAPHS_N_BARS_H
|
||||
#ifndef CONVERSATIONS_H
|
||||
#define CONVERSATIONS_H
|
||||
#include "path.h"
|
||||
#include <stddef.h>
|
||||
#include <mastodont.h>
|
||||
#include "session.h"
|
||||
|
||||
char* construct_bar_graph_container(char* bars, size_t* size);
|
||||
char* construct_bar(float value, int* size);
|
||||
char* construct_hashtags_graph(struct mstdnt_tag* tags,
|
||||
size_t tags_len,
|
||||
size_t days,
|
||||
size_t* ret_size);
|
||||
void content_chats(PATH_ARGS);
|
||||
void content_chat_view(PATH_ARGS);
|
||||
|
||||
#endif /* GRAPHS_N_BARS_H */
|
||||
AV* perlify_chats(const struct mstdnt_chat* chats, size_t chats_len);
|
||||
HV* perlify_chat(const struct mstdnt_chat* chat);
|
||||
|
||||
AV* perlify_messages(const struct mstdnt_message* messages, size_t messages_len);
|
||||
HV* perlify_message(const struct mstdnt_message* message);
|
||||
|
||||
#endif // LISTS_H
|
48
src/cookie.c
|
@ -16,10 +16,10 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <fcgi_stdio.h>
|
||||
#include "cookie.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "cookie.h"
|
||||
#include "env.h"
|
||||
|
||||
enum cookie_state
|
||||
{
|
||||
|
@ -29,16 +29,14 @@ enum cookie_state
|
|||
STATE_V_START,
|
||||
};
|
||||
|
||||
char* read_cookies_env(struct cookie_values* cookies)
|
||||
char* read_cookies_env(REQUEST_T req, struct cookie_values* cookies)
|
||||
{
|
||||
struct http_cookie_info info;
|
||||
char* cookies_env = getenv("HTTP_COOKIE");
|
||||
|
||||
// Is it even work bothering with?
|
||||
char* cookies_env = GET_ENV("HTTP_COOKIE", req);
|
||||
if (!cookies_env)
|
||||
return NULL;
|
||||
|
||||
char* cookies_str = malloc(strlen(cookies_env) + 1);
|
||||
char* cookies_str = tb_malloc(strlen(cookies_env) + 1);
|
||||
if (!cookies_str)
|
||||
{
|
||||
perror("malloc");
|
||||
|
@ -52,12 +50,15 @@ char* read_cookies_env(struct cookie_values* cookies)
|
|||
{ "access_token", &(cookies->access_token), key_string },
|
||||
{ "logged_in", &(cookies->logged_in), key_string },
|
||||
{ "theme", &(cookies->theme), key_string },
|
||||
{ "lang", &(cookies->lang), key_int },
|
||||
{ "instance_url", &(cookies->instance_url), key_string },
|
||||
{ "background_url", &(cookies->background_url), key_string },
|
||||
{ "interact_img", &(cookies->interact_img), key_int },
|
||||
{ "client_id", &(cookies->client_id), key_string },
|
||||
{ "client_secret", &(cookies->client_secret), key_string },
|
||||
{ "themeclr", &(cookies->themeclr), key_int },
|
||||
{ "jsactions", &(cookies->jsactions), key_int },
|
||||
{ "notifembed", &(cookies->notif_embed), key_int },
|
||||
{ "jsreply", &(cookies->jsreply), key_int },
|
||||
{ "jslive", &(cookies->jslive), key_int },
|
||||
{ "js", &(cookies->js), key_int },
|
||||
|
@ -66,6 +67,7 @@ char* read_cookies_env(struct cookie_values* cookies)
|
|||
{ "statdope", &(cookies->stat_dope), key_int },
|
||||
{ "statoneclicksoftware", &(cookies->stat_oneclicksoftware), key_int },
|
||||
{ "statemojolikes", &(cookies->stat_emojo_likes), key_int },
|
||||
{ "sidebaropacity", &(cookies->sidebar_opacity), key_int },
|
||||
{ "stathidemuted", &(cookies->stat_hide_muted), key_int },
|
||||
{ "instanceshowshoutbox", &(cookies->instance_show_shoutbox), key_int },
|
||||
{ "instancepanel", &(cookies->instance_panel), key_int },
|
||||
|
@ -160,3 +162,35 @@ int cookie_get_val(char* src, char* key, struct http_cookie_info* info)
|
|||
|
||||
return 1;
|
||||
}
|
||||
|
||||
HV* perlify_cookies(struct cookie_values* cookies)
|
||||
{
|
||||
HV* ssn_cookies_hv = newHV();
|
||||
|
||||
hv_stores(ssn_cookies_hv, "lang", newSViv(keyint(cookies->lang)));
|
||||
hv_stores(ssn_cookies_hv, "interact_img", newSViv(keyint(cookies->interact_img)));
|
||||
hv_stores(ssn_cookies_hv, "themeclr", newSViv(keyint(cookies->themeclr)));
|
||||
hv_stores(ssn_cookies_hv, "jsactions", newSViv(keyint(cookies->jsactions)));
|
||||
hv_stores(ssn_cookies_hv, "jsreply", newSViv(keyint(cookies->jsreply)));
|
||||
hv_stores(ssn_cookies_hv, "jslive", newSViv(keyint(cookies->jslive)));
|
||||
hv_stores(ssn_cookies_hv, "js", newSViv(keyint(cookies->js)));
|
||||
hv_stores(ssn_cookies_hv, "interact_img", newSViv(keyint(cookies->interact_img)));
|
||||
hv_stores(ssn_cookies_hv, "statattachments", newSViv(keyint(cookies->stat_attachments)));
|
||||
hv_stores(ssn_cookies_hv, "statgreentexts", newSViv(keyint(cookies->stat_greentexts)));
|
||||
hv_stores(ssn_cookies_hv, "statdope", newSViv(keyint(cookies->stat_dope)));
|
||||
hv_stores(ssn_cookies_hv, "statoneclicksoftware", newSViv(keyint(cookies->stat_oneclicksoftware)));
|
||||
hv_stores(ssn_cookies_hv, "statemojolikes", newSViv(keyint(cookies->stat_emojo_likes)));
|
||||
hv_stores(ssn_cookies_hv, "stathidemuted", newSViv(keyint(cookies->stat_hide_muted)));
|
||||
hv_stores(ssn_cookies_hv, "instanceshowshoutbox", newSViv(keyint(cookies->instance_show_shoutbox)));
|
||||
hv_stores(ssn_cookies_hv, "instancepanel", newSViv(keyint(cookies->instance_panel)));
|
||||
hv_stores(ssn_cookies_hv, "notifembed", newSViv(keyint(cookies->notif_embed)));
|
||||
hv_stores(ssn_cookies_hv, "access_token", newSVpv(keystr(cookies->access_token), 0));
|
||||
hv_stores(ssn_cookies_hv, "logged_in", newSVpv(keystr(cookies->logged_in), 0));
|
||||
hv_stores(ssn_cookies_hv, "theme", newSVpv(keystr(cookies->theme), 0));
|
||||
hv_stores(ssn_cookies_hv, "instance_url", newSVpv(keystr(cookies->instance_url), 0));
|
||||
hv_stores(ssn_cookies_hv, "background_url", newSVpv(keystr(cookies->background_url), 0));
|
||||
hv_stores(ssn_cookies_hv, "client_id", newSVpv(keystr(cookies->client_id), 0));
|
||||
hv_stores(ssn_cookies_hv, "client_secret", newSVpv(keystr(cookies->client_secret), 0));
|
||||
|
||||
return ssn_cookies_hv;
|
||||
}
|
||||
|
|
11
src/cookie.h
|
@ -19,13 +19,17 @@
|
|||
#ifndef COOKIE_H
|
||||
#define COOKIE_H
|
||||
#include <stddef.h>
|
||||
#include "global_perl.h"
|
||||
#include "key.h"
|
||||
#include "cgi.h"
|
||||
#include "request.h"
|
||||
|
||||
struct cookie_values
|
||||
{
|
||||
struct key access_token;
|
||||
struct key logged_in;
|
||||
struct key theme;
|
||||
struct key lang;
|
||||
struct key instance_url;
|
||||
struct key background_url;
|
||||
struct key client_id;
|
||||
|
@ -35,6 +39,7 @@ struct cookie_values
|
|||
struct key jsreply;
|
||||
struct key jslive;
|
||||
struct key js;
|
||||
struct key interact_img;
|
||||
struct key stat_attachments;
|
||||
struct key stat_greentexts;
|
||||
struct key stat_dope;
|
||||
|
@ -43,6 +48,8 @@ struct cookie_values
|
|||
struct key stat_hide_muted;
|
||||
struct key instance_show_shoutbox;
|
||||
struct key instance_panel;
|
||||
struct key sidebar_opacity;
|
||||
struct key notif_embed;
|
||||
};
|
||||
|
||||
struct http_cookie_info
|
||||
|
@ -54,7 +61,9 @@ struct http_cookie_info
|
|||
|
||||
// Stupidly fast simple cookie parser
|
||||
char* parse_cookies(char* begin, struct http_cookie_info* info);
|
||||
char* read_cookies_env(struct cookie_values* cookies);
|
||||
char* read_cookies_env(REQUEST_T req, struct cookie_values* cookies);
|
||||
int cookie_get_val(char* src, char* key, struct http_cookie_info* info);
|
||||
|
||||
HV* perlify_cookies(struct cookie_values* cookies);
|
||||
|
||||
#endif // COOKIE_H
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#include <fcgi_stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "global_perl.h"
|
||||
#include "easprintf.h"
|
||||
|
||||
int evasprintf(char** ret, const char* format, va_list ap)
|
||||
|
@ -28,7 +29,7 @@ int evasprintf(char** ret, const char* format, va_list ap)
|
|||
int sz = vsnprintf(NULL, 0, format, ap);
|
||||
va_end(ap);
|
||||
|
||||
*ret = malloc(sz + 1);
|
||||
*ret = tb_malloc(sz + 1);
|
||||
if(*ret == NULL)
|
||||
{
|
||||
perror("malloc");
|
||||
|
|
173
src/emoji.c
|
@ -16,48 +16,26 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "emoji.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "base_page.h"
|
||||
#include "string.h"
|
||||
#include "emoji.h"
|
||||
#include "easprintf.h"
|
||||
#include "string_helpers.h"
|
||||
|
||||
// Pages
|
||||
#include "../static/emoji.chtml"
|
||||
#include "../static/emoji_picker.chtml"
|
||||
|
||||
char* emojify(char* content, struct mstdnt_emoji* emos, size_t emos_len)
|
||||
enum emoji_categories
|
||||
{
|
||||
if (!content) return NULL;
|
||||
size_t sc_len;
|
||||
char* oldres = NULL;
|
||||
char* res = content;
|
||||
char* emoji_url_str;
|
||||
char* coloned;
|
||||
for (size_t i = 0; i < emos_len; ++i)
|
||||
{
|
||||
oldres = res;
|
||||
|
||||
// Add colons around string
|
||||
sc_len = strlen(emos[i].shortcode);
|
||||
// 3 = \0 and two :
|
||||
coloned = malloc(sc_len+3);
|
||||
coloned[0] = ':';
|
||||
strncpy(coloned + 1, emos[i].shortcode, sc_len);
|
||||
coloned[sc_len+1] = ':';
|
||||
coloned[sc_len+2] = '\0';
|
||||
|
||||
easprintf(&emoji_url_str, "<img class=\"emoji\" src=\"%s\">", emos[i].url);
|
||||
|
||||
res = strrepl(res, coloned, emoji_url_str, STRREPL_ALL);
|
||||
if (oldres != content && res != oldres) free(oldres);
|
||||
// Cleanup
|
||||
free(emoji_url_str);
|
||||
free(coloned);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
EMO_CAT_SMILEYS,
|
||||
EMO_CAT_ANIMALS,
|
||||
EMO_CAT_FOOD,
|
||||
EMO_CAT_TRAVEL,
|
||||
EMO_CAT_ACTIVITIES,
|
||||
EMO_CAT_OBJECTS,
|
||||
EMO_CAT_SYMBOLS,
|
||||
EMO_CAT_FLAGS,
|
||||
EMO_CAT_LEN
|
||||
};
|
||||
|
||||
struct construct_emoji_picker_args
|
||||
{
|
||||
|
@ -65,64 +43,97 @@ struct construct_emoji_picker_args
|
|||
unsigned index;
|
||||
};
|
||||
|
||||
char* construct_emoji(struct emoji_info* emoji, char* status_id, int* size)
|
||||
char* construct_emoji(struct emoji_info* emoji, char* status_id, size_t* size)
|
||||
{
|
||||
char* emoji_html;
|
||||
|
||||
if (!emoji)
|
||||
return NULL;
|
||||
char* emoji_str;
|
||||
|
||||
size_t s = easprintf(&emoji_html, data_emoji_html,
|
||||
status_id, emoji->codes, emoji->codes);
|
||||
|
||||
if (size) *size = s;
|
||||
return emoji_html;
|
||||
if (status_id)
|
||||
*size = easprintf(&emoji_str, "<a href=\"/status/%s/react/%s\" class=\"emoji\">%s</a>",
|
||||
status_id, emoji->codes, emoji->codes);
|
||||
else
|
||||
*size = easprintf(&emoji_str, "<span class=\"emoji\">%s</span>", emoji->codes);
|
||||
return emoji_str;
|
||||
}
|
||||
|
||||
static char* construct_emoji_voidwrap(void* passed, size_t index, int* res)
|
||||
static char* construct_emoji_voidwrap(void* passed, size_t index, size_t* res)
|
||||
{
|
||||
struct construct_emoji_picker_args* args = passed;
|
||||
size_t calc_index = index + args->index;
|
||||
return calc_index < 0 || calc_index >= emojos_size ? NULL :
|
||||
construct_emoji(emojos + calc_index, args->status_id, res);
|
||||
return construct_emoji(emojos + calc_index, args->status_id, res);
|
||||
}
|
||||
|
||||
char* construct_emoji_picker(char* status_id, unsigned index, size_t* size)
|
||||
#define EMOJI_PICKER_ARGS(this_index) { .status_id = status_id, .index = this_index }
|
||||
|
||||
void content_emoji_picker(PATH_ARGS)
|
||||
{
|
||||
struct construct_emoji_picker_args args = {
|
||||
.status_id = status_id,
|
||||
.index = index
|
||||
};
|
||||
char* emoji_picker_html;
|
||||
char* emojis;
|
||||
char* picker = construct_emoji_picker(NULL, NULL);
|
||||
|
||||
emojis = construct_func_strings(construct_emoji_voidwrap, &args, EMOJI_FACTOR_NUM, NULL);
|
||||
send_result(req, NULL, NULL, picker, 0);
|
||||
|
||||
size_t s = easprintf(&emoji_picker_html, data_emoji_picker_html,
|
||||
ACTIVE_CONDITION(index >= 0 && index < EMOJO_CAT_ANIMALS),
|
||||
EMOJO_CAT_ANIMALS,
|
||||
ACTIVE_CONDITION(index >= EMOJO_CAT_ANIMALS && index < EMOJO_CAT_FOOD),
|
||||
EMOJO_CAT_FOOD,
|
||||
ACTIVE_CONDITION(index >= EMOJO_CAT_FOOD && index < EMOJO_CAT_TRAVEL),
|
||||
EMOJO_CAT_TRAVEL,
|
||||
ACTIVE_CONDITION(index >= EMOJO_CAT_TRAVEL && index < EMOJO_CAT_ACTIVITIES),
|
||||
EMOJO_CAT_ACTIVITIES,
|
||||
ACTIVE_CONDITION(index >= EMOJO_CAT_ACTIVITIES && index < EMOJO_CAT_OBJECTS),
|
||||
EMOJO_CAT_OBJECTS,
|
||||
ACTIVE_CONDITION(index >= EMOJO_CAT_OBJECTS && index < EMOJO_CAT_SYMBOLS),
|
||||
EMOJO_CAT_SYMBOLS,
|
||||
ACTIVE_CONDITION(index >= EMOJO_CAT_SYMBOLS && index < EMOJO_CAT_FLAGS),
|
||||
EMOJO_CAT_FLAGS,
|
||||
ACTIVE_CONDITION(index >= EMOJO_CAT_FLAGS && index < emojos_size),
|
||||
emojis ? emojis : "",
|
||||
// Index movements
|
||||
status_id,
|
||||
index > 0 ? index - EMOJI_FACTOR_NUM : 0,
|
||||
0 > index - EMOJI_FACTOR_NUM ? "disabled" : "",
|
||||
status_id,
|
||||
index + EMOJI_FACTOR_NUM);
|
||||
free(emojis);
|
||||
|
||||
if (size) *size = s;
|
||||
return emoji_picker_html;
|
||||
tb_free(picker);
|
||||
}
|
||||
|
||||
char* construct_emoji_picker(char* status_id, size_t* size)
|
||||
{
|
||||
|
||||
char* emoji_picker_html;
|
||||
|
||||
struct construct_emoji_picker_args args[EMO_CAT_LEN] = {
|
||||
EMOJI_PICKER_ARGS(EMOJO_CAT_SMILEY),
|
||||
EMOJI_PICKER_ARGS(EMOJO_CAT_ANIMALS),
|
||||
EMOJI_PICKER_ARGS(EMOJO_CAT_FOOD),
|
||||
EMOJI_PICKER_ARGS(EMOJO_CAT_TRAVEL),
|
||||
EMOJI_PICKER_ARGS(EMOJO_CAT_ACTIVITIES),
|
||||
EMOJI_PICKER_ARGS(EMOJO_CAT_OBJECTS),
|
||||
EMOJI_PICKER_ARGS(EMOJO_CAT_SYMBOLS),
|
||||
EMOJI_PICKER_ARGS(EMOJO_CAT_FLAGS),
|
||||
};
|
||||
|
||||
char* emojis[EMO_CAT_LEN];
|
||||
size_t len[EMO_CAT_LEN];
|
||||
|
||||
// TODO refactor to use #define lol
|
||||
emojis[EMO_CAT_SMILEYS] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_SMILEYS, EMOJO_CAT_ANIMALS - EMOJO_CAT_SMILEY, len);
|
||||
emojis[EMO_CAT_ANIMALS] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_ANIMALS, EMOJO_CAT_FOOD - EMOJO_CAT_ANIMALS, len + 1);
|
||||
emojis[EMO_CAT_FOOD] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_FOOD, EMOJO_CAT_TRAVEL - EMOJO_CAT_FOOD, len + 2);
|
||||
emojis[EMO_CAT_TRAVEL] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_TRAVEL, EMOJO_CAT_ACTIVITIES - EMOJO_CAT_TRAVEL, len + 3);
|
||||
emojis[EMO_CAT_ACTIVITIES] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_ACTIVITIES, EMOJO_CAT_OBJECTS - EMOJO_CAT_ACTIVITIES, len + 4);
|
||||
emojis[EMO_CAT_OBJECTS] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_OBJECTS, EMOJO_CAT_SYMBOLS - EMOJO_CAT_OBJECTS, len + 5);
|
||||
emojis[EMO_CAT_SYMBOLS] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_SYMBOLS, EMOJO_CAT_FLAGS - EMOJO_CAT_SYMBOLS, len + 6);
|
||||
emojis[EMO_CAT_FLAGS] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_FLAGS, EMOJO_CAT_MAX - EMOJO_CAT_FLAGS, len + 7);
|
||||
|
||||
PERL_STACK_INIT;
|
||||
XPUSHs(newRV_noinc((SV*)template_files));
|
||||
AV* av = newAV();
|
||||
av_extend(av, EMO_CAT_LEN);
|
||||
for (int i = 0; i < EMO_CAT_LEN; ++i)
|
||||
{
|
||||
av_store(av, i, newSVpv(emojis[i], 0));
|
||||
}
|
||||
mXPUSHs(newRV_noinc((SV*)av));
|
||||
PERL_STACK_SCALAR_CALL("emojis::emoji_picker");
|
||||
|
||||
char* dup = PERL_GET_STACK_EXIT;
|
||||
|
||||
// Cleanup
|
||||
for (size_t i = 0; i < EMO_CAT_LEN; ++i)
|
||||
tb_free(emojis[i]);
|
||||
return dup;
|
||||
}
|
||||
|
||||
HV* perlify_emoji(const struct mstdnt_emoji* const emoji)
|
||||
{
|
||||
if (!emoji) return NULL;
|
||||
HV* emoji_hv = newHV();
|
||||
hvstores_str(emoji_hv, "shortcode", emoji->shortcode);
|
||||
hvstores_str(emoji_hv, "url", emoji->url);
|
||||
hvstores_str(emoji_hv, "static_url", emoji->static_url);
|
||||
hvstores_int(emoji_hv, "visible_in_picker", emoji->visible_in_picker);
|
||||
hvstores_str(emoji_hv, "category", emoji->category);
|
||||
return emoji_hv;
|
||||
}
|
||||
|
||||
PERLIFY_MULTI(emoji, emojis, mstdnt_emoji)
|
||||
|
||||
|
|
13
src/emoji.h
|
@ -18,8 +18,11 @@
|
|||
|
||||
#ifndef EMOJI_H
|
||||
#define EMOJI_H
|
||||
#include <stddef.h>
|
||||
#include <mastodont.h>
|
||||
#include "global_perl.h"
|
||||
#include "emoji_codes.h"
|
||||
#include "path.h"
|
||||
|
||||
#define EMOJI_FACTOR_NUM 32
|
||||
|
||||
|
@ -30,8 +33,12 @@ enum emoji_picker_cat
|
|||
EMOJI_CAT_FACES,
|
||||
};
|
||||
|
||||
char* emojify(char* content, struct mstdnt_emoji* emos, size_t emos_len);
|
||||
char* construct_emoji(struct emoji_info* emoji, char* status_id, int* size);
|
||||
char* construct_emoji_picker(char* status_id, unsigned index, size_t* size);
|
||||
char* construct_emoji(struct emoji_info* emoji, char* status_id, size_t* size);
|
||||
void content_emoji_picker(PATH_ARGS);
|
||||
char* construct_emoji_picker(char* status_id, size_t* size);
|
||||
|
||||
// Perl
|
||||
HV* perlify_emoji(const struct mstdnt_emoji* const emoji);
|
||||
AV* perlify_emojis(const struct mstdnt_emoji* const emos, size_t len);
|
||||
|
||||
#endif // EMOJI_H
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#define EMOJO_CAT_OBJECTS 3715
|
||||
#define EMOJO_CAT_SYMBOLS 4014
|
||||
#define EMOJO_CAT_FLAGS 4315
|
||||
#define EMOJO_CAT_MAX 4590
|
||||
static struct emoji_info emojos[] = {{"😀","grinning face" }, /*0 : Smileys & Emotion*/
|
||||
{"😃","grinning face with big eyes" }, /*1 : Smileys & Emotion*/
|
||||
{"😄","grinning face with smiling eyes" }, /*2 : Smileys & Emotion*/
|
||||
|
|
|
@ -22,46 +22,17 @@
|
|||
#include <stdlib.h>
|
||||
#include "easprintf.h"
|
||||
|
||||
// Templates
|
||||
#include "../static/emoji_reaction.chtml"
|
||||
#include "../static/emoji_reactions.chtml"
|
||||
|
||||
struct construct_emoji_reactions_args
|
||||
HV* perlify_emoji_reaction(const struct mstdnt_emoji_reaction* const emoji)
|
||||
{
|
||||
struct mstdnt_emoji_reaction* emojis;
|
||||
char* id;
|
||||
};
|
||||
|
||||
char* construct_emoji_reaction(char* id, struct mstdnt_emoji_reaction* emo, int* str_size)
|
||||
{
|
||||
char* emo_html;
|
||||
|
||||
size_t s = easprintf(&emo_html, data_emoji_reaction_html,
|
||||
config_url_prefix, id, emo->name, emo->me ? "active" : "", emo->name, emo->count);
|
||||
if (str_size) *str_size = s;
|
||||
return emo_html;
|
||||
if (!emoji) return NULL;
|
||||
HV* emoji_hv = newHV();
|
||||
hvstores_str(emoji_hv, "name", emoji->name);
|
||||
hvstores_str(emoji_hv, "url", emoji->url);
|
||||
hvstores_str(emoji_hv, "static_url", emoji->static_url);
|
||||
hvstores_int(emoji_hv, "count", emoji->count);
|
||||
hvstores_int(emoji_hv, "me", emoji->me);
|
||||
return emoji_hv;
|
||||
}
|
||||
|
||||
static char* construct_emoji_reactions_voidwrap(void* passed, size_t index, int* res)
|
||||
{
|
||||
struct construct_emoji_reactions_args* args = passed;
|
||||
return construct_emoji_reaction(args->id, args->emojis + index, res);
|
||||
}
|
||||
|
||||
char* construct_emoji_reactions(char* id, struct mstdnt_emoji_reaction* emos, size_t emos_len, size_t* str_size)
|
||||
{
|
||||
size_t elements_size;
|
||||
struct construct_emoji_reactions_args args = {
|
||||
.emojis = emos,
|
||||
.id = id
|
||||
};
|
||||
char* elements = construct_func_strings(construct_emoji_reactions_voidwrap, &args, emos_len, &elements_size);
|
||||
char* emos_view;
|
||||
|
||||
size_t s = easprintf(&emos_view, data_emoji_reactions_html, elements);
|
||||
if (str_size) *str_size = s;
|
||||
// Cleanup
|
||||
free(elements);
|
||||
return emos_view;
|
||||
}
|
||||
PERLIFY_MULTI(emoji_reaction, emoji_reactions, mstdnt_emoji_reaction)
|
||||
|
||||
|
|
|
@ -19,8 +19,10 @@
|
|||
#ifndef EMOJI_REACTION_H
|
||||
#define EMOJI_REACTION_H
|
||||
#include <mastodont.h>
|
||||
#include "global_perl.h"
|
||||
|
||||
char* construct_emoji_reaction(char* id, struct mstdnt_emoji_reaction* emo, int* str_len);
|
||||
char* construct_emoji_reactions(char* id, struct mstdnt_emoji_reaction* emos, size_t emos_len, size_t* str_len);
|
||||
// Perl
|
||||
HV* perlify_emoji_reaction(const struct mstdnt_emoji_reaction* const emoji);
|
||||
AV* perlify_emoji_reactions(const struct mstdnt_emoji_reaction* const emos, size_t len);
|
||||
|
||||
#endif // EMOJI_REACTION_H
|
||||
|
|
|
@ -16,13 +16,13 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TYPE_STRING_H
|
||||
#define TYPE_STRING_H
|
||||
#include <mastodont.h>
|
||||
#include "l10n.h"
|
||||
#ifndef ENV_H
|
||||
#define ENV_H
|
||||
|
||||
const char* notification_type_svg(mstdnt_notification_t type);
|
||||
const char* notification_type_str(mstdnt_notification_t type);
|
||||
const char* notification_type_compact_str(mstdnt_notification_t type);
|
||||
#ifdef SINGLE_THREADED
|
||||
#define GET_ENV(var, reqp) getenv(var)
|
||||
#else
|
||||
#define GET_ENV(var, reqp) FCGX_GetParam(var, req->envp)
|
||||
#endif
|
||||
|
||||
#endif // TYPE_STRING_H
|
||||
#endif /* ENV_H */
|
39
src/error.c
|
@ -21,45 +21,12 @@
|
|||
#include "easprintf.h"
|
||||
#include "l10n.h"
|
||||
|
||||
// Pages
|
||||
#include "../static/error_404.chtml"
|
||||
#include "../static/error.chtml"
|
||||
|
||||
char* construct_error(const char* error, enum error_type type, unsigned pad, size_t* size)
|
||||
void content_not_found(FCGX_Request* req, struct session* ssn, mastodont_t* api, char* path)
|
||||
{
|
||||
char* error_html;
|
||||
char* class;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case E_ERROR:
|
||||
class = "error"; break;
|
||||
case E_WARNING:
|
||||
class = "warning"; break;
|
||||
case E_NOTICE:
|
||||
class = "notice"; break;
|
||||
}
|
||||
size_t s = easprintf(&error_html, data_error_html,
|
||||
class,
|
||||
pad ? "error-pad" : "",
|
||||
error ? error : "An error occured");
|
||||
if (size) *size = s;
|
||||
return error_html;
|
||||
}
|
||||
|
||||
void content_not_found(struct session* ssn, mastodont_t* api, char* path)
|
||||
{
|
||||
char* page;
|
||||
easprintf(&page,
|
||||
data_error_404_html,
|
||||
L10N[L10N_EN_US][L10N_PAGE_NOT_FOUND]);
|
||||
|
||||
struct base_page b = {
|
||||
.locale = L10N_EN_US,
|
||||
.content = page,
|
||||
.content = "Content not found",
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
render_base_page(&b, ssn, api);
|
||||
free(page);
|
||||
render_base_page(&b, req, ssn, api);
|
||||
}
|
||||
|
|
11
src/error.h
|
@ -21,15 +21,8 @@
|
|||
#include <mastodont.h>
|
||||
#include <stddef.h>
|
||||
#include "session.h"
|
||||
#include "path.h"
|
||||
|
||||
enum error_type
|
||||
{
|
||||
E_ERROR,
|
||||
E_WARNING,
|
||||
E_NOTICE
|
||||
};
|
||||
|
||||
char* construct_error(const char* error, enum error_type type, unsigned pad, size_t* size);
|
||||
void content_not_found(struct session* ssn, mastodont_t* api, char* path);
|
||||
void content_not_found(FCGX_Request* req, struct session* ssn, mastodont_t* api, char* path);
|
||||
|
||||
#endif // ERROR_H
|
||||
|
|
|
@ -16,18 +16,24 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "../config.h"
|
||||
#include "global_cache.h"
|
||||
|
||||
struct global_cache g_cache = { 0 };
|
||||
struct global_cache g_cache = { {} };
|
||||
|
||||
void load_instance_info_cache(mastodont_t* api)
|
||||
{
|
||||
mastodont_instance_panel(api, &(g_cache.panel_html));
|
||||
mastodont_terms_of_service(api, &(g_cache.tos_html));
|
||||
struct mstdnt_args m_args = {
|
||||
.url = config_instance_url,
|
||||
.token = 0,
|
||||
.flags = config_library_flags,
|
||||
};
|
||||
mstdnt_instance_panel(api, &m_args, &(g_cache.panel_html));
|
||||
mstdnt_terms_of_service(api, &m_args, &(g_cache.tos_html));
|
||||
}
|
||||
|
||||
void free_instance_info_cache()
|
||||
{
|
||||
mastodont_fetch_results_cleanup(&(g_cache.panel_html));
|
||||
mastodont_fetch_results_cleanup(&(g_cache.tos_html));
|
||||
mstdnt_fetch_results_cleanup(&(g_cache.panel_html));
|
||||
mstdnt_fetch_results_cleanup(&(g_cache.tos_html));
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
#ifndef GLOBAL_CACHE_H
|
||||
#define GLOBAL_CACHE_H
|
||||
#include "mastodont.h"
|
||||
#include <mastodont.h>
|
||||
|
||||
struct global_cache
|
||||
{
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Treebird - Lightweight frontend for Pleroma
|
||||
* Copyright (C) 2022 Nekobit
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "global_perl.h"
|
||||
|
||||
/* TODO let's generate this file dynamically with a Perl script? */
|
||||
#include "../templates/main.ctt"
|
||||
#include "../templates/notification.ctt"
|
||||
#include "../templates/status.ctt"
|
||||
#include "../templates/content_status.ctt"
|
||||
#include "../templates/emoji_picker.ctt"
|
||||
#include "../templates/attachment.ctt"
|
||||
#include "../templates/emoji.ctt"
|
||||
#include "../templates/postbox.ctt"
|
||||
#include "../templates/timeline.ctt"
|
||||
#include "../templates/account.ctt"
|
||||
#include "../templates/account_statuses.ctt"
|
||||
#include "../templates/account_scrobbles.ctt"
|
||||
#include "../templates/content_notifs.ctt"
|
||||
#include "../templates/content_lists.ctt"
|
||||
#include "../templates/navigation.ctt"
|
||||
#include "../templates/accounts.ctt"
|
||||
#include "../templates/account_item.ctt"
|
||||
#include "../templates/content_search.ctt"
|
||||
#include "../templates/search_accounts.ctt"
|
||||
#include "../templates/search_statuses.ctt"
|
||||
#include "../templates/search_tags.ctt"
|
||||
#include "../templates/search.ctt"
|
||||
#include "../templates/content_chats.ctt"
|
||||
#include "../templates/chat.ctt"
|
||||
#include "../templates/config_general.ctt"
|
||||
#include "../templates/config_appearance.ctt"
|
||||
#include "../templates/embed.ctt"
|
||||
#include "../templates/notifs_embed.ctt"
|
||||
#include "../templates/about.ctt"
|
||||
#include "../templates/license.ctt"
|
||||
#include "../templates/login.ctt"
|
||||
#include "../templates/status_interactions.ctt"
|
||||
#include "../templates/memory.ctt"
|
||||
|
||||
PerlInterpreter* my_perl;
|
||||
HV* template_files;
|
||||
pthread_mutex_t perllock_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
void init_template_files(pTHX)
|
||||
{
|
||||
template_files = newHV();
|
||||
|
||||
hv_stores(template_files, "main.tt", newSVpv(data_main_tt, data_main_tt_size));
|
||||
hv_stores(template_files, "notification.tt", newSVpv(data_notification_tt, data_notification_tt_size));
|
||||
hv_stores(template_files, "status.tt", newSVpv(data_status_tt, data_status_tt_size));
|
||||
hv_stores(template_files, "content_status.tt", newSVpv(data_content_status_tt, data_content_status_tt_size));
|
||||
hv_stores(template_files, "emoji_picker.tt", newSVpv(data_emoji_picker_tt, data_emoji_picker_tt_size));
|
||||
hv_stores(template_files, "attachment.tt", newSVpv(data_attachment_tt, data_attachment_tt_size));
|
||||
hv_stores(template_files, "emoji.tt", newSVpv(data_emoji_tt, data_emoji_tt_size));
|
||||
hv_stores(template_files, "postbox.tt", newSVpv(data_postbox_tt, data_postbox_tt_size));
|
||||
hv_stores(template_files, "timeline.tt", newSVpv(data_timeline_tt, data_timeline_tt_size));
|
||||
hv_stores(template_files, "account.tt", newSVpv(data_account_tt, data_account_tt_size));
|
||||
hv_stores(template_files, "account_statuses.tt", newSVpv(data_account_statuses_tt, data_account_statuses_tt_size));
|
||||
hv_stores(template_files, "account_scrobbles.tt", newSVpv(data_account_scrobbles_tt, data_account_scrobbles_tt_size));
|
||||
hv_stores(template_files, "content_notifs.tt", newSVpv(data_content_notifs_tt, data_content_notifs_tt_size));
|
||||
hv_stores(template_files, "content_lists.tt", newSVpv(data_content_lists_tt, data_content_lists_tt_size));
|
||||
hv_stores(template_files, "navigation.tt", newSVpv(data_navigation_tt, data_navigation_tt_size));
|
||||
hv_stores(template_files, "accounts.tt", newSVpv(data_accounts_tt, data_accounts_tt_size));
|
||||
hv_stores(template_files, "account_item.tt", newSVpv(data_account_item_tt, data_account_item_tt_size));
|
||||
hv_stores(template_files, "content_search.tt", newSVpv(data_content_search_tt, data_content_search_tt_size));
|
||||
hv_stores(template_files, "search.tt", newSVpv(data_search_tt, data_search_tt_size));
|
||||
hv_stores(template_files, "search_accounts.tt", newSVpv(data_search_accounts_tt, data_search_accounts_tt_size));
|
||||
hv_stores(template_files, "search_statuses.tt", newSVpv(data_search_statuses_tt, data_search_statuses_tt_size));
|
||||
hv_stores(template_files, "search_tags.tt", newSVpv(data_search_tags_tt, data_search_tags_tt_size));
|
||||
hv_stores(template_files, "content_chats.tt", newSVpv(data_content_chats_tt, data_content_chats_tt_size));
|
||||
hv_stores(template_files, "chat.tt", newSVpv(data_chat_tt, data_chat_tt_size));
|
||||
hv_stores(template_files, "config_general.tt", newSVpv(data_config_general_tt, data_config_general_tt_size));
|
||||
hv_stores(template_files, "config_appearance.tt", newSVpv(data_config_appearance_tt, data_config_appearance_tt_size));
|
||||
hv_stores(template_files, "embed.tt", newSVpv(data_embed_tt, data_embed_tt_size));
|
||||
hv_stores(template_files, "notifs_embed.tt", newSVpv(data_notifs_embed_tt, data_notifs_embed_tt_size));
|
||||
hv_stores(template_files, "about.tt", newSVpv(data_about_tt, data_about_tt_size));
|
||||
hv_stores(template_files, "license.tt", newSVpv(data_license_tt, data_license_tt_size));
|
||||
hv_stores(template_files, "login.tt", newSVpv(data_login_tt, data_login_tt_size));
|
||||
hv_stores(template_files, "status_interactions.tt", newSVpv(data_status_interactions_tt, data_status_interactions_tt_size));
|
||||
hv_stores(template_files, "memory.tt", newSVpv(data_memory_tt, data_memory_tt_size));
|
||||
}
|
||||
|
||||
void cleanup_template_files()
|
||||
{
|
||||
hv_undef(template_files);
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Treebird - Lightweight frontend for Pleroma
|
||||
* Copyright (C) 2022 Nekobit
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef GLOBAL_PERL_H
|
||||
#define GLOBAL_PERL_H
|
||||
#include <EXTERN.h>
|
||||
#include <perl.h>
|
||||
#include "memory.h"
|
||||
#include <pthread.h>
|
||||
|
||||
/* SV* tmpsv = newSV(0); \ */
|
||||
/* sv_usepvn_flags(tmpsv, val, strlen(val), SV_HAS_TRAILING_NUL); \ */
|
||||
// Note: val MUST be a pointer to a value, and must end with a \0 (hence SvSETMAGIC)
|
||||
#define hvstores_str(hv, key, val) if (val) { \
|
||||
hv_stores((hv), key, newSVpvn_share(val, strlen(val), 0)); \
|
||||
}
|
||||
#define hvstores_int(hv, key, val) hv_stores((hv), key, newSViv((val)))
|
||||
#define hvstores_ref(hv, key, val) if (1) { \
|
||||
SV* tmp = (SV*)(val); \
|
||||
if (tmp) \
|
||||
hv_stores((hv), key, newRV_noinc(tmp)); \
|
||||
}
|
||||
|
||||
/* Seeing all this shit littered in Treebird's code made me decide to write some macros */
|
||||
#define PERL_STACK_INIT perl_lock(); \
|
||||
dSP; \
|
||||
ENTER; \
|
||||
SAVETMPS; \
|
||||
PUSHMARK(SP)
|
||||
|
||||
#define PERL_STACK_SCALAR_CALL(name) PUTBACK; \
|
||||
call_pv((name), G_SCALAR); \
|
||||
SPAGAIN
|
||||
|
||||
/* you MUST assign scalar from savesharedsvpv, then free when done */
|
||||
#define PERL_GET_STACK_EXIT savesvpv(POPs); \
|
||||
PUTBACK; \
|
||||
FREETMPS; \
|
||||
LEAVE; \
|
||||
perl_unlock()
|
||||
|
||||
#define PERLIFY_MULTI(type, types, mstype) AV* perlify_##types(const struct mstype* const types, size_t len) { \
|
||||
if (!(types && len)) return NULL; \
|
||||
AV* av = newAV(); \
|
||||
av_extend(av, len-1); \
|
||||
for (size_t i = 0; i < len; ++i) \
|
||||
av_store(av, i, newRV_noinc((SV*)perlify_##type(types + i))); \
|
||||
return av; \
|
||||
}
|
||||
|
||||
|
||||
|
||||
extern PerlInterpreter* my_perl;
|
||||
extern HV* template_files;
|
||||
extern pthread_mutex_t perllock_mutex;
|
||||
|
||||
#ifndef SINGLE_THREADED
|
||||
#define perl_lock() do { pthread_mutex_lock(&perllock_mutex); } while (0)
|
||||
#define perl_unlock() do { pthread_mutex_unlock(&perllock_mutex); } while (0)
|
||||
#else
|
||||
// NOOP
|
||||
#define perl_lock() ;;
|
||||
#define perl_unlock() ;;
|
||||
#endif
|
||||
|
||||
#define ARG_UNDEFINED() do { mXPUSHs(&PL_sv_undef); } while (0)
|
||||
|
||||
void init_template_files(pTHX);
|
||||
void cleanup_template_files();
|
||||
|
||||
#endif /* GLOBAL_PERL_H */
|
|
@ -1,126 +0,0 @@
|
|||
/*
|
||||
* Treebird - Lightweight frontend for Pleroma
|
||||
* Copyright (C) 2022 Nekobit
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "graphsnbars.h"
|
||||
#include "easprintf.h"
|
||||
#include "string_helpers.h"
|
||||
|
||||
// Pages
|
||||
#include "../static/bar.chtml"
|
||||
#include "../static/bar_graph.chtml"
|
||||
|
||||
struct hashtags_graph_args
|
||||
{
|
||||
struct mstdnt_tag* tags;
|
||||
size_t tags_len;
|
||||
unsigned max;
|
||||
time_t rel_day;
|
||||
size_t days;
|
||||
};
|
||||
|
||||
char* construct_bar_graph_container(char* bars, size_t* size)
|
||||
{
|
||||
char* bar_graph_html;
|
||||
|
||||
size_t s = easprintf(&bar_graph_html, data_bar_graph_html, bars);
|
||||
|
||||
if (size) *size = s;
|
||||
return bar_graph_html;
|
||||
}
|
||||
|
||||
char* construct_bar(float value, int* size)
|
||||
{
|
||||
char* bar_html;
|
||||
|
||||
size_t s = easprintf(&bar_html, data_bar_html,
|
||||
value*100);
|
||||
|
||||
if (size) *size = s;
|
||||
return bar_html;
|
||||
}
|
||||
|
||||
static char* construct_hashgraph_voidwrap(void* passed, size_t index, int* res)
|
||||
{
|
||||
unsigned curr_sum = 0;
|
||||
struct hashtags_graph_args* args = passed;
|
||||
struct mstdnt_tag* tags = args->tags;
|
||||
size_t tags_len = args->tags_len;
|
||||
unsigned max = args->max;
|
||||
time_t rel_day = args->rel_day;
|
||||
size_t days = args->days;
|
||||
|
||||
for (int i = 0; i < tags_len; ++i)
|
||||
{
|
||||
for (int j = 0; j < tags[i].history_len; ++j)
|
||||
{
|
||||
if (tags[i].history[j].day == rel_day-((days-index-1)*86400))
|
||||
curr_sum += tags[i].history[j].uses;
|
||||
}
|
||||
}
|
||||
|
||||
return construct_bar((float)curr_sum / max, res);
|
||||
}
|
||||
|
||||
char* construct_hashtags_graph(struct mstdnt_tag* tags,
|
||||
size_t tags_len,
|
||||
size_t days,
|
||||
size_t* ret_size)
|
||||
{
|
||||
unsigned max_sum = 0;
|
||||
unsigned curr_sum = 0;
|
||||
size_t max_history_len = 0;
|
||||
|
||||
// Get current time at midnight for basis, copy over
|
||||
time_t t = time(NULL);
|
||||
struct tm* mn_ptr = gmtime(&t);
|
||||
struct tm mn;
|
||||
memcpy(&mn, mn_ptr, sizeof(mn));
|
||||
mn.tm_hour = 0;
|
||||
mn.tm_min = 0;
|
||||
mn.tm_sec = 0;
|
||||
time_t rel_day = timegm(&mn);
|
||||
|
||||
// Run a loop through all the hashtags, sum each set up,
|
||||
// then get the largest sum
|
||||
for (size_t i = 0; i < days; ++i)
|
||||
{
|
||||
for (size_t j = 0; j < tags_len && i < tags[j].history_len; ++j)
|
||||
{
|
||||
if (tags[j].history_len > max_history_len)
|
||||
max_history_len = tags[j].history_len;
|
||||
if (tags[j].history[i].day >= rel_day-(i*86400))
|
||||
curr_sum += tags[j].history[i].uses;
|
||||
}
|
||||
|
||||
if (curr_sum > max_sum)
|
||||
max_sum = curr_sum;
|
||||
curr_sum = 0;
|
||||
}
|
||||
|
||||
struct hashtags_graph_args args = {
|
||||
.tags = tags,
|
||||
.tags_len = tags_len,
|
||||
.max = max_sum,
|
||||
.rel_day = rel_day,
|
||||
.days = max_history_len,
|
||||
};
|
||||
|
||||
return construct_func_strings(construct_hashgraph_voidwrap, &args, max_history_len, ret_size);
|
||||
}
|
|
@ -22,44 +22,7 @@
|
|||
#include "easprintf.h"
|
||||
#include "../config.h"
|
||||
|
||||
// Pages
|
||||
#include "../static/hashtag.chtml"
|
||||
#include "../static/hashtag_page.chtml"
|
||||
|
||||
#define TAG_SIZE_INITIAL 12
|
||||
|
||||
static unsigned hashtag_history_daily_uses(size_t max, struct mstdnt_history* history, size_t history_len)
|
||||
{
|
||||
unsigned total = 0;
|
||||
|
||||
for (int i = 0; i < history_len && i < max; ++i)
|
||||
total += history[i].uses;
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
char* construct_hashtag(struct mstdnt_tag* hashtag, int* size)
|
||||
{
|
||||
char* hashtag_html;
|
||||
|
||||
// Lol!
|
||||
unsigned hash_size = TAG_SIZE_INITIAL +
|
||||
CLAMP(hashtag_history_daily_uses(7, hashtag->history, hashtag->history_len)*2, 0, 42);
|
||||
|
||||
size_t s = easprintf(&hashtag_html, data_hashtag_html,
|
||||
config_url_prefix, hashtag->name, hash_size, hashtag->name);
|
||||
|
||||
if (size) *size = s;
|
||||
return hashtag_html;
|
||||
}
|
||||
|
||||
static char* construct_hashtag_voidwrap(void* passed, size_t index, int* res)
|
||||
{
|
||||
return construct_hashtag((struct mstdnt_tag*)passed + index, res);
|
||||
}
|
||||
char* construct_hashtags(struct mstdnt_tag* hashtags, size_t size, size_t* ret_size)
|
||||
{
|
||||
if (!(hashtags && size)) return NULL;
|
||||
return construct_func_strings(construct_hashtag_voidwrap, hashtags, size, ret_size);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
#include <stddef.h>
|
||||
#include <mastodont.h>
|
||||
|
||||
char* construct_hashtag(struct mstdnt_tag* hashtag, int* size);
|
||||
char* construct_hashtags(struct mstdnt_tag* hashtags, size_t size, size_t* ret_size);
|
||||
// TODO?
|
||||
|
||||
#endif /* HASHTAG_H */
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Treebird - Lightweight frontend for Pleroma
|
||||
* Copyright (C) 2022 Nekobit
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "../config.h"
|
||||
#include "helpers.h"
|
||||
|
||||
void set_mstdnt_args(struct mstdnt_args* args, struct session* ssn)
|
||||
{
|
||||
args->url = get_instance(ssn);
|
||||
args->token = get_token(ssn);
|
||||
args->flags = MSTDNT_FLAG_NO_URI_SANITIZE | config_library_flags;
|
||||
}
|
||||
|