Compare commits

...

288 Commits

Author SHA1 Message Date
nekobit c2f859d6bd Remove libpcre from building
FossilOrigin-Name: 855454da6a753dbf833bad72aa3a6292032a24f958c4c9d14c3307c920116e89
2022-10-27 14:54:23 +00:00
nekobit 0ed7b49709 Memory page
FossilOrigin-Name: d99c3ec4b0090f12c8c3a4fbc26254a65ed3d37b2aaa2c7ef3fb45ff345dbe6d
2022-10-17 20:38:40 +00:00
nekobit f2107a803a Use cool memory hooks (bloat)
FossilOrigin-Name: d46dabde38291803f6832582a832ee636ef6cc42de5191854b94ec38a35bcd32
2022-10-17 20:17:07 +00:00
nekobit 64f72cc20f Very cool and useful memory autism
FossilOrigin-Name: 190c1b16d26a67e361d2666e795734667ce9bb12ba4c234af0a1c1b70d06b040
2022-10-17 20:13:32 +00:00
nekobit d0eaca6439 Stress tests
FossilOrigin-Name: a99b17e4812b46eab344023c64ed6cecea81f81ba945da3823d4accb3b337350
2022-10-17 19:18:46 +00:00
nekobit a6a730649d Update JS
FossilOrigin-Name: 6f5b19c5b79c90365430be17127185a22c1a709e6666f405869525c701a17d22
2022-10-17 05:43:47 +00:00
nekobit 0dfa6560c0 Oh.
FossilOrigin-Name: c164d7a14c9105aef9159e8b1403c6cf9433f50e645d30e00d5df622a849efc3
2022-10-16 04:39:52 +00:00
nekobit fcd5abc7a8 Use global mutex
FossilOrigin-Name: 9fccdfd63fb68f851bb69636a9f6a61f6fd0b797bd32827802b7d78d3c9f3dbf
2022-10-16 00:59:28 +00:00
nekobit 4ecb46c745 Fix reply id and remove bloat and break JS
FossilOrigin-Name: b6872706978878f7592658f009043654481520f6fbd6592e01c53481645274e3
2022-10-15 23:04:07 +00:00
nekobit 9c70d7f1b5 fds
FossilOrigin-Name: 5d5981b69775e6a342a9f2980e2c020624e26799dc085ac1d8bdda77400ca5b4
2022-10-15 21:42:24 +00:00
nekobit 9f44fa8623 Change
FossilOrigin-Name: 5a89b703b94bb8ce089d448ef9c0044b97b73ef3c411b499d4aadf96ac786130
2022-10-15 21:34:09 +00:00
nekobit e36f9e05ca Memory optimizations
FossilOrigin-Name: e62800fb343eb8ea0c8e0f1215c7c055bd8a50eac706d74d56ac934415de656d
2022-10-15 21:30:02 +00:00
nekobit 8fdb143303 Improve memory by not duplicating mstdnt perl strings
FossilOrigin-Name: aaa960e3a3c6c89429dbb2210b9693bfbd79f84e824053cee9b5d2eb7dde7f8b
2022-10-15 21:05:44 +00:00
nekobit 4ad40369e9 Use Perl memory methods
FossilOrigin-Name: 1337944567f1c048fd4292a8acdab472328f5fb9559e9e1727ab53b86d895075
2022-10-15 20:45:08 +00:00
nekobit bf8785674f Threaded fixes
FossilOrigin-Name: d81c32b38b46db09fc33f45708d91c99bad5a31655567ad859e35c332c881476
2022-10-15 19:17:59 +00:00
nekobit 115acd3231 Follow new mastodont naming convention
FossilOrigin-Name: f19ecee41392fe5ebc04f1a11a13bba2833c6f3eb2ebca1b310b5ceb3199b95b
2022-10-14 13:39:44 +00:00
nekobit 4733000997 Merge
FossilOrigin-Name: aede803dd7e9334e756798bf609e43bdeb391418a7e4af79ffe23c005a1d7dcf
2022-10-14 13:36:45 +00:00
nekobit 7e77b9566a Improve docs
FossilOrigin-Name: dd9f17a042796f045536f9586710038ec0ffdd759411ab9912e57cdfe17e2f56
2022-10-14 13:35:22 +00:00
nekobit fe114a4995 Fix JS
FossilOrigin-Name: 9d198513fc108e603d50e1f21082a5970f6996504c0541d54e376f9c98a6975c
2022-10-14 02:47:04 +00:00
nekobit cb0b676cd7 Bundle webshitter lib
FossilOrigin-Name: ce5d7f4522f9b7a75ed918b58ba51473975f37d7c9e11a273f5648c481dc14d9
2022-10-14 01:51:43 +00:00
nekobit c2b64cdf33 JS cleanup
FossilOrigin-Name: 3a671f85951c708f37ded7345e0e4207e4e5031b7bd2c840cb91d74288740a99
2022-10-13 18:50:06 +00:00
nekobit 4eb1389418 CSS tweaks and polishing
FossilOrigin-Name: 9d32ddd347cb36062c661df3775a4d7a775e82f9dde7eab9405782f38b876fbb
2022-10-13 17:55:59 +00:00
nekobit bb8dedb37e Uncomment code
FossilOrigin-Name: 86b1b76ca8f2b42a92c7099649f3f8c0f6bb704056645116dac6715aa935e324
2022-10-13 11:32:26 +00:00
nekobit 61b4c20f2e FINALLY fixed the memleak root cause, for good
FossilOrigin-Name: c7f650b4f4efaf66ad769919f2277c46345b79337058a13db732c0d8770e625c
2022-10-13 02:35:35 +00:00
nekobit 682f6e0813 Undef in main
FossilOrigin-Name: bad68ecf59eb93817413d54d47c7ca5a5bdb6b36dba0c9163dc7299b26c31f7a
2022-10-12 19:30:55 +00:00
nekobit a8ac0d3b4c Memory optimizations (in progress)
FossilOrigin-Name: 22237b8da97b3a0053609d84c43b07ac2929beb913f66d8677750eec6b4343cc
2022-10-12 15:11:45 +00:00
nekobit 6e6ca8ad9a Fiddling...
FossilOrigin-Name: 7ac1a6ecafbc849e0510aeccf025ea07d7853f64eb89e87dc19046c8efa36a8c
2022-08-26 18:20:54 +00:00
nekobit e12be1a84b Fix memleak
FossilOrigin-Name: 1d0d189731ac60247707eb08f7bbc422c945916626638e052340c31a23ab4130
2022-08-26 01:49:35 +00:00
nekobit f1ef5c6fa0 Switch to 1 destruct level
FossilOrigin-Name: 5a8202cbb4c52a74b5467c82b721e15b9c8e109d7e954dc659300fc01c684ff4
2022-08-25 17:10:37 +00:00
nekobit 0617cdbd87 Fix notifications memleak
FossilOrigin-Name: d7c1cffde3338a7ef54bdf3355735807e0dab93414c0936ebfc7f92d2861ec86
2022-08-25 01:29:09 +00:00
nekobit f5d4a08d3b Macro for perlifying arrays, fix perl runtime leak
FossilOrigin-Name: 59639b62f7ebed10373fe3aa5ac234a500c14dbf428996395eee43738f156936
2022-08-25 01:01:15 +00:00
nekobit 7390326d2c Cleanup chats correctly
FossilOrigin-Name: 926e998d4538ddd0d601865073069728207a59a5cf69300c158be38e101c201f
2022-08-25 00:14:22 +00:00
nekobit d68d553759 Cleanup chats, cleanup more perl stuff
FossilOrigin-Name: 01a373542dd5377a14612fe998a346a7356a5197afbe07eed96ffbb1e53ae4d2
2022-08-25 00:10:26 +00:00
nekobit 9efd9f9a9f Cleanup Perl runtime leaks
FossilOrigin-Name: 5081c19df88f2dfca0ef18710c47f906de3104a1411275c7826965faf6479317
2022-08-24 23:57:18 +00:00
nekobit bebc9eb58b Memory improvements
FossilOrigin-Name: 74d721f05bd0cb5fd53a5fff34258113afc9087f2083688f733bc3fe5b32e323
2022-08-24 17:12:41 +00:00
nekobit 1b80b589b7 Debug options
FossilOrigin-Name: 00a884ba50b3a7a8d5e002ab07eef2430a705d60c789fa434a578c8f73920134
2022-08-24 13:59:48 +00:00
nekobit d7d52a0a1f DEV Docs
FossilOrigin-Name: e52ecf9d629ce54fd9f6bcd0947b7817a3c9206f21da888525381607d952c9a4
2022-08-24 13:51:39 +00:00
nekobit 62e782c8f7 Fix macros
\nWe could use __VA_OPT__(,), but this is for a very new version of GCC

FossilOrigin-Name: cd18b7747d55d6251bcabad7350629303e313b0f053a06a41fa7b188969b2071
2022-08-24 12:36:19 +00:00
nekobit 06867d36f5 Fix FCGX usage
FossilOrigin-Name: 20d5a7ce7f9a7ed9b1c18ba0f28e264f5a09c57034a05287687be529aa0849cd
2022-08-24 12:18:54 +00:00
nekobit 8d225b2c86 Fix FCGX app issues
FossilOrigin-Name: 0d15195d046dd622b4b10fa5979872781b088b22f4f8528cea3a442439c9d41e
2022-08-24 11:53:58 +00:00
nekobit 8d9d537030 More single threaded macros
FossilOrigin-Name: 78ff7ba472d155fba3fe238e59193053b96dd2792b1b6ff7012ffe15cd4aec0e
2022-08-24 01:49:02 +00:00
nekobit 67a2f90c8b Add single-threaded NOOP
FossilOrigin-Name: 706225b5257311abdb37ed19ee3d4da2b2fe9cbf1aa88841f6f91b4ce66a4490
2022-08-24 01:00:46 +00:00
nekobit dce5df9821 Single threaded ifdef
FossilOrigin-Name: 06ba85d5a4f7912cfce89256d3ff9b3089000c4b1deaddf1c4d42f17c2f25ce1
2022-08-23 18:50:34 +00:00
nekobit d2b9d500b2 Fix
FossilOrigin-Name: f859a15e4a2ac7336622350ee84ed51245a8e14c8bf26cb712022f4756b53c0c
2022-08-23 17:05:26 +00:00
nekobit 78c68f5a92 request macro
FossilOrigin-Name: 772a47d24d0ba6364561f29f8dad44abb63f267458ff893c998047574a99e7ff
2022-08-23 17:01:32 +00:00
nekobit dceb8881bb Emoji picker
Seems to have a bug when using data::dumper, may be deeper, so check it out

FossilOrigin-Name: 2ccee21d230f55f38d2f53b0e8bbff2df711d5db45e1a7451bb10850935baff8
2022-08-22 17:23:16 +00:00
nekobit f916e8d0c7 Show emoji picker
FossilOrigin-Name: 3973f69d88b1e4b7394095ed335f686d2565b5242be79c7ffb1e362f38bdfcde
2022-08-22 16:46:59 +00:00
nekobit 09ddb21d42 Finish cleaning up template stuff
FossilOrigin-Name: 1fa9a720bfca80c96264c4f7263f6de710bee40da23d92f874070de5f32412a7
2022-08-22 13:50:23 +00:00
nekobit 50e76ae712 Restore filec compilation in makefiles
FossilOrigin-Name: a6559f6e98a925371e2c75272f054c4069c89437b9872c80d641d92b800aac37
2022-08-22 12:11:55 +00:00
nekobit 1b347848e8 Quit
FossilOrigin-Name: c62ed43b115d05ed5ceca3b258da63cbdd4081b3f034ff741f7b33e2d1f2daaa
2022-08-22 01:08:35 +00:00
nekobit d033f363c3 Emoji picker and refactoring bullshit still
FossilOrigin-Name: 8ac12640e4940972a2528fb690e7a1736208275dcb053edc9bf418642a82867c
2022-08-20 03:32:29 +00:00
nekobit aae83c2bd9 Cleanup old template shit, good riddance
FossilOrigin-Name: 555fc3773213a18194ffb772aec29855553275a997e38962627f328d5457e9b8
2022-08-20 02:50:31 +00:00
nekobit 3b334562bf Perlify status interactions
FossilOrigin-Name: de6177ca45c0d238fedbc8824f85220736453e0df45f3e2160ea6f027e763fae
2022-08-20 02:43:17 +00:00
nekobit bbdb95187b Convert login page
FossilOrigin-Name: f61018fe4ab711036008fc1b24b27c4c1fa2fa249ee19932d943351f5a9dbc43
2022-08-20 02:26:41 +00:00
nekobit 8fae1ddfe9 Remove test
FossilOrigin-Name: a4b205102959fc9c7c03b3aa2715b97d5bc856666be87f58a8e82ba8aa5426b1
2022-08-20 01:14:24 +00:00
nekobit 549150504f Fix
FossilOrigin-Name: 4c81ab29e0aee66ee0c8efd6cfd97c7f0858eccfe427187f950117b3ee73ab1c
2022-08-19 17:55:41 +00:00
nekobit d0989bed94 Fix function calls
FossilOrigin-Name: 1f071f17ed2014be4dda47899127a39749bc3c82397de6af94923100b40da822
2022-08-19 17:53:08 +00:00
nekobit 52c011968f Refactor search tabs
FossilOrigin-Name: 77ae2a3ad81f7655791d0431d7312dc325da8c292ada210b9a0654d4183740a8
2022-08-19 17:43:42 +00:00
nekobit 9bcf209e5c Search tab results
FossilOrigin-Name: 68d1917ab69702c73a51670aad91abf4e6ff82ebc9278c5e70a148031f00444d
2022-08-19 17:00:53 +00:00
nekobit 785394e435 Redirect search query
FossilOrigin-Name: 92ca8377ab9c47fbd1f3348ad380ac25e0aa8b3367e5349dc63b8028f4da07e0
2022-08-19 16:05:54 +00:00
nekobit 4db0dea58b Broken redirect
FossilOrigin-Name: 72feef8ae694f79bab1eb6295b38e07b3b70cc16722490fac7751a6002a799fe
2022-08-19 15:30:49 +00:00
nekobit 670a4021b8 Perlify meta pages
FossilOrigin-Name: d2af76ca88f30b880bf7d22f8247dc17b370dfa83700adef18798e33b3e85a37
2022-08-19 15:06:19 +00:00
nekobit 6f90b245a4 Fix list crash
\n\nNeeds error handling

FossilOrigin-Name: 5a4fe5967a5ccd65e3811aa5d75f3dd75e8a48ed30a90922ae3a2c82a773028f
2022-08-19 14:55:03 +00:00
nekobit c33d7174f1 Remove navbar top border
On HiDPI displays it had a little gap, maybe due to the gradient since it's glossy now?

FossilOrigin-Name: 244b80a33e291d985a8f707a5b6aa2c9a3bc06364c16016ebb3a363f9744952a
2022-08-19 14:53:24 +00:00
nekobit 2609e9680c Bring back embedded notifications
FossilOrigin-Name: 8eb2f577e9a9de679829d1694231f527dfacf53938ecc52f323021ed52744bfd
2022-08-19 04:46:41 +00:00
nekobit c9eaaac234 Notifications embed
FossilOrigin-Name: 9ceac560758f39950f7edeaa79580d7cd0b212108a197efd7f57b24413c42dac
2022-08-19 03:52:26 +00:00
nekobit 5f32afa0cd Config page
All that is left is we carry current config values

FossilOrigin-Name: d91f87afec897d986b251b80428e97355244c4307d440f8aea9c29803f171755
2022-08-18 22:34:11 +00:00
nekobit dfd49db8d3 Config page boilerplate
FossilOrigin-Name: bc30f815e30d2776fd26c2147963e4358c73f9efc09b64ff527459fc5e5f9319
2022-08-18 22:31:34 +00:00
nekobit ad8819f482 Improve sidebar dropdowns, glossy top bar
FossilOrigin-Name: 967b23f6176189dd87ca4b1a8c8f8e90cd5066e81f138ff741cd40dd9233f5a0
2022-08-18 21:58:41 +00:00
nekobit b49346e059 Switch to new macros
FossilOrigin-Name: 9adf01fb2b7c7ee846235df49ccc874e792196a2779034bde1be4043ca9509a7
2022-08-18 14:58:21 +00:00
nekobit cbeba98890 Add macros for perl stack manipulation
FossilOrigin-Name: b6526e43663896b8e12ffeeec48bdc4ef940fcf73d5c6a8f1a593b9bdb9f9ba8
2022-08-18 13:58:26 +00:00
nekobit 9917ac981e Reverse chat loop
FossilOrigin-Name: b408ab947f0ee6ceff7ff49d04022c5329fb4fba0f4e92581e41598c8c7e3a82
2022-08-18 02:54:33 +00:00
nekobit 56a4146a42 Perlify chats
FossilOrigin-Name: 3386c14b1650d67751762df511b6ed641499890a3992c8e31dbf57ba62efd102
2022-08-18 02:27:05 +00:00
nekobit c9ec49af9f Chats C perl
FossilOrigin-Name: 569bf19f47d6b4b64712a226fa0b60c8dc6d3d7db31f2fd3c49ced9c75bc1646
2022-08-18 02:08:59 +00:00
nekobit eac2f53aad perlify_message(s)
FossilOrigin-Name: ac178b0d18e84d776b7f6ce8ffad5ad900a3d5af9dbdfca4a137d2e9ae5bf1f0
2022-08-17 21:38:28 +00:00
nekobit ce74e7125e Chats
FossilOrigin-Name: 54a80dceb8d34d72d13ae4adc77229d8293fed346908ea4d5fdbac6c4c916b67
2022-08-17 21:35:11 +00:00
nekobit 2aa34fea2a perlify_chat(s)
FossilOrigin-Name: f77180996ce3b881a0395c015338668fb1db76af35329591c8174ded6803a87e
2022-08-17 21:20:07 +00:00
nekobit 35982e5caa ???
FossilOrigin-Name: 0bb1cb9d1808c11b716f644ba0bc150af5fe86efd95153e447a3a8c9073f0e31
2022-08-17 15:28:58 +00:00
nekobit a2848c5df2 Fix reblog fake notification
FossilOrigin-Name: 01cffdb17769dbb6b711e8a3ff7d239b824714e7ba47e5563671b52bcf7401c4
2022-08-17 04:49:44 +00:00
nekobit 93ba8c2737 Show notification menu options on the notif time
FossilOrigin-Name: cd21d4370f054729de3699367d004ff168fc39c51fb7097d6420559c20eddfb6
2022-08-17 04:41:10 +00:00
nekobit 85c7e21d10 Switch type
FossilOrigin-Name: 9b1bc1e9501f783f858d7aaed5723370f3bb7300c5b13df20655a41bfd265270
2022-08-17 04:38:29 +00:00
nekobit 177636a08a Show time on end
FossilOrigin-Name: e046337361a5fdbbd1c822c965126d133e88266c6907699bca16fae7f885752b
2022-08-17 04:36:38 +00:00
nekobit 242c11bf28 Offset menu search a little
FossilOrigin-Name: 31a518e7b282f4275d2e13f1e349518be473e52ca8dd4956a81c4d1563ab2410
2022-08-17 04:09:10 +00:00
nekobit 37f23ec3a2 Tweak small notifications more
FossilOrigin-Name: 9df6d19e067310f606cc0503f189584ef85186a49d506fbe869a41b3a1fd30d6
2022-08-17 04:08:48 +00:00
nekobit 399fe70f08 Compact notifications redo
FossilOrigin-Name: 4c93b6fae6df82cb2c199a5df3b964f217b7866a59e7ee9a08bd681548841175
2022-08-17 03:43:29 +00:00
nekobit 2609b8d54f Seperate notifications logic, fix perl modules
FossilOrigin-Name: 8dedd9a9e0ca9f982140de1e980be166d07e9d549eae5cb50115fc3a7a414ae6
2022-08-17 02:08:13 +00:00
nekobit 9923154112 Show lists
FossilOrigin-Name: 10cb2fd50d3c08e5bf30109b35df1d3af4948f86d9b2099f716da30949f5b734
2022-08-15 04:23:19 +00:00
nekobit 71b636d77f Lists perlify , remove compact notifications CSS
FossilOrigin-Name: 6110d9f562673f233328fa5c5abd845c1ab1b3da5a314e94adb4d142287e70fa
2022-08-15 03:58:30 +00:00
nekobit 6f0c959dc7 Search dropdown menu
FossilOrigin-Name: 38a53150faceed277b933bde3edb37abb42d8d27c258d0a9b0564af9c8946773
2022-08-15 03:06:46 +00:00
nekobit 5904362b1a Search all and searchbox tweaks
FossilOrigin-Name: 4130b59a244b1e0a82023c9e18f50ebe731348ecc06ad3b1df1e1390a455636c
2022-08-14 05:37:49 +00:00
nekobit 3ffbfb2f13 Refactormaxxxxx account lists, rewrite muted/blocked users
FossilOrigin-Name: e571d12a115516b8dea8a68ee3caa1272b102e10a4e0fb308df4e7556b8c30ec
2022-08-13 22:35:30 +00:00
nekobit 0504203df1 Fix notification read all
FossilOrigin-Name: e2f99021b7def56f6fbe347ecec3c1b5310d4b36c09261298054b462499e591c
2022-08-13 06:55:22 +00:00
nekobit 15804cdf61 Fix warnings
FossilOrigin-Name: 2b6668ceae19377db2219ae451b496199a9dcef486a451857024d277269fda21
2022-08-13 06:19:41 +00:00
nekobit 6d46b56d54 Perlify scrobbles
FossilOrigin-Name: 8652ea3608a2a48af9f4915dd97732606cb8d5c5324821754472885d4f784a61
2022-08-13 06:16:21 +00:00
nekobit 4b696779ae Boilerplate for all the account lists
FossilOrigin-Name: 67c823fd5c5d45f87e8f3ae0413fd5ebfbaec4f522b35c0f9136d57d358e0db4
2022-08-13 06:12:35 +00:00
nekobit 6534dd27aa Accounts and accounts item template
FossilOrigin-Name: 248b2707f4670077651f40f93bb928b75e70ec33789025d0ef8fe239f7858bc8
2022-08-13 05:56:43 +00:00
nekobit ca444fde1d Bookmarks and favorites page
FossilOrigin-Name: 9af3f8d27ddd84caab9c72e74c591a1969eb3b422fcaae2f512605b836a7ff43
2022-08-13 05:45:15 +00:00
nekobit a0240a63db Magic
FossilOrigin-Name: 59fd4c3e0ec9a394e497cb35d5e9c01e0f28a9ded3a3ca45c230f0fb8435e57b
2022-08-13 05:04:15 +00:00
nekobit 72ffa2abed Mark all as read, fix CSS
FossilOrigin-Name: 115f3020f24f12be0b54fcfff7088ab483fccd5029a36506e02051ec715e5b65
2022-08-13 05:01:19 +00:00
nekobit 33dbabd49b Mastodont: rename to mstdnt_notifications_args
FossilOrigin-Name: 5998ae4318e218c7175f57bb6416ed4be9e68432ab4d47575ae97f6c723dd01f
2022-08-12 04:50:04 +00:00
nekobit b3c3fe360a Errors and fix autovivification with statuses in account
FossilOrigin-Name: f1e729fac2faaf4aa483d98f29712f752de726f9c2063cfa6fa35551cc5d92e9
2022-08-11 04:56:32 +00:00
nekobit d789caec0a Disable button if no notifications
FossilOrigin-Name: 1456b994bb3d1bd152059f874616b941c42f304c291d7de14929c43b80d00d83
2022-08-11 03:28:12 +00:00
nekobit 997cce08f6 More notifications, errors, etc
FossilOrigin-Name: 052b9f7fbf6a43076c501922cc611b89949d24d03863ccb88dd0f910467eb1e2
2022-08-11 03:25:24 +00:00
nekobit c18179b52b Navigation
FossilOrigin-Name: e1f2a5e64b2f8ba9ddb5c96d4d3ad8e37195d8f40020304df7f10ffb112a3c38
2022-08-10 05:06:47 +00:00
nekobit ba24b6a993 Quick fix
FossilOrigin-Name: a8703da3643f9be3a251751f358e6ac8263311cf9eda3e13bf13f84b4f704a75
2022-08-09 02:41:50 +00:00
nekobit d80f9a3c62 Show icon
FossilOrigin-Name: de6307adad08ce359ccadf55db72af387caf37e45025b5d414cfe737092ed008
2022-08-09 02:31:17 +00:00
nekobit 82ca6009ae Show boosts
FossilOrigin-Name: 449baa1ea91a5f5404f938a8e210262840fbfe7d01b4de7f38efdee0de3eba8f
2022-08-09 02:29:20 +00:00
nekobit 03e62a5e51 Add back serialization
FossilOrigin-Name: 380c160f7c82dc100c2203f03e89844a06c6bf8df07ac58413be93be3ea7465d
2022-08-09 01:56:59 +00:00
nekobit 1a4175351a Fix timeline race condition
FossilOrigin-Name: 737dc9ddb194890e0eebc61fb6bbd5807b8c5aaad6c097ddd887b1002214c9c3
2022-08-08 21:27:47 +00:00
nekobit ec3dbcb60b Fix perl context. I really have no clue how this works, but it works.
FossilOrigin-Name: 808322f63ddc7af32b994c9101b241ca9caabfc9c0ddef55fcb2f07d54251b8a
2022-08-08 19:03:20 +00:00
nekobit 1edf678d90 Create statuses
FossilOrigin-Name: 5767e0610e9a60b2c908fe7a62b2e723e761e4d372424a57d029ddb677fe3610
2022-08-07 22:03:46 +00:00
nekobit ae453835b4 Add content to Account page
FossilOrigin-Name: ddb93098b95a7e750d80156451580be8a59a8471985b93ad00cf43e05a6e49dd
2022-08-06 17:41:35 +00:00
nekobit 19b19b900b Chat
FossilOrigin-Name: 7129d3c344032bab229fdc356df7522608a95af3696d92d22ec8f40d557c5734
2022-08-06 05:06:27 +00:00
nekobit a6d119833c Search boilerplate
FossilOrigin-Name: f7c7717cc3d549d6a2126efef96e702a6027c7841531b7eb276c8ebbd4106d0d
2022-08-06 04:47:57 +00:00
nekobit 4697a9fce8 Remove:
FossilOrigin-Name: 7e34768ce6d9c1e3bf79e6fd90b1881a7e3ffd97b6720bd0369a493334eae795
2022-08-05 20:38:49 +00:00
nekobit f598254df5 Fix
FossilOrigin-Name: 777207b1be91397673581322f0e29de2710da552cfc95e3ef65a97952b2062db
2022-08-05 19:47:58 +00:00
nekobit 6c28a9cbfa Export &lists::content_lists
FossilOrigin-Name: 4a2a1ce36f827bda26d6c5f24ed4ef64ed025ddbd8e8fe9732d0953d1892d7c2
2022-08-05 19:46:59 +00:00
nekobit 3c4a5970e8 Lists boilerplate, Fix perl/fcgiapp header conflict warnings
FossilOrigin-Name: a04dd4a4b737e28845a422293b10f72335f55aea72ae5bcd20eb01f8842b0dc3
2022-08-05 19:45:35 +00:00
nekobit 937301e307 Notifications perl template
FossilOrigin-Name: 7f5ff955aa478f571019fe9d6cf992f229e421dd7bac1b8ea689594baca3621f
2022-08-05 19:20:26 +00:00
nekobit 388f4fadf4 Account template
FossilOrigin-Name: 8b23951e351627eddbb3a4520c4fe43015955ec4b32621416b6e144d6086a1e4
2022-08-05 04:31:51 +00:00
nekobit 6ea891ae8b Perlify relationship
FossilOrigin-Name: 08437db1c8a6eae47804c4687a67c5522b2b98b7464cc089229bf07fbbd8880a
2022-08-05 04:01:03 +00:00
nekobit 72f8932155 Fix account subroutines
FossilOrigin-Name: 11a7cb02e4b0b669445fe1bb6330026b1fa01764008e917a417476b8f8d274c9
2022-08-05 01:26:57 +00:00
nekobit 637e1591d2 Call account::content_statuses
FossilOrigin-Name: f84703f24f566b1e246844dd63b708c40683fad58f95ddefcd1771e460da51a6
2022-08-05 00:16:59 +00:00
nekobit bbe47e8721 Fix fetch_account_page
FossilOrigin-Name: 177ff656f354a699a830082adf54ad723c043f25af955f91c80355e3f9a8019e
2022-08-04 17:48:22 +00:00
nekobit e002975c75 Template files and stuff
FossilOrigin-Name: 7b78c4539633a22909aa1d3d3c86d10d83897ee9e4db0796562fa290c34eb657
2022-08-04 16:17:59 +00:00
nekobit 50b0404c51 Fix timelines
FossilOrigin-Name: db1f2c1597a362e40bc65c39daf079ee51091bba653e721a9ac4e918bad95d61
2022-08-04 05:00:13 +00:00
nekobit 6ab40c0a66 lmao
FossilOrigin-Name: ce623bcfc6aa85e250f8861966654b8c476372c1829d14d96b4b80bbd799a34c
2022-08-04 04:49:28 +00:00
nekobit 473b3b5920 Progress
FossilOrigin-Name: 69b265f1adaa240d8ec99f3448ed386a9e543e4f30cf1534cb387c57ee198a4d
2022-08-04 04:31:28 +00:00
nekobit ff3d383464 Timeline template
FossilOrigin-Name: 33082559569a1dd5aa038d4636583d0918253724defbb58570f5baafe06220d1
2022-08-04 02:47:48 +00:00
nekobit a3bb29d01a Perl timeline C
FossilOrigin-Name: c078ecf81d0273400acba2d3be987a8a0363eb6bd540beebb8f2427348707496
2022-08-04 01:15:17 +00:00
nekobit afba0ef35c Link to origin
FossilOrigin-Name: e3dbf360000f832d1d3ddb4728a0db01689ebcf296e65badf2a3d4a933f284a5
2022-08-03 05:09:15 +00:00
nekobit 70b3fd7611 Finish statuses
FossilOrigin-Name: 4fe6d637a9f0610b978c32c1a39851546b141c37fd275ee5d44c57884a528a12
2022-08-03 05:08:37 +00:00
nekobit b745b140d9 Don't show post box if not logged in
FossilOrigin-Name: 89d85ad2a2ed246fed646d8d7b3e05929a55624530ec150e05b04055925d114b
2022-08-01 05:25:48 +00:00
nekobit 19f5e5c14d Get mentions from content
FossilOrigin-Name: 29716d4f981e4cc8750de01a82dea395044c472165ede67954ac7cc781c3532d
2022-08-01 05:17:32 +00:00
nekobit 3ac0145efc Postbox
FossilOrigin-Name: fa0971dfd73b18da2b62d02dcb1f3aa5ef301d17c21efab4ece79d5a6981a297
2022-08-01 04:56:10 +00:00
nekobit 4f07f2768f Only show application name if focused
FossilOrigin-Name: dd9cf53f4b67929188af70ef57ebead4467a2e21017afc705360189d9bd7862b
2022-07-31 21:10:48 +00:00
nekobit 7d0de2754f Titles, Emojis, Greentexts, and more
FossilOrigin-Name: 7166edf7fcd136ffbda39d2f36e96832690cf978a1c5df4bb6d172f8a2e905ac
2022-07-31 05:59:21 +00:00
nekobit 849b63640c fds
FossilOrigin-Name: fe992d7bba6c2918cf35dc1886579b325eb2b4cb1448cd05ef6a32fc20f6acff
2022-07-30 22:37:07 +00:00
nekobit 025036832a Attachments, Emoji reactions, reply id's
FossilOrigin-Name: 58d7a2add5fc05eac12f8bacd7071095d91d0c9d3f481454202898b1cf5c115a
2022-07-30 06:19:17 +00:00
nekobit d46209f0ee Relative time perl, emojis and attachment funcs
FossilOrigin-Name: 7a290aefc9a21f9789cb097ff99c1423fbf67a399f916ab2232e6eab7e83b460
2022-07-30 03:51:30 +00:00
nekobit e19f1f854e Visibility and CSS tweaks
FossilOrigin-Name: d45822206ba3c4bb2be994e4443a909f5c6d5896642703f11d901a2f5f1a37ab
2022-07-29 23:08:43 +00:00
nekobit a11470cfd7 Status icons
FossilOrigin-Name: af3a2a07971fa9fbbf9d8c894e1bec8dbc2442b50894460e479077c7911ffe57
2022-07-29 05:41:28 +00:00
nekobit 97c81d08b7 In reply to template:
FossilOrigin-Name: 7b0b3088bc82f0b7e4dc2bf9b4ffa00982ff6b18cd2e36f17a9b1eac50f040b3
2022-07-28 16:48:07 +00:00
nekobit 830a9c05b0 Remove data dumper
FossilOrigin-Name: 478e33c595f64c9c18ee56bb54e7b69276efa32d55c4c1377cf5ca2c61af772d
2022-07-28 05:15:13 +00:00
nekobit 9069a17a41 Statuses
FossilOrigin-Name: cff813d2d65c0ce99156358724ecff352e0c5a25feb605f2a46f48b0869cf80b
2022-07-28 05:14:24 +00:00
nekobit 253f55dd36 Improved unit testing
FossilOrigin-Name: 786b4080abc83f076704b33ec884236a3733fef9a5a276d7df54fa35d228015b
2022-07-28 03:36:33 +00:00
nekobit eba2a360e0 asd
FossilOrigin-Name: 2cad6235808b58a0041bad16b9bf72102dcfd797023686b28b2a17a6fcd25be0
2022-07-27 05:26:41 +00:00
nekobit 588bfc2126 More progress
FossilOrigin-Name: 87d1a1fa92872a66a37bd82ea9cc2e687f4107c45855a115b49193953c9b770e
2022-07-27 05:10:07 +00:00
nekobit a90878c1b8 Notifications perlify
FossilOrigin-Name: 0d65589909abc6c72911d68ef4c03f48c7d3f552ba5c82d681964eefbf8a2128
2022-07-26 22:04:44 +00:00
nekobit 5b9c7d75f5 Perl file hashes
FossilOrigin-Name: 99a43c7224138245ccd8270c04aefcc3b47cdf23e1eecb65abae6913e3b9105e
2022-07-25 05:12:54 +00:00
nekobit 2e21b6fea3 More perl stuff
FossilOrigin-Name: 7a4d25bfdd0b7eb8e20e91c6f01d719c8e9fb43e0d35c0d46027876b6fab8659
2022-07-25 01:35:47 +00:00
nekobit e038ff9b82 Parallel Makefile
FossilOrigin-Name: 452c66e88ea68af10885312ea1ce9853ace53cb961d42c02084b0efb6d397475
2022-07-24 17:09:10 +00:00
nekobit 440ee60b8d Account sidebar
FossilOrigin-Name: fdae7c14435d63dc3f2b80dafe7d37fb867211e01f4cbff31296aa9f05b43b78
2022-07-24 04:48:13 +00:00
nekobit b8d67b3bcb Remove
FossilOrigin-Name: 5d9e507d8303aa58b1384d2419d18b7349b5f83eb8d8ea7a360ccfc566c6d179
2022-07-24 01:48:01 +00:00
nekobit 5f6c7b9108 Progress
FossilOrigin-Name: 52629add8a196eb6dcf025cee092cfa0a3104a54ef4e9782ddc212310cca8078
2022-07-24 01:03:57 +00:00
nekobit bba2ad9cda More perl shit
FossilOrigin-Name: 3e3d09e82d3730e070a60b59be3c4ad69c1f3c965c3bf027673e9a3811825eb6
2022-07-23 05:32:17 +00:00
nekobit 772db3ce28 perl L10N and Perl Templates
FossilOrigin-Name: 06ed73d30ae11c7651dcb87e4b78f9ef7b297b386f8ff0bbd087e22628bc9a68
2022-07-22 22:41:07 +00:00
nekobit 004b47ad07 Perlify config
FossilOrigin-Name: e39a49305b7b3a890a937a41550e7d927764bf768e2a8d1b916df553f23c31cf
2022-07-22 05:02:59 +00:00
nekobit 10875ff940 Load perl
FossilOrigin-Name: 0107dd3706329714f684f913c2ae67d072851eca96b981c11f5315e1ef780c22
2022-07-22 03:56:41 +00:00
nekobit 09a667a6cd Create new branch named "perlify"
FossilOrigin-Name: c62e59c0dc3bb1b12fbaa520f3d923833beb0737e449f0012673b725560562d8
2022-07-21 23:32:08 +00:00
nekobit 8ab015670b Update documentation
FossilOrigin-Name: 2d30985f1372881742aecd1a74547c608bb2b100d6667f534dcb4148e7b97e2d
2022-07-21 22:32:48 +00:00
nekobit 8dc34e0a53 Multithreading
FossilOrigin-Name: 4fe7e81719d9a8b32c1c35f1a0b0c691ad1395a68a6ac045977eebbb90089fd7
2022-07-20 04:45:47 +00:00
nekobit f4ced103be Multithreading
FossilOrigin-Name: cadee06c6bb43e3b8f40ea06b3c4decf781a6c215b0c50ee5f71d013331d10b9
2022-07-20 04:44:28 +00:00
nekobit 12020d7e6c Prepare for multithreading
FossilOrigin-Name: 4769308887a553fb7176b334705aa31ab6ecad017dab259383554261e9b51be9
2022-07-20 02:32:27 +00:00
nekobit 5e9701dfa3 No JS replybox
FossilOrigin-Name: 47e174164920f713bc3f7409dbc1e0b9195677fcf4b064679dffd76a2ab399fd
2022-07-18 23:15:29 +00:00
nekobit 1083ed03ff Improve account sidebar
FossilOrigin-Name: 0c144ea23f1d0d1490b28492433fcd03032c19070c5826af5ee3583a1cd5c2f4
2022-07-16 03:11:41 +00:00
nekobit 661272026c Sidebar opacity, fix dark theme, and other stuff
FossilOrigin-Name: 9e6dd5bad46fd36aaa7e1cac0b0ac4507b9aafe89613dd98e184040835398c83
2022-07-15 05:24:33 +00:00
nekobit 65300b6ce8 Fix file ids
FossilOrigin-Name: de0da6d03cccb0b3ad76b3595aa834feca563603d54563a0fc43ad1a32209b60
2022-07-14 05:34:31 +00:00
nekobit 7e1b555925 Upload file_ids if set
FossilOrigin-Name: 9c3717500e89386b8795e8dfc22345fd7da79c0ca0a9087b3f4f162e72b7c1d5
2022-07-14 05:23:39 +00:00
nekobit b20d906292 Go back to old sidebar style
FossilOrigin-Name: 78d8b832a39f7e8eb77356dda3b58fc5a87f6fd92175e0cb3fb0df5a47555bd5
2022-07-14 02:29:21 +00:00
nekobit 541556613d Upload image ajax
FossilOrigin-Name: efc75f41345b6fc27e7002152f5a198b69864197f3e2c5f04fc170bdfa3d7015
2022-07-14 02:22:13 +00:00
nekobit 04e48fc2e7 Upload attachment API call
FossilOrigin-Name: f8e1aa6aac057394f9fbaef40d6733226671b4331c51c1653c6a08ba5ce0fb6c
2022-07-13 22:13:56 +00:00
nekobit 170d651650 Full size attachments
FossilOrigin-Name: d75147c35a2141c51e03f678abe95d4c99657e37e42cd9c8c90e2a0397afcf5e
2022-07-13 05:16:33 +00:00
nekobit e3b0eebe7d More file upload info
FossilOrigin-Name: 31096b2a5f627274d407d26ff417f63c038b27c6c5eb96d5553fd39b69e4ce38
2022-07-13 05:09:21 +00:00
nekobit 15849c03d5 Add image under new ID
FossilOrigin-Name: ef42c049e208f207dbda34c2c0b313e216d97354a1a74ec49089f3f2df3b1b61
2022-07-13 03:03:34 +00:00
nekobit 9b66c172d8 Upload and load files
FossilOrigin-Name: 01177fd25324478b1c826911906f86e4bc34a001c7df0f59b9d8428e03d82f4f
2022-07-12 21:54:53 +00:00
nekobit 95c750b07a Add dom per file
FossilOrigin-Name: 4748904de8bfc4e9324542fe71c1c0cfc42c4b73a4b2c29f170cedf5e8ae3a85
2022-07-12 05:20:49 +00:00
nekobit afe85ce1ae Resize notifications frame
FossilOrigin-Name: 482443d0c2140deef0f1b5682d5fe35dda95dec752812b0dab4764c3835bb537
2022-07-12 03:41:22 +00:00
nekobit f4fcb0dfa7 File upload CSS
FossilOrigin-Name: aa8a17ed2567d0c9db37d8a0f0ec426bfb5a182d83465d0dadc5867202858b4e
2022-07-12 03:01:22 +00:00
nekobit 8d7e376fb4 File upload button
FossilOrigin-Name: 553ca5a9a2576b10c3c5fb98d5dd99dffe6c96b617d5fdee1885b9c5f9693b39
2022-07-11 06:13:56 +00:00
nekobit f110b7480a Replying respects post visibility
FossilOrigin-Name: d5409d1ac1e3dff4878cde1a5a8a710bbfd69fc53569a0d2d74745743c597b88
2022-07-11 04:49:25 +00:00
nekobit da5e8039db Control + enter to submit form
FossilOrigin-Name: 8f7945affc483246637b0cd636a609b1f697469350ceef114d1ac54ebe9180bf
2022-07-09 08:27:08 +00:00
nekobit f078d16523 More dark theme stuff
FossilOrigin-Name: 64e46e1bd6ba5362c90cd5ba2c1da60efbcc3f7aff0d054b2745bb8297eca2cc
2022-07-09 07:20:36 +00:00
nekobit 6a8802adae Icons and template updates
FossilOrigin-Name: 940f5314e2f0ee2d57a65e89726a0e1b15b522d947b3ff6430ac4c7ade52e840
2022-07-09 03:42:11 +00:00
nekobit 708ac7b023 Fix config resetting
FossilOrigin-Name: 20c585001854675bf5026baba2c8939399e8b1102420ff2b990b6f614c54ca92
2022-07-09 02:55:10 +00:00
nekobit 68528563ca Theme reworking and dark theme
FossilOrigin-Name: 3b07c47b0c552f2b9a76b8e5ba3dc0ae16477d60b018694a6cfbcca5b5c419d5
2022-07-08 08:19:42 +00:00
nekobit 97bd6b561d Contacts improvement
FossilOrigin-Name: 2322ec181715393e20da17a234610d61189d082cf32e09b61d79aeaca0e7615b
2022-07-08 06:15:52 +00:00
nekobit 54363270ff Theme changes
FossilOrigin-Name: 80cd22939b91931d51b8dfb92bf09a6efab2dd211f612ad83f59e79e25d26e9d
2022-07-07 08:23:48 +00:00
nekobit fc6981e5c6 Add caddy files, update solarized
FossilOrigin-Name: 48ed063c862f41c72851195891203a703b3da6aefb848f4c902fa5df9437dce3
2022-07-07 06:12:09 +00:00
nekobit 05c0407ced More CSS fixes
FossilOrigin-Name: f34213e149b5f45b25463680335149a6fa9f1b4865b8680157118f21828d949e
2022-07-07 06:06:31 +00:00
nekobit 510e97aeeb CSS overflow fixes
FossilOrigin-Name: 45cf724a1cd145eea7071a5a1780ecab1f07eeed93632720b87d673fca7e15e5
2022-07-07 02:51:35 +00:00
nekobit 969f12e347 More chat stuff, chat embeds, fix segfault
FossilOrigin-Name: 4f3d242139e6ea4c8050858ce6be541c415bbf5c4388f81648495df8f534ff31
2022-07-07 01:36:05 +00:00
nekobit db83718dde Chat view page
FossilOrigin-Name: 028f7856fcfe76360d66dd7d23c6e641d941d5d512ef806ebc84dd5c92176982
2022-07-06 07:02:42 +00:00
nekobit 80ce3e38bc Messages theming and reversing
FossilOrigin-Name: ad9ad08b617e3768defccfdbcd47313e108fd17eada75b46f86e082be87538a6
2022-07-06 06:36:00 +00:00
nekobit 95bca87556 Messages
FossilOrigin-Name: 74fe969864d4419e2a040bda2e5482806ed3fd390dafe66c7ec9962304cd763d
2022-07-05 09:44:38 +00:00
nekobit 434ec2e8bd Chats page
FossilOrigin-Name: 397b4d1c5981fe12defbe8d643a4cc8deba637d3ccbbf465c919fe91ab512d78
2022-07-04 08:46:38 +00:00
nekobit c0ccb74569 Remove temporary error code
FossilOrigin-Name: 9a6c646a13f89e2e422b5ead0067ab597161a349ddf3003a88fe40dbb5097400
2022-07-03 23:14:06 +00:00
nekobit 3f28928a1b Fix status regex
FossilOrigin-Name: 48b6e79f5035b50bdce1aff46b8cb0eec39fe92ec98609704b8b4905e4e5cf92
2022-07-03 04:47:43 +00:00
nekobit 7f654b7f5c Thread pagination
FossilOrigin-Name: ea762ff1808965e992f2a7685e13f06030629dfdfd014ae6fbdf9134de973e70
2022-07-02 06:12:04 +00:00
nekobit 6d1ef5f938 Fix typo in parser
FossilOrigin-Name: 2bd8204dfd90dd51073a1c8d974785399272578b8adee4f781363a4c33ca32c0
2022-07-02 04:49:36 +00:00
nekobit c020158001 Sanitize, parsing, fix issues, CSS changes, and lots more - I forgot to commit all day lol
FossilOrigin-Name: 8b9833d6b00afa365e4fda12551947a20076e8ad6106aea80e8304e667e1110a
2022-07-02 04:31:21 +00:00
nekobit dab66cf833 Mobile CSS
FossilOrigin-Name: b9cc1e6da0111fac5f38e3d26c122ba40ffb7ca4246a0cd1244c826038773073
2022-06-30 03:07:53 +00:00
nekobit dbc737226c Fix various memleaks + overruns
FossilOrigin-Name: 6c24b524226c4bb45eb53effd2b4232cabe7a5588f2e8551e4d159144f015734
2022-06-29 04:08:23 +00:00
nekobit 56af83d157 Fix typo and remove unused vars
FossilOrigin-Name: f196a46950ce28f6099b4a23bfc91d274e5f744091176e7603c94648616328ae
2022-06-29 03:42:00 +00:00
nekobit 1cec6bb4b5 Refactor parser
FossilOrigin-Name: f3cd880740b95d2fbe3b61e4f8b9cdfd503f62720da1755ba14d387b95e0938c
2022-06-29 03:37:38 +00:00
nekobit c3e8af299f IMG react buttons
FossilOrigin-Name: b68a8c576c99dfbf981642f6551d02de7a74a6f3e49136d5d7005a910dfd0d3c
2022-06-28 06:12:08 +00:00
nekobit 0a5e3cd7ee SVG compat icons
FossilOrigin-Name: 6ce5b106ff43e9ef03cdb2b35f019436ee151fd3c3a5010921a0bd64eba19c43
2022-06-28 05:16:44 +00:00
nekobit 468d9cb8aa Fix
FossilOrigin-Name: 92a3fcb1e4b90107e03ab3485fce655176083965ff09bfa12aa189cee0234647
2022-06-28 05:09:54 +00:00
nekobit ba8a6c2027 Notifications segfault fix
FossilOrigin-Name: 34ee1e9e8a61b38013a90ad9dad8e5726496bf6f24461365c643d0dc7a7e17f5
2022-06-27 18:16:33 +00:00
nekobit 9eb8d79dca New emoji button, fix CSS
FossilOrigin-Name: 62922826f620d3ba19980043626830e6d32b7c665af31de2fcff4d5d3e54e86b
2022-06-26 06:02:59 +00:00
nekobit 40334b633e CSS tweaks to menus
FossilOrigin-Name: 54f24b341d70aaa59e10d6d4616842dda728e49a86915acfaaaae6728d303e1b
2022-06-26 05:37:51 +00:00
nekobit ff2aa768c9 Use insertAdjacentHTML for the emojo picker:
FossilOrigin-Name: b42f9e4982e0e02f62c1d5de7e41c4fb9d35326149ebab55a853d0bd672cc45e
2022-06-26 05:04:41 +00:00
nekobit 72b8fefc60 Don't show JS emoji picker
FossilOrigin-Name: f7ad858fbd6aa527c98bd5bd7876db420103c860d721617543ecf82f3a9fb4d7
2022-06-25 05:13:39 +00:00
nekobit 38f9ecb55d Fix lists css not hiding
FossilOrigin-Name: cf8ecb1e63819a43d9f9093d4db1a42e1ed76d5dc4239a156aa244657ce46680
2022-06-25 05:06:07 +00:00
nekobit f1abdd3b50 Fetch emoji picker document instead for JS - Before we were generating our own but this was pretty slow for JS
FossilOrigin-Name: 97ab71ad7f73882b8f980ce0daaad90636e9d2214e4fab4169f975b64f5eb309
2022-06-24 23:56:54 +00:00
nekobit 91425632e2 JS Emoji picker
FossilOrigin-Name: dee0ddfe4af83bb952b8baf4d41d778974ee09d033c13b937fdc0c7a4a70eed0
2022-06-24 23:15:59 +00:00
nekobit e31e09029a JS create emoji picker
FossilOrigin-Name: 26515b617c6cdfc8c29c0976593245e825b38e7045a363171660881130d04870
2022-06-24 21:21:38 +00:00
nekobit f0befa3d3c Emoji for JS
FossilOrigin-Name: 222f905018be0cb759945de3a9c151171b5368d14a0d310baa768e80d646617b
2022-06-24 21:05:13 +00:00
nekobit 03f0b0eeba Emoji picker redo w/ faster tabbing
FossilOrigin-Name: 882c213536926029ce5490f6556cc0088a10985ba65a3426756744291d239e8c
2022-06-24 06:45:38 +00:00
nekobit e0a6e8a751 Fix account in_reply_to
FossilOrigin-Name: 5d6ac81c0f3d5fe6c12ab0b87244c8776d9ba33df2ef2bc5fdcf2fef8a978461
2022-06-24 05:16:47 +00:00
nekobit 7099f504f1 Refactor for mastodont + fix status xss bug
FossilOrigin-Name: d5c3f7b33120a5b137e09026be17b252b62bf73371d55c920e376d0941f34d3d
2022-06-24 04:24:12 +00:00
nekobit 8432e66cb4 Lists update
FossilOrigin-Name: 44a4c4611a6af7048dcaa15d6a2d576373a39af9d77312481908a53add3bb761
2022-06-22 05:27:51 +00:00
nekobit c88c30996c HTMl bad
FossilOrigin-Name: 694ea446032626c6209be683b5f439788ef4192f2fe1057b0639645e77c58ef3
2022-06-21 05:58:40 +00:00
nekobit c2602801c4 Fix JS replies
FossilOrigin-Name: 3f6fb582d868b93781c3ee63c9c63305a61753f1d9719072ab5cd5cf17fb7c1a
2022-06-21 05:03:01 +00:00
nekobit d4deadf064 Fix use after free
FossilOrigin-Name: 7d1dd9f2fcbd48f57578275443c870d89e0feeec83289311635e3e0d8185556d
2022-06-20 21:18:01 +00:00
nekobit 5b4db3fb74 List dropdowns
FossilOrigin-Name: bc1ad4b3716d65bded6f808f2219d5861497cfa87a4ab81b0250f299f486fa13
2022-06-20 20:41:42 +00:00
nekobit 06b95be3db Lazy loading everywhere
FossilOrigin-Name: f3e381d50ca906f7b9a28b6bdbfe8e5e36b2478206166a69c981bdaf7f1bd991
2022-06-19 06:03:04 +00:00
nekobit a129f9178f Search page pagination w/out JS
FossilOrigin-Name: e40f45e2d41593d47060e5a7daa0aadadc95ea3e55965f1bc7fa026f06e53251
2022-06-18 06:23:43 +00:00
nekobit ffa4d9cabb fds
FossilOrigin-Name: dc8c3963e947338c9575e21e1bb53fe674c27dd726d2d32c0e910440a550ea36
2022-06-18 05:50:09 +00:00
nekobit 12bcdc19ff Fix status replies JS
FossilOrigin-Name: b1377be8778ec04a3315a45987cf05e2baf9adf12600a4493c9de7dad47b8a5d
2022-06-18 05:47:32 +00:00
nekobit dce2c390b5 Fix missing navigation bar on timelines
FossilOrigin-Name: 1668f4d49f7f4cc7ed113317dea99ec4558b01a5c7da038ac3a187c2032f60d7
2022-06-18 04:33:05 +00:00
nekobit a74c7d626b Fix status hiding
FossilOrigin-Name: 5188091d528aaa5c7135139cb436779d2093ed793ea4d37d7d6d57c9c48de893
2022-06-18 04:22:52 +00:00
nekobit 9db1263ad5 Finish wormle
FossilOrigin-Name: 5c6b0df0549e27536bd06732fff0eced8aba5bf8dfc813a6e99d1f3787e5312f
2022-06-18 01:40:39 +00:00
nekobit 7fed076c04 Wormle apple
FossilOrigin-Name: e4352f1d17cd6a75a73fc6f735fcc0072d26b8c7225514313e946001090cbd73
2022-06-17 20:58:59 +00:00
nekobit 1616c5f6f0 Wormle
FossilOrigin-Name: 2590ac1110487535856c4eed5c0780cf7af169bad5ea97245dfea54a60d69f6a
2022-06-15 08:08:08 +00:00
nekobit 0cc4efb2a3 Visibility enum to string
FossilOrigin-Name: 78c011d7b0caba49e0e19b54c9113c2b650cd9048840abefd9e0e44ab90edd06
2022-06-14 16:05:26 +00:00
nekobit 4e2a9ae33b Fix focused links
FossilOrigin-Name: 9123dc43b6e73d94c81fefb69d51e70b5866b134c80f94e5c0e9407ece2e532a
2022-06-14 15:35:26 +00:00
nekobit 7a64a6dc7f CSS fixes
FossilOrigin-Name: 9bd8d5fa715fcb2de3f56e9c8ed970971cfa7916b6780dcd05c2142e73f55ab8
2022-06-14 15:27:37 +00:00
nekobit 4708687be2 List creation
FossilOrigin-Name: f0c478cb4c4645a9f02b3e4a27392b99bf29a41b728cfcb65320045d94cef706
2022-06-14 15:21:36 +00:00
nekobit 38230e38ee Update lists reference
FossilOrigin-Name: 0f58996df8b3b3237bea71d39db5ef350eaef1b2ab7fac4fa83df82d1788fbe4
2022-06-13 06:33:40 +00:00
nekobit 9bc8783bff Implement all List APIs
FossilOrigin-Name: 9f7fa36367e568ce5cd336004af4542a20fec912ed1ca727e020c625595e1601
2022-06-13 06:29:49 +00:00
nekobit c55afbf2f6 Update list template
FossilOrigin-Name: 9373536cc9daeae9f5867825dee0e22806e4326db505347802515629d3636953
2022-06-13 00:43:18 +00:00
nekobit 8d153983d6 Mute and block pages
FossilOrigin-Name: 0fa2aeb7c66595fade365d8c4ea7d027b83657aa9679223b13ee6844f7b7ae32
2022-06-12 05:44:45 +00:00
nekobit d3bf48a1ed Block/mute pages
FossilOrigin-Name: ec353fe9ca9604483bdd6f7afc891ba3402b6c0f9b2a2d0b2c844c92ed670709
2022-06-12 05:11:45 +00:00
nekobit 8ee07faf50 Set limit
FossilOrigin-Name: 602912385e87b67036d4d9fca8766410bad0a803441c65e4e6bbbcc282a0da33
2022-06-11 05:55:31 +00:00
nekobit b22d2acf1d Following/followers page and some links
FossilOrigin-Name: 413c0b46c82bbaf6f224063ffa0b221a7c7885f42b851798d29931143a6699d5
2022-06-11 05:50:36 +00:00
nekobit 698bba32c6 Menubar on current account
FossilOrigin-Name: 1a9669dd23cb49106ec919cbaac5cab4511e009fa46fbf311e216983a87a0be4
2022-06-11 05:02:23 +00:00
nekobit eb6c8285f7 Improve timeline options and menu bar
FossilOrigin-Name: f1411b974a0626843c76428f683a87b5378ee7520ce96752f966ae370ed33cd9
2022-06-10 06:13:34 +00:00
nekobit 3518f86b2d Timeline options
FossilOrigin-Name: 8165488b62b3a57e56e8f85af5adc4819e98e07c2be946ba0777c67240a82524
2022-06-09 06:52:39 +00:00
nekobit 72dd972c42 Toggle status stub, stub muted statuses
FossilOrigin-Name: 612e8523e256f4d59f17ef79f8cf9eaedeacfd37ec9e1141c779a82e0196c3ee
2022-06-09 01:25:54 +00:00
nekobit 8bd187bece Fix nginx config sample
FossilOrigin-Name: 7db96f289c477805f18cf4d0d79c3f14bd6f55b80ef4cc37f74c8b30207903a1
2022-06-08 23:18:27 +00:00
nekobit d4d81258ba Custom emoji reactions
FossilOrigin-Name: 0aae537cb034f505724ba9bf1c90c2f0b946d665bc306e1de944cf564d3dfb63
2022-06-08 22:01:31 +00:00
nekobit 04ae07d796 Only show pin status button on self statuses
FossilOrigin-Name: 92b0521020f2d47d7b617d008117e27621fd8f8cbd3c1a0a7e8154fb690ab1c7
2022-06-08 04:55:58 +00:00
nekobit ef3f712883 Like/boost pages
FossilOrigin-Name: ac61288ee02687bbf2a6bef576c5599a47009df3253b9e3914292365a6e2a0f4
2022-06-08 04:27:39 +00:00
nekobit 7b852dbbcb JS reply_get_mentions
FossilOrigin-Name: 8d6cc782d8149bfb3b0d223cea058c8fc7a4141f191b7d25e3280de1d87dd2cf
2022-06-07 06:58:08 +00:00
nekobit d3554ba3f2 Fix make_mentions_local
FossilOrigin-Name: b870e712dfa825b9a1f5d81fbb4e4359200ebb52d2f14c8982f5fca26f9e9e6b
2022-06-07 05:59:44 +00:00
nekobit 2240cee57a Fix reply duplicates and self replies
FossilOrigin-Name: f5b0bbe135008cf1d84fe91c910134815ef920476544648bf95491416901e10c
2022-06-07 03:51:08 +00:00
nekobit 5a6929e20b Regex fix mentions
FossilOrigin-Name: a625188e6d0161d3377da2abcaebd23756626b7a1a404cec46d20ea467d6d820
2022-06-06 23:49:44 +00:00
nekobit eaf3350511 Switch to PCRE2 and interaction animation
FossilOrigin-Name: 877f69fe0c99305ef15c7ab5a8db695986d7179f8d30e776a785347637d4f2a4
2022-06-06 20:28:51 +00:00
nekobit 4a9500d0c0 AJAX interaction fixes
FossilOrigin-Name: cfc54659730b258dcd127953cfca216735cf0023df3363c6ea35afa46295603c
2022-06-06 19:18:12 +00:00
nekobit 3dba7538f7 Javascript AJAX interactions
FossilOrigin-Name: e8123356ede23ac260c423e70b6434f79193dff64c106b47735cd6d7e89aed17
2022-06-06 05:26:51 +00:00
nekobit 4c0b6c7859 Credits and JS stuff
FossilOrigin-Name: 38ce3752bd163ee8c28cd5f624146e5ef09dbdac5495848d42828bd03d5813b0
2022-06-05 22:54:29 +00:00
nekobit c5d8b9df68 L10N Language support
FossilOrigin-Name: 14aa65bba9ad19568fcec029c2edd8e8e7ea2c0a78a019265c2ff0c8d639ff80
2022-06-05 22:25:18 +00:00
nekobit 8382573d30 CSS fixes and notification emojis
FossilOrigin-Name: f9bc1382cd9622de84be83a9fb9e903c7fd82bd1390c6e9b417588ea815a4675
2022-06-05 21:45:50 +00:00
nekobit ada4630442 is Bun
FossilOrigin-Name: 486b9c61be8c089639c66255bd9cecad925b0ea55ed54a47510381996d9047c1
2022-06-05 20:40:40 +00:00
nekobit c070b6a031 Show emojis in certain locations
FossilOrigin-Name: 653d3bf1653802edf8000318354f3f3aeae05a5eacecbd7c6cbf9496b3fa9535
2022-06-05 20:05:14 +00:00
nekobit 0a34c78ff8 Fix scrobble and hashtag wrapper types
FossilOrigin-Name: f515ac0dfb0779469221bfa77a110761412b958323c2b4c91137d1f942703acf
2022-06-05 19:25:29 +00:00
nekobit 4e42784215 Fix libcurl and cjson dep
FossilOrigin-Name: efea82c8c7259d5d9163403fe9bbff58d0d071ad53481f59a18827bea129277c
2022-06-05 19:24:14 +00:00
nekobit bbaaa166a8 Fix bug
FossilOrigin-Name: 45a716cf6c964458db6adcbe8b8e566575b056cdb8388cf4f2aa1641e696ea51
2022-06-02 06:04:00 +00:00
nekobit 67094237a6 Update send_result function
FossilOrigin-Name: 070284b06db0c672bf2e0c63a49f80611909cc531ea12d732de346cfbea5abb9
2022-06-02 05:45:40 +00:00
nekobit cebd17dad9 Status interact api
FossilOrigin-Name: d0f0c287d9436739cba685f11178e9e48b373adde668fabf0b5be3c59653b982
2022-06-02 05:40:01 +00:00
nekobit f34889cde1 Cat ears #isCat
FossilOrigin-Name: 06fbde2dfe4884d3dfd298fee85ff78c8b3482c38408a2df053aaa4a03ce3b91
2022-06-02 05:32:44 +00:00
nekobit 537193779f Switch to size_t for everything
FossilOrigin-Name: 891b005fccf60f0da131a4bff6408bb70953b2ad9061584d3e46158f74aae206
2022-06-02 03:28:54 +00:00
nekobit c8e3f5d70f Incomplete loop fix
FossilOrigin-Name: 1fb69b8c5ce57d2120cc3e80d08e29a0de27d0f8cc6d4fa96863c1b7fe0cd002
2022-06-01 06:11:50 +00:00
nekobit d4c7564e03 Templates rewritten
FossilOrigin-Name: 829485569f9ea39c9f417e7c4eb1a104665da0bb0257ba6fc8dd4b711a031d98
2022-06-01 06:08:00 +00:00
nekobit 382bbe4a13 Use templates for account.c
FossilOrigin-Name: b01d16d6feefde6b35f73ff396cf71617da3ae85c0ec85913d5bdc7e9aa51dc6
2022-05-31 22:03:44 +00:00
nekobit f1a407c79f Templatify 1/1
FossilOrigin-Name: 6ca19819ff1547744a39e4d248a7803bf6dfd8f20ae64e704f044200746acaf1
2022-05-31 21:32:36 +00:00
nekobit e01ea11625 Templatify 0/1
FossilOrigin-Name: 969c150fdd1a11067792ff1f6b7f2eeda7da97cf0d25d859a1339a0cdea2ea3e
2022-05-31 04:23:38 +00:00
nekobit 8d5f9eb269 Template makefile and status template
FossilOrigin-Name: 9a4b3c391855b099fa68f157ce655fd95532c602d954a3e6d8974fe46f29f988
2022-05-30 07:46:19 +00:00
nekobit 0e71de0ba6 del
FossilOrigin-Name: 89c33484016c0296f2e7ec5c415b142dcde888ad1274243c5105d05f8a5ec619
2022-05-28 05:53:33 +00:00
nekobit 3976497ae2 Use template engine for base page
FossilOrigin-Name: 9a7dc653376414af58c66015fcaed25cfc878e2c20be160798af07b8cce35673
2022-05-27 04:35:21 +00:00
nekobit cdbbfed221 Template engine
FossilOrigin-Name: 80100717c5483087c985254616f3c784e79536ac4c161c77915a355e0fa04fc8
2022-05-27 03:37:47 +00:00
nekobit 0a580d0a32 qwerty
FossilOrigin-Name: db3937de01580bd3adf1a531ea1dc7b966beaba4cba7629a3b1f0b9e939746b3
2022-05-26 05:29:26 +00:00
nekobit dd3818abbc Template file test
FossilOrigin-Name: ec7f897e8f7e9897554b1d6f5875bc0642630e425347c7e082010fbf7fd00d8f
2022-05-25 18:49:04 +00:00
nekobit d3e5c4e706 Status deletion
FossilOrigin-Name: e29e4c081d48cf724df5597e6cb22d9977a47ba91dd1b66921a4ef8b3fd4bc67
2022-05-25 07:32:54 +00:00
nekobit edd88c095d Javascript quick reply
FossilOrigin-Name: 0137005c73da03add30c4bf2011d44faafdbb9e7e6821580e9dca72dc1c8fde9
2022-05-25 06:50:55 +00:00
nekobit f66c2c1e4d Improve interactions
FossilOrigin-Name: d7dd250d57c7e4c505d46ae5003373ddba03fea24d0a65343e5f727024c163c8
2022-05-25 03:57:10 +00:00
nekobit 57dbcc8ad8 iFrame notifications sidebar
FossilOrigin-Name: fcb690387596d29439591f84589fba848c20a2f1bd72ea3aa3259e427a9d372f
2022-05-25 02:06:11 +00:00
247 changed files with 13363 additions and 8133 deletions

View File

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

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

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

View File

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

View File

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

1
dist/emoji.json vendored Normal file

File diff suppressed because one or more lines are too long

BIN
dist/img/compat_icons/alert-circle.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 939 B

BIN
dist/img/compat_icons/alert-triangle.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 B

BIN
dist/img/compat_icons/bell.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

BIN
dist/img/compat_icons/compass.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

BIN
dist/img/compat_icons/emoji.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
dist/img/compat_icons/home-2.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

BIN
dist/img/compat_icons/inbox.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

BIN
dist/img/compat_icons/list.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

BIN
dist/img/compat_icons/mail.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 645 B

BIN
dist/img/compat_icons/maximize.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 B

BIN
dist/img/compat_icons/message-circle.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 B

BIN
dist/img/compat_icons/repeat.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 B

BIN
dist/img/compat_icons/settings.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
dist/img/compat_icons/star-repeat.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 B

BIN
dist/img/compat_icons/star.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 904 B

BIN
dist/img/compat_icons/user-plus.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 B

BIN
dist/img/noname.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

15
dist/js/emoji.js vendored Normal file
View File

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

351
dist/js/main.js vendored Normal file
View File

@ -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> &bull; <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);
}
});

412
dist/js/worm.js vendored Normal file
View File

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

1191
dist/solarized-dark.css vendored

File diff suppressed because it is too large Load Diff

1204
dist/solarized.css vendored

File diff suppressed because it is too large Load Diff

2
dist/svg/emoji.svg vendored
View File

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

1
dist/svg/globe.svg vendored Normal file
View File

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

1
dist/svg/lock.svg vendored Normal file
View File

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

1
dist/svg/menu.svg vendored Normal file
View File

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

1
dist/svg/paperclip.svg vendored Normal file
View File

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

1
dist/svg/rotate-right.svg vendored Normal file
View File

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

1
dist/svg/search.svg vendored Normal file
View File

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

1
dist/svg/searchmenu.svg vendored Normal file
View File

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

1
dist/svg/unlock.svg vendored Normal file
View File

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

501
dist/treebird-dark.css vendored Normal file
View File

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

3209
dist/treebird.css vendored Normal file

File diff suppressed because it is too large Load Diff

1258
dist/treebird20-dark.css vendored

File diff suppressed because it is too large Load Diff

1648
dist/treebird20.css vendored

File diff suppressed because it is too large Load Diff

11
docs/DEVELOP.md Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

118
perl/account.pm Normal file
View File

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

26
perl/attachments.pm Normal file
View File

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

42
perl/chat.pm Normal file
View File

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

20
perl/config.pm Normal file
View File

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

25
perl/embed.pm Normal file
View File

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

35
perl/emojis.pm Normal file
View File

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

75
perl/icons.pm Normal file
View File

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

96
perl/l10n.pm Normal file
View File

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

19
perl/lists.pm Normal file
View File

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

24
perl/login.pm Normal file
View File

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

59
perl/main.pl Normal file
View File

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

37
perl/meta.pm Normal file
View File

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

25
perl/navigation.pm Normal file
View File

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

63
perl/notifications.pm Normal file
View File

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

26
perl/postbox.pm Normal file
View File

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

106
perl/search.pm Normal file
View File

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

99
perl/status.pm Normal file
View File

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

103
perl/string_helpers.pm Normal file
View File

@ -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/&/&amp;/gs;
$text =~ s/</&lt;/gs;
$text =~ s/>/&gt;/gs;
$text =~ s/"/&quot;/gs;
$text;
}
sub greentextify
{
my $text = shift;
$text =~ s/(&gt;.*?)(?=<|$)/<span class="greentext">$1<\/span>/gs;
$text =~ s/(&lt;.*?)(?=<|$)/<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)];
}

55
perl/template_helpers.pm Normal file
View File

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

33
perl/timeline.pm Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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, &notifs, &notifs_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,
&notifs,
&notifs_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
}

View File

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

View File

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

163
src/conversations.c Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,7 +18,7 @@
#ifndef GLOBAL_CACHE_H
#define GLOBAL_CACHE_H
#include "mastodont.h"
#include <mastodont.h>
struct global_cache
{

102
src/global_perl.c Normal file
View File

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

86
src/global_perl.h Normal file
View File

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

View File

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

View File

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

View File

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

28
src/helpers.c Normal file
View File

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

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