Compare commits

...

160 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
223 changed files with 6087 additions and 6212 deletions

View file

@ -1,4 +1,5 @@
template
ctemplate
filec
emojitoc
**/*.cgi
@ -9,4 +10,6 @@ mastodont-c
config.h
treebird
test/tests
scripts/*.o
scripts/*.o
templates/*.ctt
test/unit/*.bin

View file

@ -2,42 +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/ -I $(MASTODONT_DIR)/libs $(shell pkg-config --cflags libcurl libpcre2-8)
LDFLAGS = -L$(MASTODONT_DIR) -lmastodont $(shell pkg-config --libs libcurl libpcre2-8) -lfcgi -lpthread
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) config.h
PAGES_DIR = static
PAGES = $(wildcard $(PAGES_DIR)/*.tmpl)
PAGES_CMP = $(patsubst %.tmpl,%.ctmpl,$(PAGES))
PAGES_C = $(patsubst %.tmpl, %.c,$(PAGES))
PAGES_C_OBJ = $(patsubst %.c,%.o,$(PAGES_C))
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 template $(PAGES_CMP) $(PAGES_C) $(PAGES_C_OBJ) $(OBJ) $(HEADERS)
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)
template: src/template/main.o
$(CC) $(LDFLAGS) -o template $<
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
# Redirect stdout and stderr into separate contents as a hack
# Let bash do the work :)
$(PAGES_DIR)/%.ctmpl: $(PAGES_DIR)/%.tmpl
./template $< $(notdir $*) 2> $(PAGES_DIR)/$(notdir $*).c 1> $@
$(TMPL_DIR)/%.ctt: $(TMPL_DIR)/%.tt
./filec $< data_$(notdir $*)_tt > $@
make_tmpls: $(TMPLS_C)
$(MASTODONT_DIR):
cd ..; fossil clone $(MASTODONT_URL) || true
@ -48,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)
@ -60,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:
@ -71,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
/*

562
dist/js/main.js vendored
View file

@ -1,145 +1,192 @@
(function(){
Element.prototype.insertAfter = function(element) {
element.parentNode.insertBefore(this, element.nextSibling);
};
"use strict";
function get_cookie(cookiestr)
// 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)
{
return document.cookie
.split(';')
.find(row => row.startsWith(cookiestr+'='))
.split('=')[1];
element.append(element);
}
// 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)
else if (typeof values === 'number') {
element.append(values.toString());
}
else if (typeof values === 'object' && child) {
for (const prop in values)
{
res += `@${x[1]}@${x[2]} `;
element[prop] = values[prop];
}
return res;
}
} // ???
function form_enter_submit(e, that)
// Type doesn't matter, just append whatever is in child
if (child)
{
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)
{
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
// Well, except if it's a string
if (typeof values === 'number')
element.append(child.toString());
else
val = parseInt(val);
val += sum;
return val > 0 ? val.toString() : "";
element.append(child);
}
return element;
}
function interact_action(status, type)
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)
{
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 ];
svg.forEach(that => {
let label = that.parentNode;
let counter = label.querySelector(".count");
let is_active = that.classList.contains("active");
that.classList.toggle("active");
if (is_active)
{
// Animation
that.classList.remove("active-anim");
// Flip itype value
type.value = type.value.replace("un", "");
}
else {
that.classList.add("active-anim");
type.value = "un" + type.value;
}
counter.innerHTML = change_count_text(counter.innerHTML, is_active ? -1 : 1);
});
res += `@${x[1]}@${x[2]} `;
}
return res;
}
function status_interact_props(e)
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)
{
let interact = e.target.closest(".statbtn");
let type = interact.parentNode.querySelector(".itype");
if (type === null)
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 status = interact.closest(".status");
let type = target.parentNode.querySelector(".itype");
let status = e.target.closest(".status");
send_request("/treebird_api/v1/interact",
{
@ -160,146 +207,145 @@
e.preventDefault();
return false;
}
}
function frame_resize()
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)
{
let rightbar_frame = document.querySelector("#rightbar .sidebar-frame");
let rbar_frame_win = rightbar_frame.contentWindow;
rightbar_frame.height = rbar_frame_win.document.body.scrollHeight;
if (i.dataset.id)
ids.push(i.dataset.id);
}
function filesize_to_str(bs)
// Goto statusbox
root = root.parentNode;
let file_ids = root.querySelector(".file-ids-json");
if (!file_ids)
{
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];
// 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);
}
function html_encode(str)
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)
{
let en = document.createElement("span");
en.textContent = str;
return en.innerHTML;
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);
}
function construct_file_upload(file, file_content)
// File upload
let file_inputs = document.querySelectorAll(".statusbox input[type=file]");
for (let file_input of file_inputs)
{
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;
file_input.addEventListener('change', evt_file_upload);
}
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 reply_btn = document.getElementsByClassName("reply-btn");
let interact_btn = document.getElementsByClassName("statbtn");
// Add event listener to add specificied buttons
for (let i = 0; i < interact_btn.length; ++i)
{
interact_btn[i].addEventListener('click', status_interact_props);
}
// 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);
}
});
})();
});

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

762
dist/treebird.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

@ -8,22 +8,45 @@ For the following GNU/Linux distributions, you will need the following libraries
###### Debian
`# apt install libcurl4-gnutls-dev libpcre2-dev libfcgi-dev base-devel`
`# 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`
`# xbps-install libcurl libcurl-devel base-devel pcre2 pcre2-devel fcgi fcgi-devel perl-Template-Toolkit`
###### Arch
`# pacman -S curl base-devel`
`# 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 `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
Run `# make install`
@ -33,15 +56,14 @@ If this succeeds (assuming you used default variables), you can now find Treebir
- `/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 shouldn't spit anything out
### Using NGINX
## Development
For developing Treebird, see `DEVELOP.md`.
## Nginx
Treebird can be served over nginx by using a FastCGI daemon such as spawn-fcgi.
The example static files will be in `/usr/local/share/treebird/`, with `treebird.cgi` at `/usr/local/bin/treebird`.
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.
## Nginx
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
@ -51,7 +73,7 @@ An example Nginx configuration is available in [treebird.nginx.conf](./sample/tr
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`.
Example Apache configuration is available in [treebird.apache.conf](./sample/treebird.apache.conf).
An example Apache configuration is available in [treebird.apache.conf](./sample/treebird.apache.conf).
#### spawn-fcgi

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

@ -20,40 +20,47 @@
#include "base_page.h"
#include "about.h"
#include "../static/about.ctmpl"
#include "../static/license.ctmpl"
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,
.content = (char*)data_about,
.content = dup,
.session = session_hv,
.sidebar_left = NULL
};
// Output
render_base_page(&b, req, ssn, api);
tb_free(dup);
}
void content_about_license(PATH_ARGS)
{
char* page;
char* referer = GET_ENV("HTTP_REFERER", req);
struct license_template tdata = {
.back_ref = referer,
.license_str = "License"
};
page = tmpl_gen_license(&tdata, NULL);
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,
.content = page,
.content = dup,
.session = session_hv,
.sidebar_left = NULL
};
// Output
render_base_page(&b, req, ssn, api);
free(page);
tb_free(dup);
}

View file

@ -18,6 +18,7 @@
#include <string.h>
#include <stdlib.h>
#include "global_perl.h"
#include "helpers.h"
#include "base_page.h"
#include "error.h"
@ -30,19 +31,8 @@
#include "base_page.h"
#include "scrobble.h"
#include "string_helpers.h"
#include "navigation.h"
#include "emoji.h"
// Files
#include "../static/account.ctmpl"
#include "../static/account_info.ctmpl"
#include "../static/account_follow_btn.ctmpl"
#include "../static/favourites_page.ctmpl"
#include "../static/bookmarks_page.ctmpl"
#include "../static/account_stub.ctmpl"
#include "../static/account_sidebar.ctmpl"
#include "../static/account_current_menubar.ctmpl"
#include "../static/basic_page.ctmpl"
#include "timeline.h"
#define FOLLOWS_YOU_HTML "<span class=\"acct-badge\">%s</span>"
@ -53,116 +43,58 @@ 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* info_html;
char* note = emojify(acct->note,
acct->emojis,
acct->emojis_len);
struct account_info_template data = {
.acct_note = note
};
info_html = tmpl_gen_account_info(&data, size);
if (note != acct->note)
free(note);
return info_html;
}
char* construct_account_sidebar(struct mstdnt_account* acct, size_t* size)
{
char* result = NULL;
char* sanitized_display_name = NULL;
char* display_name = NULL;
char* header_css = NULL;
if (acct->display_name)
{
sanitized_display_name = sanitize_html(acct->display_name);
display_name = emojify(sanitized_display_name,
acct->emojis,
acct->emojis_len);
}
easprintf(&header_css, "style=\"background: linear-gradient(var(--account-overlay-gradient-top), var(--account-overlay-gradient-bottom)), url(%s);\"", acct->header);
struct account_sidebar_template data = {
.prefix = config_url_prefix,
.avatar = acct->avatar,
.username = display_name,
.header = acct->header ? header_css : "",
.statuses_text = L10N[L10N_EN_US][L10N_TAB_STATUSES],
.following_text = L10N[L10N_EN_US][L10N_TAB_FOLLOWING],
.followers_text = L10N[L10N_EN_US][L10N_TAB_FOLLOWERS],
.statuses_count = acct->statuses_count,
.following_count = acct->following_count,
.followers_count = acct->followers_count,
.acct = acct->acct,
};
result = tmpl_gen_account_sidebar(&data, size);
if (sanitized_display_name != acct->display_name) free(sanitized_display_name);
if (display_name != sanitized_display_name &&
display_name != acct->display_name)
free(display_name);
free(header_css);
return result;
}
// TODO put account stuff into one function to cleanup a bit
static char* account_followers_cb(struct session* ssn,
mastodont_t* api,
struct mstdnt_account* acct,
void* _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_args m_args;
set_mstdnt_args(&m_args, ssn);
char* accounts_html = NULL, *navigation_box = NULL;
char* output;
struct mstdnt_storage storage = { 0 };
struct mstdnt_account* accounts = NULL;
size_t accts_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_followers(api, &m_args, acct->id, &args, &storage, &accounts, &accts_len))
{
accounts_html = construct_error(storage.error, E_ERROR, 1, NULL);
}
else {
accounts_html = construct_accounts(api, accounts, accts_len, 0, NULL);
if (!accounts_html)
accounts_html = construct_error("No followers...", E_NOTICE, 1, NULL);
}
if (accts && accts_len)
mXPUSHs(newRV_noinc((SV*)perlify_accounts(accts, accts_len)));
else ARG_UNDEFINED();
if (accounts)
{
// If not set, set it
start_id = keystr(ssn->post.start_id) ? keystr(ssn->post.start_id) : accounts[0].id;
navigation_box = construct_navigation_box(start_id,
accounts[0].id,
accounts[accts_len-1].id,
NULL);
}
easprintf(&output, "%s%s",
STR_NULL_EMPTY(accounts_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_accounts(accounts, accts_len);
if (accounts_html) free(accounts_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_following_cb(struct session* ssn,
static char* account_followers_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,
@ -171,99 +103,94 @@ static char* account_following_cb(struct session* ssn,
.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);
char* accounts_html = NULL, *navigation_box = NULL;
char* output;
struct mstdnt_storage storage = { 0 };
struct mstdnt_account* accounts = NULL;
size_t accts_len = 0;
char* start_id;
char* result;
if (mastodont_get_following(api, &m_args, acct->id, &args, &storage, &accounts, &accts_len))
{
accounts_html = construct_error(storage.error, E_ERROR, 1, NULL);
}
else {
accounts_html = construct_accounts(api, accounts, accts_len, 0, NULL);
if (!accounts_html)
accounts_html = construct_error("Not following anyone", E_NOTICE, 1, NULL);
}
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);
if (accounts)
{
// If not set, set it
start_id = keystr(ssn->post.start_id) ? keystr(ssn->post.start_id) : accounts[0].id;
navigation_box = construct_navigation_box(start_id,
accounts[0].id,
accounts[accts_len-1].id,
NULL);
}
easprintf(&output, "%s%s",
STR_NULL_EMPTY(accounts_html),
STR_NULL_EMPTY(navigation_box));
mastodont_storage_cleanup(&storage);
mstdnt_cleanup_accounts(accounts, accts_len);
if (accounts_html) free(accounts_html);
if (navigation_box) free(navigation_box);
return output;
return accounts_page(session_hv, api, acct, rel, NULL, &storage, accounts, accts_len);
}
static char* account_statuses_cb(struct session* ssn,
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;
char* statuses_html = NULL, *navigation_box = NULL;
char* output;
struct mstdnt_storage storage = { 0 };
struct mstdnt_status* statuses = NULL;
size_t statuses_len = 0;
char* start_id;
char* result;
if (mastodont_get_account_statuses(api, &m_args, 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);
}
mstdnt_get_account_statuses(api, &m_args, acct->id, args, &storage, &statuses, &statuses_len);
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));
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();
mastodont_storage_cleanup(&storage);
PERL_STACK_SCALAR_CALL("account::content_statuses");
result = PERL_GET_STACK_EXIT;
mstdnt_storage_cleanup(&storage);
mstdnt_cleanup_statuses(statuses, statuses_len);
if (statuses_html) free(statuses_html);
if (navigation_box) free(navigation_box);
return output;
return result;
}
static char* account_scrobbles_cb(struct session* ssn, mastodont_t* api, struct mstdnt_account* acct, void* _args)
static char* account_scrobbles_cb(HV* session_hv,
struct session* ssn,
mastodont_t* api,
struct mstdnt_account* acct,
struct mstdnt_relationship* rel,
void* _args)
{
(void)_args;
char* scrobbles_html = NULL;
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,
@ -271,44 +198,59 @@ static char* account_scrobbles_cb(struct session* ssn, mastodont_t* api, struct
.offset = 0,
.limit = 20
};
struct mstdnt_args m_args;
set_mstdnt_args(&m_args, ssn);
if (mastodont_get_scrobbles(api, &m_args, 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 && mastodont_verify_credentials(api, &m_args, &(ssn->acct), &(ssn->acct_storage)) == 0)
if (ssn->cookies.access_token.is_set && mstdnt_verify_credentials(api, &m_args, &(ssn->acct), &(ssn->acct_storage)) == 0)
{
ssn->logged_in = 1;
}
}
/**
* 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 };
@ -319,32 +261,18 @@ static void fetch_account_page(FCGX_Request* req,
int lookup_type = config_experimental_lookup ? MSTDNT_LOOKUP_ACCT : MSTDNT_LOOKUP_ID;
if (mastodont_get_account(api, &m_args, lookup_type, id, &acct, &storage))
{
account_page = construct_error(storage.error, E_ERROR, 1, NULL);
}
else {
// Relationships may fail
mastodont_get_relationships(api, &m_args, &(acct.id), 1, &relations_storage, &relationships, &relationships_len);
data = callback(ssn, api,
&acct, args);
account_page = load_account_page(ssn,
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,
.content = account_page,
.content = data,
.session = session_hv,
.sidebar_left = NULL
};
@ -353,218 +281,9 @@ static void fetch_account_page(FCGX_Request* req,
/* Output */
mstdnt_cleanup_account(&acct);
mstdnt_cleanup_relationships(relationships);
mastodont_storage_cleanup(&storage);
mastodont_storage_cleanup(&relations_storage);
free(account_page);
}
size_t construct_account_page(struct session* ssn,
char** result,
struct account_page* page,
char* content)
{
if (!page->account)
{
*result = NULL;
return 0;
}
size_t size;
struct mstdnt_relationship* rel = page->relationship;
int is_same_user = ssn->logged_in && strcmp(ssn->acct.acct, page->acct) == 0;
char* follow_btn = NULL,
* follow_btn_text = NULL,
* follows_you = NULL,
* info_html = NULL,
* is_blocked = NULL,
* menubar = NULL,
* display_name = NULL,
* sanitized_display_name = NULL;
sanitized_display_name = sanitize_html(page->display_name);
display_name = emojify(sanitized_display_name,
page->account->emojis,
page->account->emojis_len);
// Check if note is not empty
if (page->note && strcmp(page->note, "") != 0)
{
info_html = load_account_info(page->account, NULL);
}
// Display follow button only if not the same user
if (rel && !is_same_user)
{
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];
struct account_follow_btn_template data = {
.prefix = config_url_prefix,
.active = (rel && MSTDNT_FLAG_ISSET(rel->flags, MSTDNT_RELATIONSHIP_FOLLOWING)
? "active" : ""),
.follow_text = follow_btn_text,
.unfollow = (rel && (MSTDNT_FLAG_ISSET(rel->flags, MSTDNT_RELATIONSHIP_FOLLOWING) ||
MSTDNT_FLAG_ISSET(rel->flags, MSTDNT_RELATIONSHIP_REQUESTED))
? "un" : ""),
.userid = page->id,
};
follow_btn = tmpl_gen_account_follow_btn(&data, NULL);
}
// Display menubar with extra options for access if same user
if (is_same_user)
{
struct account_current_menubar_template acmdata = {
.prefix = config_url_prefix,
.blocked_str = "Blocks",
.muted_str = "Mutes",
.favourited_str = "Favorites",
};
menubar = tmpl_gen_account_current_menubar(&acmdata, NULL);
}
struct account_template acct_data = {
.is_blocked = STR_NULL_EMPTY(is_blocked),
.header = page->header_image,
.menubar = menubar,
.display_name = display_name,
.acct = page->acct,
.prefix = config_url_prefix,
.userid = page->id,
.follows_you = follows_you,
.unsubscribe = (rel && MSTDNT_FLAG_ISSET(rel->flags,
MSTDNT_RELATIONSHIP_NOTIFYING)
? "un" : ""),
.subscribe_text = (rel && MSTDNT_FLAG_ISSET(rel->flags,
MSTDNT_RELATIONSHIP_NOTIFYING)
? L10N[page->locale][L10N_UNSUBSCRIBE] : L10N[page->locale][L10N_SUBSCRIBE]),
.unblock = (rel && MSTDNT_FLAG_ISSET(rel->flags,
MSTDNT_RELATIONSHIP_BLOCKING)
? "un" : ""),
.block_text = (rel && MSTDNT_FLAG_ISSET(rel->flags,
MSTDNT_RELATIONSHIP_BLOCKING)
? L10N[page->locale][L10N_UNBLOCK] : L10N[page->locale][L10N_BLOCK]),
.unmute = (rel && MSTDNT_FLAG_ISSET(rel->flags,
MSTDNT_RELATIONSHIP_MUTING)
? "un" : ""),
.mute_text = (rel && MSTDNT_FLAG_ISSET(rel->flags,
MSTDNT_RELATIONSHIP_MUTING)
? L10N[page->locale][L10N_UNMUTE] : L10N[page->locale][L10N_MUTE]),
.tab_statuses_text = L10N[page->locale][L10N_TAB_STATUSES],
.statuses_count = page->statuses_count,
.tab_following_text = L10N[page->locale][L10N_TAB_FOLLOWING],
.following_count = page->following_count,
.tab_followers_text = L10N[page->locale][L10N_TAB_FOLLOWERS],
.followers_count = page->followers_count,
.follow_btn = follow_btn,
.avatar = page->profile_image,
.info = info_html,
.tab_statuses_focused = MAKE_FOCUSED_IF(page->tab, ACCT_TAB_STATUSES),
.tab_statuses_text = L10N[page->locale][L10N_TAB_STATUSES],
.tab_scrobbles_focused = MAKE_FOCUSED_IF(page->tab, ACCT_TAB_SCROBBLES),
.tab_scrobbles_text = L10N[page->locale][L10N_TAB_SCROBBLES],
.tab_media_focused = MAKE_FOCUSED_IF(page->tab, ACCT_TAB_MEDIA),
.tab_media_text = L10N[page->locale][L10N_TAB_MEDIA],
.tab_pinned_focused = MAKE_FOCUSED_IF(page->tab, ACCT_TAB_PINNED),
.tab_pinned_text = L10N[page->locale][L10N_TAB_PINNED],
.acct_content = content
};
*result = tmpl_gen_account(&acct_data, &size);
free(info_html);
free(follows_you);
free(follow_btn);
free(is_blocked);
free(menubar);
if (sanitized_display_name != page->display_name) free(sanitized_display_name);
if (display_name != page->display_name &&
display_name != sanitized_display_name)
free(display_name);
return size;
}
char* construct_account(mastodont_t* api,
struct mstdnt_account* acct,
uint8_t flags,
size_t* size)
{
char* result;
char* sanitized_display_name = sanitize_html(acct->display_name);
struct account_stub_template data = {
.prefix = config_url_prefix,
.acct = acct->acct,
.avatar = acct->avatar,
.display_name = sanitized_display_name,
};
result = tmpl_gen_account_stub(&data, size);
if (sanitized_display_name != acct->display_name) free(sanitized_display_name);
return result;
}
static char* construct_account_voidwrap(void* passed, size_t index, size_t* 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(struct session* ssn,
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_normalize(ssn->config.lang),
.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(ssn, &result, &page, content);
if (res_size) *res_size = size;
return result;
mstdnt_storage_cleanup(&storage);
mstdnt_storage_cleanup(&relations_storage);
tb_free(data);
}
void content_account_statuses(PATH_ARGS)
@ -648,37 +367,32 @@ void content_account_action(PATH_ARGS)
struct mstdnt_relationship acct = { 0 };
if (strcmp(data[1], "follow") == 0)
mastodont_follow_account(api, &m_args, 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, &m_args, 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, &m_args, 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, &m_args, 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, &m_args, 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, &m_args, 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, &m_args, 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, &m_args, data[0], &storage, &acct);
mstdnt_unsubscribe_account(api, &m_args, data[0], &storage, &acct);
mastodont_storage_cleanup(&storage);
mstdnt_storage_cleanup(&storage);
redirect(req, REDIRECT_303, referer);
}
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,
@ -688,86 +402,11 @@ void content_account_bookmarks(PATH_ARGS)
struct mstdnt_args m_args;
set_mstdnt_args(&m_args, ssn);
if (mastodont_get_bookmarks(api, &m_args, &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);
}
struct bookmarks_page_template tdata = {
.statuses = status_format,
.navigation = navigation_box
};
output = tmpl_gen_bookmarks_page(&tdata, NULL);
struct base_page b = {
.category = BASE_CAT_BOOKMARKS,
.content = output,
.sidebar_left = NULL
};
// Output
render_base_page(&b, req, ssn, api);
// Cleanup
mastodont_storage_cleanup(&storage);
mstdnt_cleanup_statuses(statuses, status_count);
free(status_format);
free(navigation_box);
free(output);
content_timeline(req, ssn, api, &storage, statuses, statuses_len, BASE_CAT_BOOKMARKS, "Bookmarks", 0, 1);
}
static void accounts_page(FCGX_Request* req,
mastodont_t* api,
struct session* ssn,
struct mstdnt_storage* storage,
char* header,
struct mstdnt_account* accts,
size_t accts_len)
{
char* output;
char* content = construct_accounts(api, accts, accts_len, 0, NULL);
if (!content)
content = construct_error("No accounts here!", E_NOTICE, 1, NULL);
struct basic_page_template tdata = {
.back_ref = getenv("HTTP_REFERER"),
.page_title = header,
.page_content = content,
};
output = tmpl_gen_basic_page(&tdata, NULL);
struct base_page b = {
.category = BASE_CAT_NONE,
.content = output,
.sidebar_left = NULL
};
// Output
render_base_page(&b, req, ssn, api);
mastodont_storage_cleanup(storage);
free(output);
free(content);
}
void content_account_blocked(PATH_ARGS)
{
struct mstdnt_account_args args = {
@ -784,10 +423,20 @@ void content_account_blocked(PATH_ARGS)
struct mstdnt_args m_args;
set_mstdnt_args(&m_args, ssn);
mastodont_get_blocks(api, &m_args, &args, &storage, &accts, &accts_len);
mstdnt_get_blocks(api, &m_args, &args, &storage, &accts, &accts_len);
accounts_page(req, api, ssn, &storage, "Blocked users", accts, accts_len);
mstdnt_cleanup_accounts(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_NONE,
.content = result,
.session = session_hv,
.sidebar_left = NULL
};
render_base_page(&b, req, ssn, api);
tb_free(result);
}
void content_account_muted(PATH_ARGS)
@ -806,72 +455,88 @@ void content_account_muted(PATH_ARGS)
struct mstdnt_args m_args;
set_mstdnt_args(&m_args, ssn);
mastodont_get_mutes(api, &m_args, &args, &storage, &accts, &accts_len);
mstdnt_get_mutes(api, &m_args, &args, &storage, &accts, &accts_len);
accounts_page(req, api, ssn, &storage, "Muted users", accts, accts_len);
mstdnt_cleanup_accounts(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)
{
size_t status_count = 0, statuses_html_count = 0;
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,
};
struct mstdnt_args m_args;
set_mstdnt_args(&m_args, ssn);
if (mastodont_get_favourites(api, &m_args, &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);
}
struct favourites_page_template tdata = {
.statuses = status_format,
.navigation = navigation_box
};
output = tmpl_gen_favourites_page(&tdata, NULL);
struct base_page b = {
.category = BASE_CAT_FAVOURITES,
.content = output,
.sidebar_left = NULL
};
// Output
render_base_page(&b, req, 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
@ -55,33 +57,6 @@ struct account_page
};
void get_account_info(mastodont_t* api, struct session* ssn);
char* construct_account_sidebar(struct mstdnt_account* acct, size_t* size);
char* construct_account(mastodont_t* api,
struct mstdnt_account* account,
uint8_t flags,
size_t* 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(struct session *ssn,
char** result,
struct account_page* page,
char* content);
char* load_account_page(struct session* ssn,
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_followers(PATH_ARGS);
void content_account_following(PATH_ARGS);
@ -95,4 +70,8 @@ void content_account_action(PATH_ARGS);
void content_account_favourites(PATH_ARGS);
void content_account_bookmarks(PATH_ARGS);
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,19 +16,21 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef REPLY_H
#define REPLY_H
#include "session.h"
#include <stddef.h>
#include <mastodont.h>
#include "applications.h"
char* construct_post_box(struct mstdnt_status* reply_id,
char* default_content,
size_t* size);
HV* perlify_application(const struct mstdnt_app* app)
{
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);
char* reply_status(struct session* ssn,
char* id,
struct mstdnt_status* status);
return app_hv;
}
#endif // REPLY_H

View file

@ -16,14 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef NAVIGATION_H
#define NAVIGATION_H
#include <stddef.h>
#ifndef APPLICATIONS_H
#define APPLICATIONS_H
#include <mastodont.h>
#include "global_perl.h"
char* construct_navigation_box(char* start_id,
char* prev_id,
char* next_id,
size_t* size);
HV* perlify_application(const struct mstdnt_app* app);
#endif // NAVIGATION_H
#endif /* APPLICATIONS_H */

View file

@ -24,13 +24,6 @@
#include "attachments.h"
#include "string_helpers.h"
// Pages
#include "../static/attachments.ctmpl"
#include "../static/attachment_image.ctmpl"
#include "../static/attachment_gifv.ctmpl"
#include "../static/attachment_video.ctmpl"
#include "../static/attachment_link.ctmpl"
#include "../static/attachment_audio.ctmpl"
struct attachments_args
{
@ -52,10 +45,10 @@ int try_upload_media(struct mstdnt_storage** storage,
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)
{
@ -71,7 +64,7 @@ int try_upload_media(struct mstdnt_storage** storage,
.description = NULL,
};
if (mastodont_upload_media(api,
if (mstdnt_upload_media(api,
&m_args,
&args,
*storage + i,
@ -79,26 +72,26 @@ int try_upload_media(struct mstdnt_storage** storage,
{
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);
}
}
@ -110,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)
@ -119,83 +112,26 @@ 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(struct session* ssn,
mstdnt_bool sensitive,
struct mstdnt_attachment* att,
size_t* str_size)
HV* perlify_attachment(const struct mstdnt_attachment* const attachment)
{
// Due to how similar the attachment templates are, we're just going to use their data files
// and not generate any templates, saves some LOC!
char* att_html;
size_t s;
const char* attachment_str;
if (!att) return NULL;
if (ssn->config.stat_attachments)
switch (att->type)
{
case MSTDNT_ATTACHMENT_IMAGE:
attachment_str = data_attachment_image; break;
case MSTDNT_ATTACHMENT_GIFV:
attachment_str = data_attachment_gifv; break;
case MSTDNT_ATTACHMENT_VIDEO:
attachment_str = data_attachment_video; break;
case MSTDNT_ATTACHMENT_AUDIO:
attachment_str = data_attachment_audio; break;
case MSTDNT_ATTACHMENT_UNKNOWN: // Fall through
default:
attachment_str = data_attachment_link; break;
}
else
attachment_str = data_attachment_link;
// Images/visible content displays sensitive placeholder after
if ((att->type == MSTDNT_ATTACHMENT_IMAGE ||
att->type == MSTDNT_ATTACHMENT_GIFV ||
att->type == MSTDNT_ATTACHMENT_VIDEO) &&
ssn->config.stat_attachments)
{
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);
}
if (str_size) *str_size = s;
return att_html;
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;
}
static char* construct_attachments_voidwrap(void* passed, size_t index, size_t* res)
{
struct attachments_args* args = passed;
return construct_attachment(args->ssn, args->sensitive, args->atts + index, res);
}
char* construct_attachments(struct session* ssn,
mstdnt_bool sensitive,
struct mstdnt_attachment* atts,
size_t atts_len,
size_t* str_size)
{
size_t elements_size;
struct attachments_args args = { ssn, 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, elements);
if (str_size) *str_size = s;
// Cleanup
free(elements);
return att_view;
}
PERLIFY_MULTI(attachment, attachments, mstdnt_attachment)
void api_attachment_create(PATH_ARGS)
{
@ -217,7 +153,7 @@ void api_attachment_create(PATH_ARGS)
{
string = cJSON_Print(root);
send_result(req, NULL, "application/json", string, 0);
free(string);
tb_free(string);
}
else
send_result(req, NULL, "application/json", "{\"status\":\"Couldn't\"}", 0);
@ -225,6 +161,6 @@ void api_attachment_create(PATH_ARGS)
// Cleanup media stuff
cleanup_media_storages(ssn, att_storage);
cleanup_media_ids(ssn, media_ids);
free(attachments);
tb_free(attachments);
cJSON_Delete(root);
}

View file

@ -21,6 +21,7 @@
#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 && \
@ -33,8 +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(struct session* ssn, mstdnt_bool sensitive, struct mstdnt_attachment* att, size_t* str_size);
char* construct_attachments(struct session* ssn, 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,7 +16,7 @@
* 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"
@ -28,179 +28,71 @@
#include "../config.h"
#include "local_config_set.h"
#include "account.h"
#include "cgi.h"
#include "global_cache.h"
// Files
#include "../static/index.ctmpl"
#include "../static/quick_login.ctmpl"
#define BODY_STYLE "style=\"background:url('%s');\""
void render_base_page(struct base_page* page, FCGX_Request* req, struct session* ssn, mastodont_t* api)
{
struct mstdnt_args m_args;
set_mstdnt_args(&m_args, ssn);
char* cookie = GET_ENV("HTTP_COOKIE", req);
enum l10n_locale locale = l10n_normalize(ssn->config.lang);
char* theme_str = NULL;
const char* login_string = "<a href=\"login\" id=\"login-header\">Login / Register</a>";
const char* sidebar_embed = "<iframe class=\"sidebar-frame\" loading=\"lazy\" src=\"/notifications_compact\"></iframe>";
char* background_url_css = NULL;
// Sidebar
char* sidebar_str,
* main_sidebar_str = NULL,
* account_sidebar_str = NULL,
* instance_str = NULL;
// Mastodont, used for notifications sidebar
struct mstdnt_storage storage = { 0 };
struct mstdnt_notification* notifs = NULL;
size_t notifs_len = 0;
#define SIDEBAR_CSS_LEN 128
char sidebar_css[SIDEBAR_CSS_LEN];
if (keyint(ssn->cookies.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))
{
account_sidebar_str = construct_account_sidebar(&(ssn->acct), NULL);
// Get / Show notifications on sidebar
if (ssn->config.notif_embed)
{
main_sidebar_str = (char*)sidebar_embed;
}
else {
struct mstdnt_get_notifications_args args = {
.exclude_types = 0,
.account_id = NULL,
.exclude_visibilities = 0,
.include_types = 0,
.with_muted = 1,
.max_id = NULL,
.min_id = NULL,
.since_id = NULL,
.offset = 0,
.limit = 8,
};
if (mastodont_get_notifications(api,
&m_args,
&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
struct quick_login_template tdata = {
.prefix = config_url_prefix,
.username = L10N[locale][L10N_USERNAME],
.password = L10N[locale][L10N_PASSWORD],
.login = L10N[locale][L10N_LOGIN_BTN],
struct mstdnt_notifications_args args = {
.exclude_types = 0,
.account_id = NULL,
.exclude_visibilities = 0,
.include_types = 0,
.with_muted = 1,
.max_id = NULL,
.min_id = NULL,
.since_id = NULL,
.offset = 0,
.limit = 8,
};
main_sidebar_str = tmpl_gen_quick_login(&tdata, NULL);
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;
// Create instance panel
if (g_cache.panel_html.response)
easprintf(&instance_str, "<div class=\"static-html\" id=\"instance-panel\">%s</div>",
(g_cache.panel_html.response ?
g_cache.panel_html.response : ""));
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 (ssn->config.theme && !(strcmp(ssn->config.theme, "treebird") == 0 &&
ssn->config.themeclr == 0))
if (notifs && notifs_len)
{
easprintf(&theme_str, "<link rel=\"stylesheet\" type=\"text/css\" href=\"/%s%s.css\">",
ssn->config.theme,
ssn->config.themeclr ? "-dark" : "");
mXPUSHs(newRV_noinc(perlify_notifications(notifs, notifs_len)));
}
else ARG_UNDEFINED();
if (ssn->config.sidebar_opacity)
{
float sidebar_opacity = (float)ssn->config.sidebar_opacity / 255.0f;
snprintf(sidebar_css, SIDEBAR_CSS_LEN, ":root { --sidebar-opacity: %.2f; }",
sidebar_opacity);
}
// Run function
PERL_STACK_SCALAR_CALL("base_page");
char* dup = PERL_GET_STACK_EXIT;
struct index_template index_tmpl = {
.title = L10N[locale][L10N_APP_NAME],
.sidebar_css = sidebar_css,
.theme_str = theme_str,
.prefix = config_url_prefix,
.background_url = background_url_css,
.name = L10N[locale][L10N_APP_NAME],
.sidebar_cnt = login_string,
.placeholder = L10N[locale][L10N_SEARCH_PLACEHOLDER],
.search_btn = L10N[locale][L10N_SEARCH_BUTTON],
.active_home = CAT_TEXT(page->category, BASE_CAT_HOME),
.home = L10N[locale][L10N_HOME],
.active_local = CAT_TEXT(page->category, BASE_CAT_LOCAL),
.local = L10N[locale][L10N_LOCAL],
.active_federated = CAT_TEXT(page->category, BASE_CAT_FEDERATED),
.federated = L10N[locale][L10N_FEDERATED],
.active_notifications = CAT_TEXT(page->category, BASE_CAT_NOTIFICATIONS),
.notifications = L10N[locale][L10N_NOTIFICATIONS],
.active_lists = CAT_TEXT(page->category, BASE_CAT_LISTS),
.lists = L10N[locale][L10N_LISTS],
.active_favourites = CAT_TEXT(page->category, BASE_CAT_FAVOURITES),
.favourites = L10N[locale][L10N_FAVOURITES],
.active_bookmarks = CAT_TEXT(page->category, BASE_CAT_BOOKMARKS),
.bookmarks = L10N[locale][L10N_BOOKMARKS],
.active_direct = CAT_TEXT(page->category, BASE_CAT_DIRECT),
.direct = L10N[locale][L10N_DIRECT],
.active_chats = CAT_TEXT(page->category, BASE_CAT_CHATS),
.chats = "Chats",
.active_config = CAT_TEXT(page->category, BASE_CAT_CONFIG),
.config = L10N[locale][L10N_CONFIG],
.sidebar_leftbar = page->sidebar_left,
.instance_panel = ssn->config.instance_panel ? instance_str : "",
.main = page->content,
.sidebar_rightbar = sidebar_str,
.about_link_str = "About",
.license_link_str = "License",
.source_link_str = "Source code",
};
size_t len;
char* data = tmpl_gen_index(&index_tmpl, &len);
send_result(req, NULL, "text/html", dup, 0);
if (!data)
{
perror("malloc");
goto cleanup;
}
send_result(req, NULL, "text/html", data, len);
// Cleanup
/* cleanup_all: */
free(data);
cleanup:
free(sidebar_str);
if (main_sidebar_str != sidebar_embed) free(main_sidebar_str);
free(account_sidebar_str);
free(background_url_css);
free(instance_str);
free(theme_str);
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)
@ -209,6 +101,7 @@ void send_result(FCGX_Request* req, char* status, char* content_type, char* data
#ifdef SINGLE_THREADED
printf(
#else
pthread_mutex_lock(&print_mutex);
FCGX_FPrintF(req->out,
#endif
"Status: %s\r\n"
@ -216,10 +109,11 @@ void send_result(FCGX_Request* req, char* status, char* content_type, char* data
"Content-Length: %d\r\n\r\n",
status ? status : "200 OK",
content_type ? content_type : "text/html",
data_len + 1);
data_len);
#ifdef SINGLE_THREADED
puts(data);
#else
FCGX_PutStr(data, data_len, req->out);
pthread_mutex_unlock(&print_mutex);
#endif
}

View file

@ -18,13 +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 "path.h"
#include "session.h"
#include <pthread.h>
enum base_category
{
@ -46,6 +49,7 @@ struct base_page
enum base_category category;
char* content;
char* sidebar_left;
HV* session;
};
void render_base_page(struct base_page* page, FCGX_Request* req, struct session* ssn, mastodont_t* api);

View file

@ -16,13 +16,15 @@
* 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 IMPORT_CGI_H */
/* #define IMPORT_CGI_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);
#ifndef NO_FCGI
#include <fcgi_stdio.h>
#endif // NO_FCGI
#endif // TYPE_STRING_H
#ifndef SINGLE_THREADED
#include <fcgiapp.h>
#endif // SINGLE_THREADED
// #endif

View file

@ -17,6 +17,8 @@
*/
#include <stdlib.h>
#include "account.h"
#include "emoji.h"
#include "../config.h"
#include "conversations.h"
#include "helpers.h"
@ -24,138 +26,6 @@
#include "error.h"
#include "base_page.h"
// Files
#include "../static/chat.ctmpl"
#include "../static/chats_page.ctmpl"
#include "../static/message.ctmpl"
#include "../static/chat_view.ctmpl"
#include "../static/embed.ctmpl"
struct construct_message_args
{
struct mstdnt_message* msg;
struct mstdnt_account* you;
struct mstdnt_account* them;
size_t msg_size; // Read messages backwards
};
struct construct_chats_args
{
mastodont_t* api;
struct mstdnt_args* args;
struct mstdnt_chat* chats;
};
char* construct_chat(mastodont_t* api,
struct mstdnt_args* m_args,
struct mstdnt_chat* chat,
size_t* size)
{
char* result;
char* msg_id = NULL;
char* last_message = "<span class=\"empty-chat-text\">Chat created</span>";
// Get latest message
struct mstdnt_storage storage = { 0 };
struct mstdnt_message* messages = NULL;
size_t messages_len = 0;
struct mstdnt_chats_args args = {
.with_muted = MSTDNT_TRUE,
.offset = 0,
.limit = 1,
};
if (mastodont_get_chat_messages(api, m_args, chat->id, &args, &storage,
&messages, &messages_len) == 0 && messages_len == 1)
{
last_message = messages[0].content;
msg_id = messages[0].id;
}
struct chat_template data = {
.id = chat->id,
.prefix = config_url_prefix,
.acct = chat->account.acct,
.avatar = chat->account.avatar,
.display_name = chat->account.display_name,
.message_id = msg_id,
.last_message = last_message,
};
result = tmpl_gen_chat(&data, size);
mastodont_storage_cleanup(&storage);
// TODO cleanup messages
return result;
}
static char* construct_chat_voidwrap(void* passed, size_t index, size_t* res)
{
struct construct_chats_args* args = passed;
return construct_chat(args->api, args->args, args->chats + index, res);
}
char* construct_chats(mastodont_t* api,
struct mstdnt_args* m_args,
struct mstdnt_chat* chats,
size_t size,
size_t* ret_size)
{
struct construct_chats_args args = {
.api = api,
.args = m_args,
.chats = chats,
};
return construct_func_strings(construct_chat_voidwrap, &args, size, ret_size);
}
char* construct_message(struct mstdnt_message* msg,
struct mstdnt_account* you,
struct mstdnt_account* them,
size_t* size)
{
char* result;
if (!(you && them)) return NULL;
int is_you = strcmp(you->id, msg->account_id) == 0;
struct message_template data = {
.id = msg->id,
.content = msg->content,
.is_you = is_you ? "message-you" : NULL,
.avatar = is_you ? you->avatar : them->avatar
};
result = tmpl_gen_message(&data, size);
return result;
}
static char* construct_message_voidwrap(void* passed, size_t index, size_t* res)
{
struct construct_message_args* args = passed;
return construct_message(args->msg + (args->msg_size - index - 1), args->you, args->them, res);
}
char* construct_messages(struct mstdnt_message* messages,
struct mstdnt_account* you,
struct mstdnt_account* them,
size_t size,
size_t* ret_size)
{
struct construct_message_args args = {
.msg = messages,
.you = you,
.them = them,
.msg_size = size
};
return construct_func_strings(construct_message_voidwrap, &args, size, ret_size);
}
char* construct_chats_view(char* lists_string, size_t* size)
{
struct chats_page_template data = {
.content = lists_string,
};
return tmpl_gen_chats_page(&data, size);
}
void content_chats(PATH_ARGS)
{
struct mstdnt_args m_args;
@ -163,8 +33,6 @@ void content_chats(PATH_ARGS)
struct mstdnt_chat* chats = NULL;
size_t chats_len = 0;
struct mstdnt_storage storage = { 0 };
char* chats_page = NULL;
char* chats_html = NULL;
struct mstdnt_chats_args args = {
.with_muted = MSTDNT_TRUE,
@ -175,20 +43,25 @@ void content_chats(PATH_ARGS)
.limit = 20,
};
if (mastodont_get_chats_v2(api, &m_args, &args, &storage, &chats, &chats_len))
{
chats_page = construct_error(storage.error, E_ERROR, 1, NULL);
}
else {
chats_html = construct_chats(api, &m_args, chats, chats_len, NULL);
if (!chats_html)
chats_html = construct_error("No chats", E_NOTICE, 1, NULL);
chats_page = construct_chats_view(chats_html, NULL);
}
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 = chats_page,
.content = dup,
.session = session_hv,
.sidebar_left = NULL
};
@ -196,13 +69,12 @@ void content_chats(PATH_ARGS)
render_base_page(&b, req, ssn, api);
// Cleanup
mastodont_storage_cleanup(&storage);
free(chats_page);
free(chats_html);
// TOOD cleanup chats
mstdnt_storage_cleanup(&storage);
mstdnt_cleanup_chats(chats, chats_len);
tb_free(dup);
}
char* construct_chat_view(struct session* ssn, mastodont_t* api, char* id, size_t* len)
void content_chat_view(PATH_ARGS)
{
struct mstdnt_args m_args;
set_mstdnt_args(&m_args, ssn);
@ -211,9 +83,6 @@ char* construct_chat_view(struct session* ssn, mastodont_t* api, char* id, size_
size_t messages_len = 0;
struct mstdnt_storage storage = { 0 }, storage_chat = { 0 };
struct mstdnt_chat chat;
struct mstdnt_storage acct_storage = { 0 };
char* chats_page = NULL;
char* messages_html = NULL;
struct mstdnt_chats_args args = {
.with_muted = MSTDNT_TRUE,
@ -224,72 +93,71 @@ char* construct_chat_view(struct session* ssn, mastodont_t* api, char* id, size_
.limit = 20,
};
if (len) *len = 0;
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);
if (mastodont_get_chat_messages(api, &m_args, id,
&args, &storage, &messages, &messages_len) ||
mastodont_get_chat(api, &m_args, id,
&storage_chat, &chat))
{
chats_page = construct_error(storage.error, E_ERROR, 1, NULL);
}
else {
messages_html = construct_messages(messages, &(ssn->acct), &(chat.account), messages_len, NULL);
if (!messages_html)
messages_html = construct_error("This is the start of something new...", E_NOTICE, 1, NULL);
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");
struct chat_view_template tmpl = {
.back_link = "/chats",
.prefix = config_url_prefix,
.avatar = chat.account.avatar,
.acct = chat.account.acct,
.messages = messages_html
};
chats_page = tmpl_gen_chat_view(&tmpl, len);
}
mastodont_storage_cleanup(&storage);
mastodont_storage_cleanup(&acct_storage);
free(messages_html);
// TODO cleanup messages
return chats_page;
}
void content_chat_view(PATH_ARGS)
{
char* chat_view = construct_chat_view(ssn, api, data[0], NULL);
// Duplicate so we can free the TMPs
char* dup = PERL_GET_STACK_EXIT;
struct base_page b = {
.category = BASE_CAT_CHATS,
.content = chat_view,
.content = dup,
.session = session_hv,
.sidebar_left = NULL
};
// Output
render_base_page(&b, req, ssn, api);
free(chat_view);
mstdnt_storage_cleanup(&storage);
mstdnt_storage_cleanup(&storage_chat);
mstdnt_cleanup_chat(&chat);
mstdnt_cleanup_messages(messages);
tb_free(dup);
}
void content_chat_embed(PATH_ARGS)
HV* perlify_chat(const struct mstdnt_chat* chat)
{
size_t result_len;
char* result;
char* chat_view = construct_chat_view(ssn, api, data[0], NULL);
if (!chat) return NULL;
struct embed_template tmpl = {
.stylesheet = "treebird20",
.embed = chat_view,
};
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);
result = tmpl_gen_embed(&tmpl, &result_len);
// Output
send_result(req, NULL, NULL, result, result_len);
free(chat_view);
free(result);
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

@ -23,30 +23,13 @@
#include <mastodont.h>
#include "session.h"
char* construct_chat(mastodont_t* api,
struct mstdnt_args* m_args,
struct mstdnt_chat* chat,
size_t* size);
char* construct_chats(mastodont_t* api,
struct mstdnt_args* m_args,
struct mstdnt_chat* chats,
size_t size,
size_t* ret_size);
char* construct_chats_view(char* lists_string, size_t* size);
// Message
char* construct_message(struct mstdnt_message* message,
struct mstdnt_account* your_profile,
struct mstdnt_account* their_profile,
size_t* size);
char* construct_messages(struct mstdnt_message* message,
struct mstdnt_account* your_profile,
struct mstdnt_account* their_profile,
size_t size,
size_t* ret_size);
void content_chats(PATH_ARGS);
char* construct_chat_view(struct session* ssn, mastodont_t* api, char* id, size_t* len);
void content_chat_embed(PATH_ARGS);
void content_chat_view(PATH_ARGS);
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,11 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <fcgi_stdio.h>
#include <fcgiapp.h>
#include "cookie.h"
#include <string.h>
#include <stdlib.h>
#include "cookie.h"
#include "env.h"
enum cookie_state
@ -31,14 +29,14 @@ enum cookie_state
STATE_V_START,
};
char* read_cookies_env(FCGX_Request* req, struct cookie_values* cookies)
char* read_cookies_env(REQUEST_T req, struct cookie_values* cookies)
{
struct http_cookie_info info;
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");
@ -164,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,7 +19,10 @@
#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
{
@ -58,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(FCGX_Request* req, 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,19 +16,14 @@
* 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.ctmpl"
#include "../static/emoji_plain.ctmpl"
#include "../static/emoji_picker.ctmpl"
enum emoji_categories
{
EMO_CAT_SMILEYS,
@ -42,38 +37,6 @@ enum emoji_categories
EMO_CAT_LEN
};
char* emojify(char* content, struct mstdnt_emoji* emos, size_t emos_len)
{
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\" loading=\"lazy\">", 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;
}
struct construct_emoji_picker_args
{
char* status_id;
@ -84,21 +47,14 @@ char* construct_emoji(struct emoji_info* emoji, char* status_id, size_t* size)
{
if (!emoji)
return NULL;
char* emoji_str;
if (status_id)
{
struct emoji_template data = {
.status_id = status_id,
.emoji = emoji->codes
};
return tmpl_gen_emoji(&data, size);
}
else {
struct emoji_plain_template data = {
.emoji = emoji->codes
};
return tmpl_gen_emoji_plain(&data, size);
}
*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, size_t* res)
@ -116,7 +72,7 @@ void content_emoji_picker(PATH_ARGS)
send_result(req, NULL, NULL, picker, 0);
free(picker);
tb_free(picker);
}
char* construct_emoji_picker(char* status_id, size_t* size)
@ -136,30 +92,48 @@ char* construct_emoji_picker(char* status_id, size_t* size)
};
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, NULL);
emojis[EMO_CAT_ANIMALS] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_ANIMALS, EMOJO_CAT_FOOD - EMOJO_CAT_ANIMALS, NULL);
emojis[EMO_CAT_FOOD] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_FOOD, EMOJO_CAT_TRAVEL - EMOJO_CAT_FOOD, NULL);
emojis[EMO_CAT_TRAVEL] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_TRAVEL, EMOJO_CAT_ACTIVITIES - EMOJO_CAT_TRAVEL, NULL);
emojis[EMO_CAT_ACTIVITIES] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_ACTIVITIES, EMOJO_CAT_OBJECTS - EMOJO_CAT_ACTIVITIES, NULL);
emojis[EMO_CAT_OBJECTS] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_OBJECTS, EMOJO_CAT_SYMBOLS - EMOJO_CAT_OBJECTS, NULL);
emojis[EMO_CAT_SYMBOLS] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_SYMBOLS, EMOJO_CAT_FLAGS - EMOJO_CAT_SYMBOLS, NULL);
emojis[EMO_CAT_FLAGS] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_FLAGS, EMOJO_CAT_MAX - EMOJO_CAT_FLAGS, NULL);
struct emoji_picker_template data = {
.emojis_smileys = emojis[EMO_CAT_SMILEYS],
.emojis_animals = emojis[EMO_CAT_ANIMALS],
.emojis_food = emojis[EMO_CAT_FOOD],
.emojis_travel = emojis[EMO_CAT_TRAVEL],
.emojis_activities = emojis[EMO_CAT_ACTIVITIES],
.emojis_objects = emojis[EMO_CAT_OBJECTS],
.emojis_symbols = emojis[EMO_CAT_SYMBOLS],
.emojis_flags = emojis[EMO_CAT_FLAGS],
};
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);
emoji_picker_html = tmpl_gen_emoji_picker(&data, size);
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)
free(emojis[i]);
return emoji_picker_html;
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

@ -20,7 +20,9 @@
#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
@ -31,9 +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, 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

@ -22,67 +22,17 @@
#include <stdlib.h>
#include "easprintf.h"
// Templates
#include "../static/custom_emoji_reaction.ctmpl"
#include "../static/emoji_reaction.ctmpl"
#include "../static/emoji_reactions.ctmpl"
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, size_t* str_size)
{
char* ret;
char* emoji = emo->name;
if (emo->url)
{
struct custom_emoji_reaction_template c_data = {
.url = emo->url,
};
emoji = tmpl_gen_custom_emoji_reaction(&c_data, NULL);
}
struct emoji_reaction_template data = {
.prefix = config_url_prefix,
.status_id = id,
.custom_emoji = emo->url ? "custom-emoji-container" : NULL,
.emoji = emo->name,
.emoji_display = emoji,
.emoji_active = emo->me ? "active" : NULL,
.emoji_count = emo->count
};
ret = tmpl_gen_emoji_reaction(&data, str_size);
if (emoji != emo->name)
free(emoji);
return ret;
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, size_t* 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;
struct emoji_reactions_template data = {
.emojis = elements
};
emos_view = tmpl_gen_emoji_reactions(&data, str_size);
// 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, size_t* 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

@ -21,46 +21,12 @@
#include "easprintf.h"
#include "l10n.h"
// Pages
#include "../static/error_404.ctmpl"
#include "../static/error.ctmpl"
char* construct_error(const char* error, enum error_type type, unsigned pad, size_t* size)
{
char* class;
switch (type)
{
case E_ERROR:
class = "error"; break;
case E_WARNING:
class = "warning"; break;
case E_NOTICE:
class = "notice"; break;
}
struct error_template data = {
.err_type = class,
.is_padded = pad ? "error-pad" : NULL,
.error = error ? error : "An error occured",
};
return tmpl_gen_error(&data, size);
}
void content_not_found(FCGX_Request* req, struct session* ssn, mastodont_t* api, char* path)
{
char* page;
struct error_404_template data = {
.error = L10N[L10N_EN_US][L10N_PAGE_NOT_FOUND]
};
page = tmpl_gen_error_404(&data, NULL);
struct base_page b = {
.content = page,
.content = "Content not found",
.sidebar_left = NULL
};
render_base_page(&b, req, ssn, api);
free(page);
}

View file

@ -23,14 +23,6 @@
#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(FCGX_Request* req, struct session* ssn, mastodont_t* api, char* path);
#endif // ERROR_H

View file

@ -28,12 +28,12 @@ void load_instance_info_cache(mastodont_t* api)
.token = 0,
.flags = config_library_flags,
};
mastodont_instance_panel(api, &m_args, &(g_cache.panel_html));
mastodont_terms_of_service(api, &m_args, &(g_cache.tos_html));
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,121 +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.ctmpl"
#include "../static/bar_graph.ctmpl"
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)
{
struct bar_graph_template data = {
.graph = bars,
};
return tmpl_gen_bar_graph(&data, size);
}
char* construct_bar(float value, size_t* size)
{
struct bar_template data = {
.value = value * 100
};
return tmpl_gen_bar(&data, size);
}
static char* construct_hashgraph_voidwrap(void* passed, size_t index, size_t* 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.ctmpl"
#include "../static/hashtag_page.ctmpl"
#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, size_t* size)
{
// Lol!
unsigned hash_size = TAG_SIZE_INITIAL +
CLAMP(hashtag_history_daily_uses(7, hashtag->history, hashtag->history_len)*2, 0, 42);
struct hashtag_template data = {
.prefix = config_url_prefix,
.tag = hashtag->name,
.tag_size = hash_size,
};
return tmpl_gen_hashtag(&data, size);
}
static char* construct_hashtag_voidwrap(void* passed, size_t index, size_t* 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, size_t* size);
char* construct_hashtags(struct mstdnt_tag* hashtags, size_t size, size_t* ret_size);
// TODO?
#endif /* HASHTAG_H */

View file

@ -27,15 +27,14 @@
#define REDIR_HTML_END "</body>" \
"</html>"
void redirect(FCGX_Request* req, char* status, char* location)
void redirect(REQUEST_T req, char* status, char* location)
{
char* loc_str = location ? location : "/";
FCGX_FPrintF(req->out,
"Status: %s\r\n"
"Location: %s\r\n\r\n"
REDIR_HTML_BEGIN "Redirecting to <a href=\"\">%s</a>..." REDIR_HTML_END,
status,
loc_str,
loc_str);
PRINTF("Status: %s\r\n"
"Location: %s\r\n\r\n"
REDIR_HTML_BEGIN "Redirecting to <a href=\"\">%s</a>..." REDIR_HTML_END,
status,
loc_str,
loc_str);
}

View file

@ -18,11 +18,11 @@
#ifndef HTTP_H
#define HTTP_H
#include <fcgi_stdio.h>
#include <fcgiapp.h>
#include "request.h"
#include "cgi.h"
#define REDIRECT_303 "303 See Other"
void redirect(FCGX_Request* req, char* status, char* location);
void redirect(REQUEST_T req, char* status, char* location);
#endif // HTTP_H

View file

@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "global_perl.h"
#include <stdlib.h>
#include <limits.h>
#include "key.h"
@ -46,13 +47,13 @@ void key_files(char* val, struct file_content* form, struct key* arg)
struct file_array* arr = &(arg->type.f);
char* content_cpy;
arr->content = realloc(arr->content,
arr->content = tb_realloc(arr->content,
sizeof(struct file_content) * ++(arr->array_size));
if (!(arr->content))
return;
// Make a copy so we can remember it later
if (!(content_cpy = malloc(form->content_size+1)))
if (!(content_cpy = tb_malloc(form->content_size+1)))
return;
memcpy(content_cpy, val, form->content_size+1);

View file

@ -17,6 +17,7 @@
*/
#include <stdlib.h>
#include "global_perl.h"
#include "helpers.h"
#include "base_page.h"
#include "../config.h"
@ -28,55 +29,14 @@
#include "lists.h"
#include "string_helpers.h"
#include "http.h"
// Files
#include "../static/account.ctmpl"
#include "../static/list.ctmpl"
#include "../static/lists.ctmpl"
char* construct_list(struct mstdnt_list* list, size_t* size)
{
char* result;
char* title = list->title;
char* list_name = sanitize_html(title);
struct list_template data = {
.list = list_name,
.prefix = config_url_prefix,
.list_id = list->id
};
result = tmpl_gen_list(&data, size);
if (list_name != title)
free(list_name);
return result;
}
static char* construct_list_voidwrap(void* passed, size_t index, size_t* res)
{
return construct_list((struct mstdnt_list*)passed + index, res);
}
char* construct_lists(struct mstdnt_list* lists, size_t size, size_t* ret_size)
{
return construct_func_strings(construct_list_voidwrap, lists, size, ret_size);
}
char* construct_lists_view(char* lists_string, size_t* size)
{
struct lists_template data = {
.lists = lists_string,
.prefix = config_url_prefix
};
return tmpl_gen_lists(&data, size);
}
void content_lists(PATH_ARGS)
{
struct mstdnt_args m_args;
set_mstdnt_args(&m_args, ssn);
struct mstdnt_list* lists = NULL;
size_t size_list = 0;
size_t lists_len = 0;
struct mstdnt_storage storage = { 0 };
char* lists_format = NULL;
char* lists_page = NULL;
if (ssn->post.title.is_set)
{
@ -85,24 +45,28 @@ void content_lists(PATH_ARGS)
.title = keystr(ssn->post.title),
.replies_policy = MSTDNT_LIST_REPLIES_POLICY_LIST,
};
mastodont_create_list(api, &m_args, &args, &create_storage, NULL);
mastodont_storage_cleanup(&create_storage);
mstdnt_create_list(api, &m_args, &args, &create_storage, NULL);
mstdnt_storage_cleanup(&create_storage);
}
if (mastodont_get_lists(api, &m_args, &storage, &lists, &size_list))
{
lists_page = construct_error(storage.error, E_ERROR, 1, NULL);
}
else {
lists_format = construct_lists(lists, size_list, NULL);
if (!lists_format)
lists_format = construct_error("No lists", E_ERROR, 1, NULL);
lists_page = construct_lists_view(lists_format, NULL);
}
mstdnt_get_lists(api, &m_args, &storage, &lists, &lists_len);
PERL_STACK_INIT;
HV* session_hv = perlify_session(ssn);
XPUSHs(newRV_noinc((SV*)session_hv));
XPUSHs(newRV_noinc((SV*)template_files));
if (lists)
mXPUSHs(newRV_noinc((SV*)perlify_lists(lists, lists_len)));
PERL_STACK_SCALAR_CALL("lists::content_lists");
// Duplicate so we can free the TMPs
char* dup = PERL_GET_STACK_EXIT;
struct base_page b = {
.category = BASE_CAT_LISTS,
.content = lists_page,
.content = dup,
.session = session_hv,
.sidebar_left = NULL
};
@ -110,10 +74,9 @@ void content_lists(PATH_ARGS)
render_base_page(&b, req, ssn, api);
// Cleanup
mastodont_storage_cleanup(&storage);
if (lists_format) free(lists_format);
if (lists_page) free(lists_page);
mstdnt_storage_cleanup(&storage);
mstdnt_cleanup_lists(lists);
tb_free(dup);
}
void list_edit(PATH_ARGS)
@ -129,7 +92,7 @@ void list_edit(PATH_ARGS)
.replies_policy = keyint(ssn->post.replies_policy)
};
mastodont_update_list(api,
mstdnt_update_list(api,
&m_args,
id,
&args,
@ -137,5 +100,20 @@ void list_edit(PATH_ARGS)
NULL);
redirect(req, REDIRECT_303, referer);
mastodont_storage_cleanup(&storage);
mstdnt_storage_cleanup(&storage);
}
HV* perlify_list(const struct mstdnt_list* list)
{
if (!list) return NULL;
HV* list_hv = newHV();
hvstores_str(list_hv, "id", list->id);
hvstores_str(list_hv, "title", list->title);
// hvstores_int(list_hv, "replies_policy", list->replies_policy);
return list_hv;
}
PERLIFY_MULTI(list, lists, mstdnt_list)

View file

@ -22,10 +22,15 @@
#include <mastodont.h>
#include "session.h"
char* construct_list(struct mstdnt_list* list, size_t* size);
char* construct_lists(struct mstdnt_list* lists, size_t size, size_t* ret_size);
char* construct_lists_view(char* lists_string, size_t* size);
/** Creates the main lists view */
void content_lists(PATH_ARGS);
/** Creates a list and then redirects */
void list_edit(PATH_ARGS);
/** Converts list to perl hash */
HV* perlify_list(const struct mstdnt_list* list);
/** Converts lists to perl array */
AV* perlify_lists(const struct mstdnt_list* lists, size_t len);
#endif // LISTS_H

47
src/local_config.c Normal file
View file

@ -0,0 +1,47 @@
/*
* 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"
#include "local_config.h"
HV* perlify_config(struct local_config* config)
{
HV* ssn_config_hv = newHV();
hv_stores(ssn_config_hv, "logged_in", newSVpv(config->logged_in, 0));
hv_stores(ssn_config_hv, "theme", newSVpv(config->theme, 0));
hv_stores(ssn_config_hv, "background_url", newSVpv(config->background_url, 0));
hv_stores(ssn_config_hv, "lang", newSViv(config->lang));
hv_stores(ssn_config_hv, "jsactions", newSViv(config->jsactions));
hv_stores(ssn_config_hv, "jslive", newSViv(config->jslive));
hv_stores(ssn_config_hv, "js", newSViv(config->js));
hv_stores(ssn_config_hv, "interact_img", newSViv(config->interact_img));
hv_stores(ssn_config_hv, "stat_attachments", newSViv(config->stat_attachments));
hv_stores(ssn_config_hv, "stat_greentexts", newSViv(config->stat_greentexts));
hv_stores(ssn_config_hv, "stat_dope", newSViv(config->stat_dope));
hv_stores(ssn_config_hv, "stat_oneclicksoftware", newSViv(config->stat_oneclicksoftware));
hv_stores(ssn_config_hv, "stat_emojo_likes", newSViv(config->stat_emojo_likes));
hv_stores(ssn_config_hv, "stat_hide_muted", newSViv(config->stat_hide_muted));
hv_stores(ssn_config_hv, "instance_show_shoutbox", newSViv(config->instance_show_shoutbox));
hv_stores(ssn_config_hv, "instance_panel", newSViv(config->instance_panel));
hv_stores(ssn_config_hv, "notif_embed", newSViv(config->notif_embed));
hv_stores(ssn_config_hv, "sidebar_opacity", newSViv(config->sidebar_opacity));
return ssn_config_hv;
}

View file

@ -18,6 +18,7 @@
#ifndef LOCAL_CONFIG_H
#define LOCAL_CONFIG_H
#include "global_perl.h"
#include "query.h"
struct local_config
@ -26,7 +27,6 @@ struct local_config
char* theme;
char* background_url;
int lang;
int themeclr;
int jsactions;
int jsreply;
int jslive;
@ -44,4 +44,6 @@ struct local_config
int sidebar_opacity;
};
HV* perlify_config(struct local_config* config);
#endif // LOCAL_CONFIG_H

View file

@ -34,9 +34,8 @@ void set_config_str(FCGX_Request* req,
{
if (ssn->post.set.is_set && post->is_set && page == curr_page)
{
FCGX_FPrintF(req->out,
"Set-Cookie: %s=%s; HttpOnly; Path=/; Max-Age=31536000; SameSite=Strict;\r\n",
cookie_name, keypstr(post));
PRINTF("Set-Cookie: %s=%s; HttpOnly; Path=/; Max-Age=31536000; SameSite=Strict;\r\n",
cookie_name, keypstr(post));
}
if ((ssn->post.set.is_set && post->is_set) || cookie->is_set)
@ -60,9 +59,8 @@ void set_config_int(FCGX_Request* req,
{
if (ssn->post.set.is_set && page == curr_page)
{
FCGX_FPrintF(req->out,
"Set-Cookie: %s=%d; HttpOnly; Path=/; Max-Age=31536000; SameSite=Strict;\r\n",
cookie_name, post_bool_intp(post));
PRINTF("Set-Cookie: %s=%d; HttpOnly; Path=/; Max-Age=31536000; SameSite=Strict;\r\n",
cookie_name, post_bool_intp(post));
}
// Checks if boolean option
@ -93,7 +91,6 @@ struct mstdnt_storage* load_config(FCGX_Request* req,
set_config_str(req, ssn, &(ssn->config.background_url), "background_url", &(atm), &(ssn->cookies.background_url), page, CONFIG_APPEARANCE);
set_config_int(LOAD_CFG_SIM("sidebaropacity", sidebar_opacity), CONFIG_APPEARANCE);
set_config_str(LOAD_CFG_SIM("theme", theme), CONFIG_APPEARANCE);
set_config_int(LOAD_CFG_SIM("themeclr", themeclr), CONFIG_APPEARANCE);
set_config_int(LOAD_CFG_SIM("jsactions", jsactions), CONFIG_GENERAL);
set_config_int(LOAD_CFG_SIM("jsreply", jsreply), CONFIG_GENERAL);
set_config_int(LOAD_CFG_SIM("jslive", jslive), CONFIG_GENERAL);

View file

@ -19,12 +19,11 @@
#ifndef LOCAL_CONFIG_SET_H
#define LOCAL_CONFIG_SET_H
#include <mastodont.h>
#include <fcgi_stdio.h>
#include <fcgiapp.h>
#include "local_config.h"
#include "session.h"
#include "attachments.h"
#include "local_config.h"
#include "key.h"
#include "cgi.h"
enum config_page
{
@ -76,7 +75,7 @@ void set_config_int(FCGX_Request* req,
* Loads the config and sets the values based on POST or session
*
* @param ssn The session
* @param api mastodont-c api
* @param api mstdnt-c api
* @param page Page enum, to ensure that config changes on different pages don't effect other cookies
* @return Storage if files were uploaded, must free. This might change
*/

View file

@ -16,28 +16,26 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <curl/curl.h>
#include <fcgi_stdio.h>
#include "login.h"
#include <string.h>
#include <stdlib.h>
#include "helpers.h"
#include "query.h"
#include "base_page.h"
#include "login.h"
#include "error.h"
#include "easprintf.h"
#include "../config.h"
#include "http.h"
// Files
#include "../static/login.ctmpl"
#include <curl/curl.h>
#include "cgi.h"
#include "request.h"
#define LOGIN_SCOPE "read+write+follow+push"
void apply_access_token(FCGX_Request* req, char* token)
static void apply_access_token(REQUEST_T req, char* token)
{
FCGX_FPrintF(req->out, "Set-Cookie: access_token=%s; Path=/; Max-Age=31536000\r\n", token);
FCGX_FPrintF(req->out, "Set-Cookie: logged_in=t; Path=/; Max-Age=31536000\r\n");
PRINTF("Set-Cookie: access_token=%s; Path=/; Max-Age=31536000\r\n", token);
PUT("Set-Cookie: logged_in=t; Path=/; Max-Age=31536000\r\n");
// if config_url_prefix is empty, make it root
redirect(req, REDIRECT_303, config_url_prefix &&
config_url_prefix[0] != '\0' ? config_url_prefix : "/");
@ -69,7 +67,7 @@ void content_login_oauth(PATH_ARGS)
.code = keystr(ssn->query.code),
};
if (mastodont_obtain_oauth_token(api,
if (mstdnt_obtain_oauth_token(api,
&m_args,
&args_token,
&oauth_storage,
@ -90,7 +88,7 @@ void content_login_oauth(PATH_ARGS)
.website = keystr(ssn->post.instance)
};
if (mastodont_register_app(api,
if (mstdnt_register_app(api,
&m_args,
&args_app,
&storage,
@ -102,12 +100,12 @@ void content_login_oauth(PATH_ARGS)
decode_url, encode_id, urlify_redirect_url);
// Set cookie and redirect
FCGX_FPrintF(req->out, "Set-Cookie: instance_url=%s; Path=/; Max-Age=3153600\r\n", decode_url);
FCGX_FPrintF(req->out, "Set-Cookie: client_id=%s; Path=/; Max-Age=3153600\r\n", app.client_id);
FCGX_FPrintF(req->out, "Set-Cookie: client_secret=%s; Path=/; Max-Age=3153600\r\n", app.client_secret);
PRINTF("Set-Cookie: instance_url=%s; Path=/; Max-Age=3153600\r\n", decode_url);
PRINTF("Set-Cookie: client_id=%s; Path=/; Max-Age=3153600\r\n", app.client_id);
PRINTF("Set-Cookie: client_secret=%s; Path=/; Max-Age=3153600\r\n", app.client_secret);
redirect(req, REDIRECT_303, url);
free(url);
tb_free(url);
curl_free(encode_id);
}
}
@ -117,9 +115,9 @@ void content_login_oauth(PATH_ARGS)
redirect(req, REDIRECT_303, config_url_prefix &&
config_url_prefix[0] != '\0' ? config_url_prefix : "/");
mastodont_storage_cleanup(&storage);
mastodont_storage_cleanup(&oauth_storage);
if (urlify_redirect_url) free(urlify_redirect_url);
mstdnt_storage_cleanup(&storage);
mstdnt_storage_cleanup(&oauth_storage);
if (urlify_redirect_url) tb_free(urlify_redirect_url);
if (decode_url) curl_free(decode_url);
}
@ -161,9 +159,9 @@ void content_login(PATH_ARGS)
m_args.url = config_instance_url;
}
if (mastodont_register_app(api, &m_args, &args_app, &storage, &app) != 0)
if (mstdnt_register_app(api, &m_args, &args_app, &storage, &app) != 0)
{
error = construct_error(oauth_store.error, E_ERROR, 1, NULL);
// error = construct_error(oauth_store.error, E_ERROR, 1, NULL);
}
else {
struct mstdnt_application_args args_token = {
@ -177,23 +175,24 @@ void content_login(PATH_ARGS)
.password = keystr(ssn->post.password)
};
if (mastodont_obtain_oauth_token(api,
if (mstdnt_obtain_oauth_token(api,
&m_args,
&args_token,
&oauth_store,
&token) != 0 && oauth_store.error)
{
error = construct_error(oauth_store.error, E_ERROR, 1, NULL);
//error = construct_error(oauth_store.error, E_ERROR, 1, NULL);
}
else {
if (url_link)
FCGX_FPrintF(req->out, "Set-Cookie: instance_url=%s; Path=/; Max-Age=31536000\r\n", url_link);
else
{
PRINTF("Set-Cookie: instance_url=%s; Path=/; Max-Age=31536000\r\n", url_link);
} else
// Clear
FCGX_FPrintF(req->out, "Set-Cookie: instance_url=; Path=/; Max-Age=-1\r\n");
PUT("Set-Cookie: instance_url=; Path=/; Max-Age=-1\r\n");
apply_access_token(req, token.access_token);
free(url_link);
tb_free(url_link);
return;
}
}
@ -202,27 +201,25 @@ void content_login(PATH_ARGS)
{
// Restore and cleanup, an error occured
m_args.url = orig_url;
free(url_link);
tb_free(url_link);
}
}
// Concat
struct login_template tdata = {
.login_header = L10N[L10N_EN_US][L10N_LOGIN],
.error = error,
.prefix = config_url_prefix,
.username = L10N[L10N_EN_US][L10N_USERNAME],
.password = L10N[L10N_EN_US][L10N_PASSWORD],
.login_submit = L10N[L10N_EN_US][L10N_LOGIN_BTN],
.instance_text = "Or",
.instance_url = "Instance url",
.instance_submit = "Authorize"
};
page = tmpl_gen_login(&tdata, NULL);
PERL_STACK_INIT;
HV* session_hv = perlify_session(ssn);
XPUSHs(newRV_noinc((SV*)session_hv));
XPUSHs(newRV_noinc((SV*)template_files));
if (storage.error || oauth_store.error)
mXPUSHs(newSVpv(storage.error ? storage.error : oauth_store.error, 0));
PERL_STACK_SCALAR_CALL("login::content_login");
page = PERL_GET_STACK_EXIT;
struct base_page b = {
.category = BASE_CAT_NONE,
.content = page,
.session = session_hv,
.sidebar_left = NULL
};
@ -230,8 +227,7 @@ void content_login(PATH_ARGS)
render_base_page(&b, req, ssn, api);
// Cleanup
mastodont_storage_cleanup(&storage);
mastodont_storage_cleanup(&oauth_store);
if (error) free(error);
if (page) free(page);
mstdnt_storage_cleanup(&storage);
mstdnt_storage_cleanup(&oauth_store);
tb_free(page);
}

View file

@ -16,10 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "global_perl.h"
#include <pthread.h>
#include <fcgi_stdio.h>
#include <fcgiapp.h>
#include <string.h>
#include "memory.h"
#include <mastodont.h>
#include <stdlib.h>
#include "../config.h"
@ -31,22 +31,37 @@
#include "login.h"
#include "local_config.h"
#include "cookie.h"
#include "memory_page.h"
#include "query.h"
#include "status.h"
#include "lists.h"
#include "timeline.h"
#include "session.h"
#include "notifications.h"
#include "test.h"
#include "env.h"
#include "search.h"
#include "about.h"
#include "local_config_set.h"
#include "global_cache.h"
#include "conversations.h"
#include "request.h"
#include "cgi.h"
#define THREAD_COUNT 20
// Allow dynamic loading for Perl
static void xs_init (pTHX);
void boot_DynaLoader (pTHX_ CV* cv);
#ifdef DEBUG
static int quit = 0;
static void exit_treebird(PATH_ARGS)
{
quit = 1;
exit(1);
}
#endif
/*******************
* Path handling *
******************/
@ -57,7 +72,6 @@ static struct path_info paths[] = {
{ "/config", content_config },
{ "/login/oauth", content_login_oauth },
{ "/login", content_login },
{ "/test", content_test },
{ "/user/:/action/:", content_account_action },
{ "/user/:", content_account_statuses },
{ "/@:/scrobbles", content_account_scrobbles },
@ -95,24 +109,34 @@ static struct path_info paths[] = {
{ "/blocked", content_account_blocked },
{ "/muted", content_account_muted },
{ "/notifications_compact", content_notifications_compact },
{ "/notification/:/read", content_notifications_read },
{ "/notification/:/delete", content_notifications_clear },
{ "/notifications/read", content_notifications_read },
{ "/notifications/clear", content_notifications_clear },
{ "/notifications", content_notifications },
{ "/tag/:", content_tl_tag },
{ "/chats/:", content_chat_view },
{ "/chats", content_chats },
{ "/chats_embed/:", content_chat_embed },
#ifdef DEBUG
{ "/quit", exit_treebird },
{ "/exit", exit_treebird },
#endif
// Debug, but cool to see
{ "/memory_stats", content_memory_stats },
// API
{ "/treebird_api/v1/notifications", api_notifications },
{ "/treebird_api/v1/interact", api_status_interact },
{ "/treebird_api/v1/attachment", api_attachment_create },
};
static void application(mastodont_t* api, FCGX_Request* req)
static void application(mastodont_t* api, REQUEST_T req)
{
propagate_memory();
// Default config
struct session ssn = {
.config = {
.theme = "treebird20",
.themeclr = 0,
.lang = L10N_EN_US,
.jsactions = 1,
.jsreply = 1,
@ -156,17 +180,18 @@ static void application(mastodont_t* api, FCGX_Request* req)
handle_paths(req, &ssn, api, paths, sizeof(paths)/sizeof(paths[0]));
// Cleanup
if (cookies_str) free(cookies_str);
if (post_str) free(post_str);
if (get_str) free(get_str);
if (cookies_str) tb_free(cookies_str);
if (post_str) tb_free(post_str);
if (get_str) tb_free(get_str);
free_files(&(keyfile(ssn.post.files)));
if (ssn.logged_in) mstdnt_cleanup_account(&(ssn.acct));
mastodont_storage_cleanup(&(ssn.acct_storage));
mstdnt_storage_cleanup(&(ssn.acct_storage));
if (attachments)
cleanup_media_storages(&ssn, attachments);
}
static void* cgi_start(void* arg)
#ifndef SINGLE_THREADED
static void* threaded_fcgi_start(void* arg)
{
mastodont_t* api = arg;
int rc;
@ -189,30 +214,89 @@ static void* cgi_start(void* arg)
return NULL;
}
#else
void cgi_start(mastodont_t* api)
{
while (FCGI_Accept() >= 0 && quit == 0)
{
application(api, NULL);
}
}
#endif
int main(void)
void xs_init(pTHX)
{
static const char file[] = __FILE__;
dXSUB_SYS;
PERL_UNUSED_CONTEXT;
newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
}
int main(int argc, char **argv, char **env)
{
// Global init
mastodont_global_curl_init();
mstdnt_global_curl_init();
#ifndef SINGLE_THREADED
FCGX_Init();
#endif
// Initiate mastodont library
// Initialize Perl
PERL_SYS_INIT3(&argc, &argv, &env);
my_perl = perl_alloc();
perl_construct(my_perl);
//char* perl_argv[] = { "", "-e", data_main_pl, NULL };
char* perl_argv[] = { "", "-I", "perl/", "perl/main.pl", NULL };
perl_parse(my_perl, xs_init, (sizeof(perl_argv) / sizeof(perl_argv[0])) - 1, perl_argv, (char**)NULL);
PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
PL_perl_destruct_level = 1;
perl_run(my_perl);
init_template_files(aTHX);
// Setup mstdnt hooks to use Perl functions
struct mstdnt_hooks hooks = {
.malloc = tb_malloc,
// Not sure how this differs from tb_free? That's undefined... (but used elsewhere in the code just fine)
.free = tb_free,
.calloc = tb_calloc,
.realloc = tb_realloc,
};
mstdnt_set_hooks(&hooks);
// Initiate mstdnt library
mastodont_t api;
mastodont_init(&api);
mstdnt_init(&api);
// Fetch information about the current instance
load_instance_info_cache(&api);
#ifndef SINGLE_THREADED
// Start thread pool
pthread_t id[THREAD_COUNT];
for (unsigned i = 0; i < THREAD_COUNT; ++i)
pthread_create(&id[i], NULL, cgi_start, &api);
pthread_create(&id[i], NULL, threaded_fcgi_start, &api);
// Hell, let's not sit around here either
threaded_fcgi_start(&api);
FCGX_ShutdownPending();
for (unsigned i = 0; i < THREAD_COUNT; ++i)
pthread_join(id[i], NULL);
#else
cgi_start(&api);
#endif
free_instance_info_cache();
mastodont_cleanup(&api);
mastodont_global_curl_cleanup();
mstdnt_global_curl_cleanup();
mstdnt_cleanup(&api);
cleanup_template_files();
perl_destruct(my_perl);
perl_free(my_perl);
PERL_SYS_TERM();
return 0;
}

68
src/memory.c Normal file
View file

@ -0,0 +1,68 @@
/*
* 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 <malloc.h>
#include "memory.h"
#ifdef __GLIBC__
#include <malloc.h>
#endif
int tb_memory_allocated_est;
int tb_memory_allocated;
int tb_memory_free;
int tb_memory_times_allocated;
int tb_memory_times_freed;
#ifdef __GLIBC__
void propagate_memory()
{
struct mallinfo2 inf = mallinfo2();
tb_memory_free = inf.fordblks;
tb_memory_allocated = inf.uordblks;
}
#endif // __GLIBC__
void* tb_malloc(size_t size)
{
tb_memory_allocated_est += size;
++tb_memory_times_allocated;
return malloc(size);
}
// Nothing....
void* tb_calloc(size_t nmemb, size_t size)
{
tb_memory_allocated_est = size * nmemb;
return calloc(nmemb, size);
}
void* tb_realloc(void* ptr, size_t size)
{
tb_memory_allocated_est = size;
return realloc(ptr, size);
}
void tb_free(void* ptr)
{
++tb_memory_times_freed;
free(ptr);
}

51
src/memory.h Normal file
View file

@ -0,0 +1,51 @@
/*
* 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/>.
*/
/* Everything seen here is info for the super curious, and does not really
have anything to do with Treebird :-) */
#ifndef TREEBIRD_MEMSTUF_H
#define TREEBIRD_MEMSTUF_H
#include <stdlib.h>
extern int tb_memory_allocated_est;
extern int tb_memory_allocated;
extern int tb_memory_free;
extern int tb_memory_times_allocated;
extern int tb_memory_times_freed;
#if 0
#define tb_malloc malloc
#define tb_calloc calloc
#define tb_realloc realloc
#define tb_free free
#endif // Nothing
void* tb_malloc(size_t size);
void* tb_calloc(size_t nmemb, size_t size);
void* tb_realloc(void* ptr, size_t size);
void tb_free(void* ptr);
#ifdef __GLIBC__
void propagate_memory();
#else
#define propagate_memory() ;
#endif
#endif // TREEBIRD_MEMSTUF_H

View file

@ -16,28 +16,33 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <string.h>
#include "navigation.h"
#include "easprintf.h"
#include "memory_page.h"
#include "base_page.h"
#include "memory.h"
// Pages
#include "../static/navigation.ctmpl"
#define SUBMIT_HTML "<input type=\"submit\" class=\"hidden\">"
char* construct_navigation_box(char* start_id,
char* prev_id,
char* next_id,
size_t* size)
void content_memory_stats(PATH_ARGS)
{
int is_start = start_id && prev_id ? strcmp(start_id, prev_id) == 0 : 0;
PERL_STACK_INIT;
HV* session_hv = perlify_session(ssn);
mXPUSHs(newRV_inc((SV*)session_hv));
mXPUSHs(newRV_inc((SV*)template_files));
// Args
mXPUSHi(tb_memory_allocated_est);
mXPUSHi(tb_memory_allocated);
mXPUSHi(tb_memory_free);
mXPUSHi(tb_memory_times_allocated);
mXPUSHi(tb_memory_times_freed);
struct navigation_template tdata = {
.start_id = start_id,
.min_id = prev_id,
.prev_active = is_start ? "btn-disabled" : NULL,
.prev_submit = is_start ? "" : SUBMIT_HTML,
.max_id = next_id
PERL_STACK_SCALAR_CALL("meta::memory");
char* dup = PERL_GET_STACK_EXIT;
struct base_page b = {
.category = BASE_CAT_NONE,
.content = dup,
.session = session_hv,
.sidebar_left = NULL
};
return tmpl_gen_navigation(&tdata, size);
render_base_page(&b, req, ssn, api);
tb_free(dup);
}

View file

@ -16,13 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef TEST_H
#define TEST_H
#include <stddef.h>
#include <mastodont.h>
#ifndef MEMORY_PAGE_H
#define MEMORY_PAGE_H
#include "memory.h"
#include "session.h"
#include "path.h"
void content_test(PATH_ARGS);
void content_memory_stats(PATH_ARGS);
#endif /* TEST_H */
#endif // MEMORY_PAGE_H

View file

@ -17,6 +17,7 @@
*/
#include <ctype.h>
#include "global_perl.h"
#include <fcgi_stdio.h>
#include <string.h>
#include <stdlib.h>
@ -33,7 +34,7 @@ char* get_mime_boundary(char* content_type_str, char** bound)
char* content = content_type_str;
// Data gets changed in place
char* content_type = malloc(strlen(content)+1);
char* content_type = tb_malloc(strlen(content)+1);
if (!content_type)
{
perror("malloc");
@ -66,7 +67,7 @@ char* get_mime_boundary(char* content_type_str, char** bound)
return content_type;
error:
free(content_type);
tb_free(content_type);
return NULL;
}

View file

@ -25,253 +25,76 @@
#include "base_page.h"
#include "string_helpers.h"
#include "easprintf.h"
#include "navigation.h"
#include "http.h"
#include "status.h"
#include "error.h"
#include "emoji.h"
#include "account.h"
#include "../config.h"
// Pages
#include "../static/notifications_page.ctmpl"
#include "../static/notifications.ctmpl"
#include "../static/notification_action.ctmpl"
#include "../static/notification.ctmpl"
#include "../static/notification_compact.ctmpl"
#include "../static/like_svg.ctmpl"
#include "../static/repeat_svg.ctmpl"
#include "../static/notifications_embed.ctmpl"
struct notification_args
{
struct session* ssn;
mastodont_t* api;
struct mstdnt_notification* notifs;
};
char* construct_notification(struct session* ssn,
mastodont_t* api,
struct mstdnt_notification* notif,
size_t* size)
{
char* notif_html;
if (notif->status)
{
// Construct status with notification_info
notif_html = construct_status(ssn, api, notif->status, size, notif, NULL, 0);
}
else {
notif_html = construct_notification_action(notif, size);
}
return notif_html;
}
char* construct_notification_action(struct mstdnt_notification* notif, size_t* size)
{
char* res;
char* serialized_display_name = sanitize_html(notif->account->display_name);
char* display_name = emojify(serialized_display_name,
notif->account->emojis,
notif->account->emojis_len);
struct notification_action_template tdata = {
.avatar = notif->account->avatar,
.acct = notif->account->acct,
.display_name = display_name,
.prefix = config_url_prefix,
.action = notification_type_compact_str(notif->type),
.notif_svg = notification_type_svg(notif->type)
};
res = tmpl_gen_notification_action(&tdata, size);
/* // Cleanup */
if (display_name != notif->account->display_name &&
display_name != serialized_display_name)
free(display_name);
if (serialized_display_name != notif->account->display_name)
free(serialized_display_name);
return res;
}
char* construct_notification_compact(struct session* ssn,
mastodont_t* api,
struct mstdnt_notification* notif,
size_t* size)
{
char* notif_html;
char* status_format = NULL;
char* notif_stats = NULL;
const char* type_str = notification_type_compact_str(notif->type);
const char* type_svg = notification_type_svg(notif->type);
if (notif->status)
{
if (notif->type == MSTDNT_NOTIFICATION_MENTION)
notif_stats = construct_interaction_buttons(ssn, notif->status, NULL,
STATUS_NO_LIKEBOOST | STATUS_NO_DOPAMEME);
status_format = reformat_status(ssn,
notif->status->content,
notif->status->emojis,
notif->status->emojis_len);
}
char* serialized_display_name = sanitize_html(notif->account->display_name);
char* display_name = emojify(serialized_display_name,
notif->account->emojis,
notif->account->emojis_len);
struct notification_compact_template tdata = {
.avatar = notif->account->avatar,
.has_icon = strlen(type_svg) == 0 ? "" : "-with-icon",
.acct = notif->account->acct,
.display_name = display_name,
.action = type_str,
.notif_svg = type_svg,
.is_status = (notif->type == MSTDNT_NOTIFICATION_STATUS ||
notif->type == MSTDNT_NOTIFICATION_MENTION ? "is-mention" : NULL),
/* Might show follower address */
.content = (notif->type == MSTDNT_NOTIFICATION_FOLLOW ?
notif->account->acct : status_format),
.stats = notif_stats
};
notif_html = tmpl_gen_notification_compact(&tdata, size);
if (status_format &&
status_format != notif->status->content) free(status_format);
if (notif_stats) free(notif_stats);
if (serialized_display_name != notif->account->display_name)
free(serialized_display_name);
if (display_name != notif->account->display_name &&
display_name != serialized_display_name)
free(display_name);
return notif_html;
}
static char* construct_notification_voidwrap(void* passed, size_t index, size_t* res)
{
struct notification_args* args = passed;
return construct_notification(args->ssn, args->api, args->notifs + index, res);
}
static char* construct_notification_compact_voidwrap(void* passed, size_t index, size_t* res)
{
struct notification_args* args = passed;
return construct_notification_compact(args->ssn, args->api, args->notifs + index, res);
}
char* construct_notifications(struct session* ssn,
mastodont_t* api,
struct mstdnt_notification* notifs,
size_t size,
size_t* ret_size)
{
struct notification_args args = {
.ssn = ssn,
.api = api,
.notifs = notifs
};
return construct_func_strings(construct_notification_voidwrap, &args, size, ret_size);
}
char* construct_notifications_compact(struct session* ssn,
mastodont_t* api,
struct mstdnt_notification* notifs,
size_t size,
size_t* ret_size)
{
struct notification_args args = {
.ssn = ssn,
.api = api,
.notifs = notifs
};
return construct_func_strings(construct_notification_compact_voidwrap,
&args,
size,
ret_size);
}
void content_notifications(PATH_ARGS)
{
struct mstdnt_args m_args;
set_mstdnt_args(&m_args, ssn);
char* page, *notif_html = NULL;
struct mstdnt_storage storage = { 0 };
struct mstdnt_notification* notifs = NULL;
size_t notifs_len = 0;
char* start_id;
char* navigation_box = NULL;
if (keystr(ssn->cookies.logged_in))
{
struct mstdnt_get_notifications_args args = {
.exclude_types = 0,
.account_id = NULL,
.exclude_visibilities = 0,
.include_types = 0,
.with_muted = 1,
.max_id = keystr(ssn->post.max_id),
.min_id = keystr(ssn->post.min_id),
.since_id = NULL,
.offset = 0,
.limit = 20,
};
if (mastodont_get_notifications(api, &m_args, &args, &storage, &notifs, &notifs_len) == 0)
{
if (notifs && notifs_len)
{
notif_html = construct_notifications(ssn, api, notifs, notifs_len, NULL);
start_id = keystr(ssn->post.start_id) ? keystr(ssn->post.start_id) : notifs[0].id;
navigation_box = construct_navigation_box(start_id,
notifs[0].id,
notifs[notifs_len-1].id,
NULL);
mstdnt_cleanup_notifications(notifs, notifs_len);
}
else
notif_html = construct_error("No notifications", E_NOTICE, 1, NULL);
}
else
notif_html = construct_error(storage.error, E_ERROR, 1, NULL);
}
struct notifications_page_template tdata = {
.notifications = notif_html,
.navigation = navigation_box
struct mstdnt_notifications_args args = {
.exclude_types = 0,
.account_id = NULL,
.exclude_visibilities = 0,
.include_types = 0,
.with_muted = 1,
.max_id = keystr(ssn->post.max_id),
.min_id = keystr(ssn->post.min_id),
.since_id = NULL,
.offset = 0,
.limit = 20,
};
page = tmpl_gen_notifications_page(&tdata, NULL);
if (keystr(ssn->cookies.logged_in))
mstdnt_get_notifications(api, &m_args, &args, &storage, &notifs, &notifs_len);
PERL_STACK_INIT;
HV* session_hv = perlify_session(ssn);
mXPUSHs(newRV_inc((SV*)session_hv));
mXPUSHs(newRV_inc((SV*)template_files));
if (notifs)
mXPUSHs(newRV_noinc((SV*)perlify_notifications(notifs, notifs_len)));
// ARGS
PERL_STACK_SCALAR_CALL("notifications::content_notifications");
// Duplicate so we can free the TMPs
char* dup = PERL_GET_STACK_EXIT;
struct base_page b = {
.category = BASE_CAT_NOTIFICATIONS,
.content = page,
.content = dup,
.session = session_hv,
.sidebar_left = NULL
};
// Output
render_base_page(&b, req, ssn, api);
mastodont_storage_cleanup(&storage);
if (notif_html) free(notif_html);
if (navigation_box) free(navigation_box);
if (page) free(page);
mstdnt_storage_cleanup(&storage);
mstdnt_cleanup_notifications(notifs, notifs_len);
tb_free(dup);
}
void content_notifications_compact(PATH_ARGS)
{
char* theme_str = NULL;
struct mstdnt_args m_args;
set_mstdnt_args(&m_args, ssn);
char* page, *notif_html = NULL;
char* page;
struct mstdnt_storage storage = { 0 };
struct mstdnt_notification* notifs = NULL;
size_t notifs_len = 0;
char* start_id = NULL;
char* navigation_box = NULL;
if (keystr(ssn->cookies.logged_in))
{
struct mstdnt_get_notifications_args args = {
struct mstdnt_notifications_args args = {
.exclude_types = 0,
.account_id = NULL,
.exclude_visibilities = 0,
@ -284,58 +107,99 @@ void content_notifications_compact(PATH_ARGS)
.limit = 20,
};
if (mastodont_get_notifications(api,
&m_args,
&args,
&storage,
&notifs,
&notifs_len) == 0)
{
if (notifs && notifs_len)
{
notif_html = construct_notifications_compact(ssn, api, notifs, notifs_len, NULL);
start_id = keystr(ssn->post.start_id) ? keystr(ssn->post.start_id) : notifs[0].id;
navigation_box = construct_navigation_box(start_id,
notifs[0].id,
notifs[notifs_len-1].id,
NULL);
mstdnt_cleanup_notifications(notifs, notifs_len);
}
else
notif_html = construct_error("No notifications", E_NOTICE, 1, NULL);
}
else
notif_html = construct_error(storage.error, E_ERROR, 1, NULL);
mstdnt_get_notifications(api, &m_args, &args, &storage, &notifs, &notifs_len);
}
// Set theme
if (ssn->config.theme && !(strcmp(ssn->config.theme, "treebird") == 0 &&
ssn->config.themeclr == 0))
{
easprintf(&theme_str, "<link rel=\"stylesheet\" type=\"text/css\" href=\"/%s%s.css\">",
ssn->config.theme,
ssn->config.themeclr ? "-dark" : "");
}
size_t len;
struct notifications_embed_template tdata = {
.theme_str = theme_str,
.notifications = notif_html,
.navigation_box = navigation_box
};
PERL_STACK_INIT;
HV* session_hv = perlify_session(ssn);
mXPUSHs(newRV_noinc((SV*)session_hv));
mXPUSHs(newRV_inc((SV*)template_files));
if (notifs)
mXPUSHs(newRV_noinc((SV*)perlify_notifications(notifs, notifs_len)));
page = tmpl_gen_notifications_embed(&tdata, &len);
PERL_STACK_SCALAR_CALL("notifications::embed_notifications");
send_result(req, NULL, NULL, page, len);
page = PERL_GET_STACK_EXIT;
mastodont_storage_cleanup(&storage);
free(notif_html);
free(navigation_box);
free(page);
free(theme_str);
send_result(req, NULL, NULL, page, 0);
mstdnt_storage_cleanup(&storage);
mstdnt_cleanup_notifications(notifs, notifs_len);
tb_free(page);
}
void content_notifications_clear(PATH_ARGS)
{
char* referer = GET_ENV("HTTP_REFERER", req);
struct mstdnt_args m_args;
set_mstdnt_args(&m_args, ssn);
struct mstdnt_storage storage = { 0 };
if (data)
{
mstdnt_notification_dismiss(api, &m_args, &storage, data[0]);
}
else {
mstdnt_notifications_clear(api, &m_args, &storage);
}
mstdnt_storage_cleanup(&storage);
redirect(req, REDIRECT_303, referer);
}
void content_notifications_read(PATH_ARGS)
{
char* referer = GET_ENV("HTTP_REFERER", req);
struct mstdnt_args m_args;
set_mstdnt_args(&m_args, ssn);
struct mstdnt_storage storage = { 0 };
if (data)
{
struct mstdnt_notifications_args args = { .id = data[0] };
mstdnt_notifications_read(api, &m_args, &args, &storage, NULL);
}
else {
struct mstdnt_notifications_args args = { .max_id = keystr(ssn->post.max_id) };
mstdnt_notifications_read(api, &m_args, &args, &storage, NULL);
}
mstdnt_storage_cleanup(&storage);
redirect(req, REDIRECT_303, referer);
}
// Converts it into a perl struct
static HV* perlify_notification_pleroma(struct mstdnt_notification_pleroma* notif)
{
if (!notif) return NULL;
HV* notif_pl_hv = newHV();
hvstores_int(notif_pl_hv, "is_muted", notif->is_muted);
hvstores_int(notif_pl_hv, "is_seen", notif->is_seen);
return notif_pl_hv;
}
// Converts it into a perl struct
HV* perlify_notification(const struct mstdnt_notification* const notif)
{
if (!notif) return NULL;
HV* notif_hv = newHV();
hvstores_str(notif_hv, "id", notif->id);
hvstores_int(notif_hv, "created_at", notif->created_at);
hvstores_str(notif_hv, "emoji", notif->emoji);
hvstores_str(notif_hv, "type", mstdnt_notification_t_to_str(notif->type));
hvstores_ref(notif_hv, "account", perlify_account(notif->account));
hvstores_ref(notif_hv, "pleroma", perlify_notification_pleroma(notif->pleroma));
hvstores_ref(notif_hv, "status", perlify_status(notif->status));
return notif_hv;
}
PERLIFY_MULTI(notification, notifications, mstdnt_notification)
void api_notifications(PATH_ARGS)
{
send_result(req, NULL, "application/json", "{\"status\":0}", 0);

View file

@ -19,36 +19,20 @@
#ifndef NOTIFICATIONS_H
#define NOTIFICATIONS_H
#include <mastodont.h>
#include <fcgi_stdio.h>
#include <fcgiapp.h>
#include "session.h"
#include "path.h"
#include "type_string.h"
char* construct_notification(struct session* ssn,
mastodont_t* api,
struct mstdnt_notification* notif,
size_t* size);
char* construct_notification_action(struct mstdnt_notification* notif, size_t* size);
char* construct_notification_compact(struct session* ssn,
mastodont_t* api,
struct mstdnt_notification* notif,
size_t* size);
char* construct_notifications(struct session* ssn,
mastodont_t* api,
struct mstdnt_notification* notifs,
size_t size,
size_t* ret_size);
char* construct_notifications_compact(struct session* ssn,
mastodont_t* api,
struct mstdnt_notification* notifs,
size_t size,
size_t* ret_size);
#include "global_perl.h"
#include "cgi.h"
// Page contents
void content_notifications(PATH_ARGS);
void content_notifications_compact(PATH_ARGS);
void content_notifications_clear(PATH_ARGS);
void content_notifications_read(PATH_ARGS);
void api_notifications(PATH_ARGS);
HV* perlify_notification(const struct mstdnt_notification* const notif);
AV* perlify_notifications(const struct mstdnt_notification* const notif, size_t len);
#endif // NOTIFICATION_H

View file

@ -16,24 +16,20 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <fcgi_stdio.h>
#include "page_config.h"
#include "global_perl.h"
#include <stdlib.h>
#include <string.h>
#include "http.h"
#include "base_page.h"
#include "../config.h"
#include "easprintf.h"
#include "page_config.h"
#include "query.h"
#include "cookie.h"
#include "local_config_set.h"
#include "string_helpers.h"
#include "l10n.h"
// Pages
#include "../static/config_general.ctmpl"
#include "../static/config_appearance.ctmpl"
#include "../static/config_sidebar.ctmpl"
#include <fcgi_stdio.h>
#define bool_checked(key) (ssn->config.key ? "checked" : "")
@ -44,69 +40,51 @@ enum config_category
CONFIG_CAT_ACCOUNT
};
static char* construct_config_sidebar(enum config_category cat, size_t* size)
{
struct config_sidebar_template tdata = {
.prefix = config_url_prefix,
.general_active = CAT_TEXT(cat, CONFIG_CAT_GENERAL),
.general = L10N[L10N_EN_US][L10N_GENERAL],
.appearance_active = CAT_TEXT(cat, CONFIG_CAT_APPEARANCE),
.appearance = L10N[L10N_EN_US][L10N_APPEARANCE],
.account_active = CAT_TEXT(cat, CONFIG_CAT_ACCOUNT),
.account = L10N[L10N_EN_US][L10N_ACCOUNT],
};
return tmpl_gen_config_sidebar(&tdata, size);
}
void content_config_general(PATH_ARGS)
{
char* sidebar_html = construct_config_sidebar(CONFIG_CAT_GENERAL, NULL);
struct config_general_template tdata = {
.js_on = bool_checked(js),
.jsactions_on = bool_checked(jsactions),
.jsreply_on = bool_checked(jsreply),
.jslive_on = bool_checked(jslive),
.status_attachments_on = bool_checked(stat_attachments),
.status_greentexts_on = bool_checked(stat_greentexts),
.status_dopameme_on = bool_checked(stat_dope),
.status_oneclicksoftware_on = bool_checked(stat_oneclicksoftware),
.status_emojo_likes_on = bool_checked(stat_emojo_likes),
.status_hide_muted_on = bool_checked(stat_hide_muted),
.instance_show_shoutbox_on = bool_checked(instance_show_shoutbox),
.instance_panel_on = bool_checked(instance_panel),
.notifications_embed_on = bool_checked(notif_embed)
};
char* general_page = tmpl_gen_config_general(&tdata, NULL);
PERL_STACK_INIT;
HV* session_hv = perlify_session(ssn);
XPUSHs(newRV_noinc((SV*)session_hv));
mXPUSHs(newRV_inc((SV*)template_files));
PERL_STACK_SCALAR_CALL("config::general");
char* dup = PERL_GET_STACK_EXIT;
struct base_page b = {
.category = BASE_CAT_CONFIG,
.content = general_page,
.sidebar_left = sidebar_html
.content = dup,
.session = session_hv,
.sidebar_left = NULL
};
render_base_page(&b, req, ssn, api);
// Cleanup
free(sidebar_html);
free(general_page);
tb_free(dup);
}
void content_config_appearance(PATH_ARGS)
{
char* sidebar_html = construct_config_sidebar(CONFIG_CAT_APPEARANCE, NULL);
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("config::appearance");
char* dup = PERL_GET_STACK_EXIT;
struct base_page b = {
.category = BASE_CAT_CONFIG,
.content = data_config_appearance,
.sidebar_left = sidebar_html
.content = dup,
.session = session_hv,
.sidebar_left = NULL
};
render_base_page(&b, req, ssn, api);
// Cleanup
free(sidebar_html);
tb_free(dup);
}
void content_config(PATH_ARGS)

View file

@ -20,14 +20,13 @@
#define PAGE_CONFIG_H
#include <stddef.h>
#include <mastodont.h>
#include <fcgi_stdio.h>
#include <fcgiapp.h>
#include "path.h"
#include "session.h"
#include "cgi.h"
void content_config_appearance(PATH_ARGS);
void content_config_general(PATH_ARGS);
void content_config_account(PATH_ARGS);
//void content_config_account(PATH_ARGS);
void content_config(PATH_ARGS);
#endif // PAGE_CONFIG_H

View file

@ -23,7 +23,7 @@
#include "account.h"
#include "error.h"
int parse_path(FCGX_Request* req,
int parse_path(REQUEST_T req,
struct session* ssn,
mastodont_t* api,
struct path_info* path_info)
@ -63,11 +63,11 @@ int parse_path(FCGX_Request* req,
read_len = (size_t)after_str - (size_t)(p2 + i);
// Copy in new data from the string we just read
tmp = malloc(read_len+1);
tmp = tb_malloc(read_len+1);
strncpy(tmp, after_str - read_len, read_len);
tmp[read_len] = '\0';
// Add our new string
data = realloc(data, ++size * sizeof(tmp));
data = tb_realloc(data, ++size * sizeof(tmp));
data[size-1] = tmp;
// Move ahead (-1 because we move again)
i += read_len - 1;
@ -95,13 +95,13 @@ breakpt:
// Cleanup
for (size_t i = 0; i < size; ++i)
{
free(data[i]);
tb_free(data[i]);
}
if (data) free(data);
if (data) tb_free(data);
return res;
}
void handle_paths(FCGX_Request* req,
void handle_paths(REQUEST_T req,
struct session* ssn,
mastodont_t* api,
struct path_info* paths,

View file

@ -18,14 +18,14 @@
#ifndef PATH_H
#define PATH_H
#include <fcgi_stdio.h>
#include <fcgiapp.h>
#include "session.h"
#include <mastodont.h>
#include <stddef.h>
#include "env.h"
#include "session.h"
#include "cgi.h"
#include "request.h"
#define PATH_ARGS FCGX_Request* req, struct session* ssn, mastodont_t* api, char** data
#define PATH_ARGS REQUEST_T req, struct session* ssn, mastodont_t* api, char** data
struct path_info
{
@ -33,13 +33,14 @@ struct path_info
void (*callback)(PATH_ARGS);
};
void handle_paths(FCGX_Request* req,
struct session* ssn,
mastodont_t* api,
struct path_info* paths,
size_t paths_len);
void handle_paths(
REQUEST_T req,
struct session* ssn,
mastodont_t* api,
struct path_info* paths,
size_t paths_len);
int parse_path(FCGX_Request* req,
int parse_path(REQUEST_T req,
struct session* ssn,
mastodont_t* api,
struct path_info* path_info);

View file

@ -16,17 +16,17 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <fcgi_stdio.h>
#include <fcgiapp.h>
#include "query.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "query.h"
#include "env.h"
#include "mime.h"
#include "cgi.h"
char* read_get_data(FCGX_Request* req, struct get_values* query)
char* read_get_data(REQUEST_T req, struct get_values* query)
{
struct http_query_info info = { 0 };
char* query_string = GET_ENV("QUERY_STRING", req);
@ -37,12 +37,13 @@ char* read_get_data(FCGX_Request* req, struct get_values* query)
{ "offset", &(query->offset), key_string },
{ "q", &(query->query), key_string },
{ "code", &(query->code), key_string },
{ "type", &(query->type), key_int },
};
// END Query references
if (query_string)
{
get_query = malloc(strlen(query_string) + 1);
get_query = tb_malloc(strlen(query_string) + 1);
if (!get_query)
{
perror("malloc");
@ -75,7 +76,7 @@ char* read_get_data(FCGX_Request* req, struct get_values* query)
char* read_post_data(FCGX_Request* req, struct post_values* post)
char* read_post_data(REQUEST_T req, struct post_values* post)
{
ptrdiff_t begin_curr_size;
struct http_query_info query_info;
@ -134,7 +135,7 @@ char* read_post_data(FCGX_Request* req, struct post_values* post)
char* mime_boundary;
char* mime_mem = get_mime_boundary(GET_ENV("CONTENT_TYPE", req), &mime_boundary);
int content_length = atoi(GET_ENV("CONTENT_LENGTH", req));
post_query = malloc(content_length + 1);
post_query = tb_malloc(content_length + 1);
if (!post_query)
{
perror("malloc");
@ -142,7 +143,11 @@ char* read_post_data(FCGX_Request* req, struct post_values* post)
}
// fread should be a macro to FCGI_fread, which is set by FCGI_Accept in previous definitions
#ifndef SINGLE_THREADED
size_t len = FCGX_GetStr(post_query, content_length, req->in);
#else
size_t len = fread(post_query, 1, content_length, stdin);
#endif
post_query[content_length] = '\0';
// For shifting through
@ -183,7 +188,7 @@ char* read_post_data(FCGX_Request* req, struct post_values* post)
}
while (p_query_read);
if (mime_mem) free(mime_mem);
if (mime_mem) tb_free(mime_mem);
}
// Free me afterwards!
@ -211,7 +216,7 @@ char* parse_query(char* begin, struct http_query_info* info)
return end ? NULL : begin+1;
}
char* try_handle_post(FCGX_Request* req, void (*call)(struct http_query_info*, void*), void* arg)
char* try_handle_post(REQUEST_T req, void (*call)(struct http_query_info*, void*), void* arg)
{
char* request_method = GET_ENV("REQUEST_METHOD", req);
char* post_query = NULL, * p_query_read;
@ -221,17 +226,19 @@ char* try_handle_post(FCGX_Request* req, void (*call)(struct http_query_info*, v
if (request_method && (strcmp("POST", request_method) == 0))
{
int content_length = atoi(GET_ENV("CONTENT_LENGTH", req));
post_query = malloc(content_length + 1);
post_query = tb_malloc(content_length + 1);
if (!post_query)
{
puts("Malloc error!");
return NULL;
}
#ifdef SINGLE_THREADED
read(STDIN_FILENO, post_query, content_length);
int size = read(STDIN_FILENO, post_query, content_length);
#else
FCGX_GetStr(post_query, content_length, req->in);
int size = FCGX_GetStr(post_query, content_length, req->in);
#endif
if (size != content_length)
return NULL;
post_query[content_length] = '\0';
@ -258,7 +265,65 @@ void free_files(struct file_array* files)
struct file_content* content = files->content;
for (size_t i = 0; i < files->array_size; ++i)
{
free(content[i].content);
tb_free(content[i].content);
}
free(content);
tb_free(content);
}
// TODO use hvstores_XXX macros
HV* perlify_post_values(struct post_values* post)
{
HV* ssn_post_hv = newHV();
// This ugly...
hv_stores(ssn_post_hv, "theme", newSVpv(keystr(post->theme), 0));
hv_stores(ssn_post_hv, "themeclr", newSViv(keyint(post->themeclr)));
hv_stores(ssn_post_hv, "lang", newSViv(keyint(post->lang)));
hv_stores(ssn_post_hv, "title", newSViv(keyint(post->title)));
hv_stores(ssn_post_hv, "jsactions", newSViv(keyint(post->jsactions)));
hv_stores(ssn_post_hv, "jsreply", newSViv(keyint(post->jsreply)));
hv_stores(ssn_post_hv, "jslive", newSViv(keyint(post->jslive)));
hv_stores(ssn_post_hv, "js", newSViv(keyint(post->js)));
hv_stores(ssn_post_hv, "interact_img", newSViv(keyint(post->interact_img)));
hv_stores(ssn_post_hv, "stat_attachments", newSViv(keyint(post->stat_attachments)));
hv_stores(ssn_post_hv, "stat_greentexts", newSViv(keyint(post->stat_greentexts)));
hv_stores(ssn_post_hv, "stat_dope", newSViv(keyint(post->stat_dope)));
hv_stores(ssn_post_hv, "stat_oneclicksoftware", newSViv(keyint(post->stat_oneclicksoftware)));
hv_stores(ssn_post_hv, "stat_emojo_likes", newSViv(keyint(post->stat_emojo_likes)));
hv_stores(ssn_post_hv, "stat_hide_muted", newSViv(keyint(post->stat_hide_muted)));
hv_stores(ssn_post_hv, "instance_show_shoutbox", newSViv(keyint(post->instance_show_shoutbox)));
hv_stores(ssn_post_hv, "instance_panel", newSViv(keyint(post->instance_panel)));
hv_stores(ssn_post_hv, "notif_embed", newSViv(keyint(post->notif_embed)));
hv_stores(ssn_post_hv, "set", newSViv(keyint(post->set)));
hv_stores(ssn_post_hv, "only_media", newSViv(keyint(post->only_media)));
hv_stores(ssn_post_hv, "replies_only", newSViv(keyint(post->replies_only)));
hv_stores(ssn_post_hv, "replies_policy", newSViv(keyint(post->replies_policy)));
hv_stores(ssn_post_hv, "emojoindex", newSViv(keyint(post->emojoindex)));
hv_stores(ssn_post_hv, "sidebar_opacity", newSViv(keyint(post->sidebar_opacity)));
hv_stores(ssn_post_hv, "file_ids", newSVpv(keystr(post->file_ids), 0));
hv_stores(ssn_post_hv, "content", newSVpv(keystr(post->content), 0));
hv_stores(ssn_post_hv, "itype", newSVpv(keystr(post->itype), 0));
hv_stores(ssn_post_hv, "id", newSVpv(keystr(post->id), 0));
hv_stores(ssn_post_hv, "username", newSVpv(keystr(post->username), 0));
hv_stores(ssn_post_hv, "password", newSVpv(keystr(post->password), 0));
hv_stores(ssn_post_hv, "replyid", newSVpv(keystr(post->replyid), 0));
hv_stores(ssn_post_hv, "visibility", newSVpv(keystr(post->visibility), 0));
hv_stores(ssn_post_hv, "instance", newSVpv(keystr(post->instance), 0));
hv_stores(ssn_post_hv, "min_id", newSVpv(keystr(post->min_id), 0));
hv_stores(ssn_post_hv, "max_id", newSVpv(keystr(post->max_id), 0));
hv_stores(ssn_post_hv, "start_id", newSVpv(keystr(post->start_id), 0));
return ssn_post_hv;
}
HV* perlify_get_values(struct get_values* get)
{
HV* ssn_query_hv = newHV();
hv_stores(ssn_query_hv, "offset", newSVpv(keystr(get->offset), 0));
hv_stores(ssn_query_hv, "query", newSVpv(keystr(get->query), 0));
hv_stores(ssn_query_hv, "code", newSVpv(keystr(get->code), 0));
hvstores_int(ssn_query_hv, "type", keyint(get->type));
return ssn_query_hv;
}

View file

@ -18,9 +18,11 @@
#ifndef QUERY_H
#define QUERY_H
#include "global_perl.h"
#include <fcgi_stdio.h>
#include <stddef.h>
#include "key.h"
#include "request.h"
struct http_query_info
{
@ -79,14 +81,19 @@ struct get_values
struct key offset; // String
struct key query; // String
struct key code; // String
struct key type; // Int
};
char* read_get_data(FCGX_Request* req, struct get_values* query);
char* read_post_data(FCGX_Request* req, struct post_values* post);
char* read_get_data(REQUEST_T req, struct get_values* query);
char* read_post_data(REQUEST_T req, struct post_values* post);
/* A stupidly quick query parser */
char* parse_query(char* begin, struct http_query_info* info);
char* try_handle_post(FCGX_Request* req, void (*call)(struct http_query_info*, void*), void* arg);
char* try_handle_post(REQUEST_T req, void (*call)(struct http_query_info*, void*), void* arg);
void free_files(struct file_array* files);
// Perl stuff
HV* perlify_post_values(struct post_values* post);
HV* perlify_get_values(struct get_values* get);
#endif // QUERY_H

View file

@ -1,183 +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/>.
*/
#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>
#include <stdlib.h>
#include <string.h>
#include "reply.h"
#include "easprintf.h"
#include "../config.h"
// Pages
#include "../static/post.ctmpl"
#define ID_REPLY_SIZE 256
#define ID_RESPONSE "<input type=\"hidden\" name=\"replyid\" value=\"%s\">"
char* construct_post_box(struct mstdnt_status* reply_status,
char* default_content,
size_t* size)
{
#define C_S "checked"
#define D_S "disabled"
char* reply_html;
char id_reply[ID_REPLY_SIZE];
enum mstdnt_visibility_type vis = MSTDNT_VISIBILITY_PUBLIC;
// Put hidden post request and check visibility
if (reply_status)
{
snprintf(id_reply, ID_REPLY_SIZE, ID_RESPONSE, reply_status->id);
vis = reply_status->visibility;
}
/*
* Mastodont-c orders the visibility type from smallest (PUBLIC) to
* largest (LOCAL), so we take advantage of the enum values
*/
struct post_template tdata = {
.prefix = config_url_prefix,
.reply_input = reply_status ? id_reply : NULL,
.content = default_content,
.public_checked = vis == MSTDNT_VISIBILITY_PUBLIC ? C_S : NULL,
// You can reply with public to unlisted posts
.public_disabled = vis > MSTDNT_VISIBILITY_UNLISTED ? D_S : NULL,
.unlisted_checked = vis == MSTDNT_VISIBILITY_UNLISTED ? C_S : NULL,
.unlisted_disabled = vis > MSTDNT_VISIBILITY_UNLISTED ? D_S : NULL,
.private_checked = vis == MSTDNT_VISIBILITY_PRIVATE ? C_S : NULL,
.private_disabled = vis > MSTDNT_VISIBILITY_PRIVATE ? D_S : NULL,
.direct_checked = vis == MSTDNT_VISIBILITY_DIRECT ? C_S : NULL,
.local_checked = vis == MSTDNT_VISIBILITY_LOCAL ? C_S : NULL,
};
return tmpl_gen_post(&tdata, size);
}
/* Some comments:
* - Misskey does not return <span>, but we still regex to make sure it's a highlight
* - The order of parameters in a tag can be changed (mastodon does this),
* so we just grep for regex href
* - Misskey/Mastodon adds an @ symbol in the href param, while pleroma adds /users and honk adds /u
*/
#define REGEX_REPLY "<a .*?href=\"https?:\\/\\/(.*?)\\/(?:@|users/|u/)?(.*?)?\".*?>@(?:<span>)?.*?(?:<\\/span>)?"
char* reply_status(struct session* ssn, char* id, struct mstdnt_status* status)
{
char* content = status->content;
size_t content_len = strlen(status->content);
char* stat_reply;
// Regex
pcre2_code* re;
PCRE2_SIZE* re_results;
pcre2_match_data* re_data;
// Regex data
int rc;
int error;
PCRE2_SIZE erroffset;
int url_off, url_len, name_off, name_len;
// Replies
size_t replies_size = 0, replies_size_orig;
char* replies = NULL;
char* instance_domain = malloc(sizeof(config_instance_url)+sizeof("https:///")+1);
// sscanf instead of regex works here and requires less work, we just need to trim off the slash at the end
if (sscanf(config_instance_url, "https://%s/", instance_domain) == 0)
if (sscanf(config_instance_url, "http://%s/", instance_domain) == 0)
{
free(instance_domain);
return NULL;
}
instance_domain[strlen(instance_domain)] = '\0';
// Remove ports, if any. Needed for development or if
// the server actually supports these
char* port_val = strchr(instance_domain, ':');
if (port_val) *port_val = '\0';
// Load first reply
if (ssn->logged_in && strcmp(status->account.acct, ssn->acct.acct) != 0)
{
replies = malloc(replies_size = strlen(status->account.acct)+2);
replies[0] = '@';
strcpy(replies+1, status->account.acct);
replies[replies_size-1] = ' ';
}
// Compile regex
re = pcre2_compile((PCRE2_SPTR)REGEX_REPLY, PCRE2_ZERO_TERMINATED, 0, &error, &erroffset, NULL);
if (re == NULL)
{
fprintf(stderr, "Couldn't parse regex at offset %ld: %d\n", erroffset, error);
free(replies);
pcre2_code_free(re);
}
re_data = pcre2_match_data_create_from_pattern(re, NULL);
for (int ind = 0;;)
{
rc = pcre2_match(re, (PCRE2_SPTR)content, content_len, ind, 0, re_data, NULL);
if (rc < 0)
break;
re_results = pcre2_get_ovector_pointer(re_data);
// Store to last result
ind = re_results[5];
// Read out
url_off = re_results[2];
url_len = re_results[3] - url_off;
name_off = re_results[4];
name_len = re_results[5] - name_off;
int instance_cmp = strncmp(instance_domain, content+url_off, url_len);
// Is this the same as us?
// Cut off url_len by one to slice the '/' at the end
if (instance_cmp == 0 &&
strncmp(ssn->acct.acct, content+name_off, name_len) == 0)
continue;
replies_size_orig = replies_size;
replies_size += (instance_cmp!=0?url_len:0)+name_len+3-(instance_cmp==0); // Bool as int :^)
// Realloc string
replies = realloc(replies, replies_size+1);
replies[replies_size_orig] = '@';
memcpy(replies + replies_size_orig + 1, content + name_off, name_len);
if (instance_cmp != 0)
{
replies[replies_size_orig+1+name_len] = '@';
memcpy(replies + replies_size_orig + 1 + name_len + 1, content + url_off, url_len);
}
replies[replies_size-1] = ' ';
}
if (replies)
replies[replies_size-1] = '\0';
pcre2_match_data_free(re_data);
stat_reply = construct_post_box(status, replies, NULL);
pcre2_code_free(re);
free(replies);
free(instance_domain);
return stat_reply;
}

View file

@ -16,18 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include "../test.h"
#include "request.h"
// Imports
#include "mime_multipart.c"
#include "string_test.c"
int main()
{
struct test tests[] = {
{ "Mime multipart parser", mime_multipart_test },
{ "Strings", string_replace_test }
};
return iterate_tests(tests, sizeof(tests)/sizeof(tests[0]));
}
pthread_mutex_t print_mutex = PTHREAD_MUTEX_INITIALIZER;

View file

@ -16,17 +16,24 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef GRAPHS_N_BARS_H
#define GRAPHS_N_BARS_H
#include <stddef.h>
#include <mastodont.h>
#include "session.h"
#ifndef REQUEST_H
#define REQUEST_H
#include <pthread.h>
char* construct_bar_graph_container(char* bars, size_t* size);
char* construct_bar(float value, size_t* size);
char* construct_hashtags_graph(struct mstdnt_tag* tags,
size_t tags_len,
size_t days,
size_t* ret_size);
extern pthread_mutex_t print_mutex;
#endif /* GRAPHS_N_BARS_H */
#ifdef SINGLE_THREADED
#define PRINTF(str, ...) printf(str, __VA_ARGS__)
#define PUT(str) printf(str)
#define REQUEST_T void*
#else
#define PRINTF(str, ...) do { pthread_mutex_lock(&print_mutex); \
FCGX_FPrintF(req->out, str, __VA_ARGS__); \
pthread_mutex_unlock(&print_mutex); } while (0);
#define PUT(str) do { pthread_mutex_lock(&print_mutex); \
FCGX_FPrintF(req->out, str); \
pthread_mutex_unlock(&print_mutex); } while (0);
#define REQUEST_T FCGX_Request*
#endif
#endif /* REQUEST_H */

View file

@ -19,35 +19,23 @@
#include "scrobble.h"
#include "easprintf.h"
#include "string_helpers.h"
#include "account.h"
#include "../static/scrobble.ctmpl"
char* construct_scrobble(struct mstdnt_scrobble* scrobble, size_t* size)
// Converts it into a perl struct
HV* perlify_scrobble(const struct mstdnt_scrobble* const scrobble)
{
struct scrobble_template tdata = {
.scrobble_id = scrobble->id,
.avatar = scrobble->account.avatar,
.username = scrobble->account.display_name,
.activity = "is listening to...",
.title_key = "Title",
.title = scrobble->title,
.artist_key = "Artist",
.artist = scrobble->artist,
.album_key = "Album",
.album = scrobble->album,
.length_key = "Duration",
.length = scrobble->length
};
if (!scrobble) return NULL;
HV* scrobble_hv = newHV();
return tmpl_gen_scrobble(&tdata, size);
hvstores_str(scrobble_hv, "album", scrobble->album);
hvstores_str(scrobble_hv, "artist", scrobble->artist);
hvstores_str(scrobble_hv, "id", scrobble->id);
hvstores_str(scrobble_hv, "title", scrobble->title);
hvstores_int(scrobble_hv, "created_at", scrobble->created_at);
hvstores_int(scrobble_hv, "length", scrobble->created_at);
hvstores_ref(scrobble_hv, "account", perlify_account(&(scrobble->account)));
return scrobble_hv;
}
static char* construct_scrobble_voidwrap(void* passed, size_t index, size_t* res)
{
return construct_scrobble((struct mstdnt_scrobble*)passed + index, res);
}
char* construct_scrobbles(struct mstdnt_scrobble* scrobbles, size_t scrobbles_len, size_t* ret_size)
{
return construct_func_strings(construct_scrobble_voidwrap, scrobbles, scrobbles_len, ret_size);
}
PERLIFY_MULTI(scrobble, scrobbles, mstdnt_scrobble)

View file

@ -19,8 +19,9 @@
#ifndef SCROBBLE_H
#define SCROBBLE_H
#include <mastodont.h>
#include "global_perl.h"
char* construct_scrobble(struct mstdnt_scrobble* scrobble, size_t* size);
char* construct_scrobbles(struct mstdnt_scrobble* scrobbles, size_t scrobbles_len, size_t* ret_size);
HV* perlify_scrobble(const struct mstdnt_scrobble* const scrobble);
AV* perlify_scrobbles(const struct mstdnt_scrobble* const scrobble, size_t len);
#endif /* SCROBBLE_H */

View file

@ -17,8 +17,9 @@
*/
#include <stdlib.h>
#include "helpers.h"
#include "search.h"
#include "http.h"
#include "helpers.h"
#include "easprintf.h"
#include "../config.h"
#include "string_helpers.h"
@ -27,54 +28,11 @@
#include "hashtag.h"
#include "error.h"
#include "account.h"
#include "graphsnbars.h"
// Pages
#include "../static/search.ctmpl"
#include "../static/search_all.ctmpl"
void search_page(FCGX_Request* req,
struct session* ssn,
mastodont_t* api,
enum search_tab tab,
char* content)
{
char* out_data;
struct search_template tdata = {
.prefix = config_url_prefix,
.query = keystr(ssn->query.query),
.accounts_active = MAKE_FOCUSED_IF(tab, SEARCH_ACCOUNTS),
.accounts = "Accounts",
.hashtags_active = MAKE_FOCUSED_IF(tab, SEARCH_HASHTAGS),
.hashtags = "Hashtags",
.statuses_active = MAKE_FOCUSED_IF(tab, SEARCH_STATUSES),
.statuses = "Statuses",
.results = content
};
out_data = tmpl_gen_search(&tdata, NULL);
struct base_page b = {
.category = BASE_CAT_NONE,
.content = out_data,
.sidebar_left = NULL
};
// Output
render_base_page(&b, req, ssn, api);
free(out_data);
}
void content_search_all(PATH_ARGS)
{
struct mstdnt_args m_args;
set_mstdnt_args(&m_args, ssn);
char* out_data = NULL;
char* statuses_html = NULL;
char* accounts_html = NULL;
char* tags_html = NULL,
* tags_graph = NULL,
* tags_bars = NULL,
* tags_page = NULL;
struct mstdnt_storage storage = { 0 };
struct mstdnt_search_args args = {
.account_id = NULL,
@ -90,72 +48,58 @@ void content_search_all(PATH_ARGS)
};
struct mstdnt_search_results results = { 0 };
if (mastodont_search(api,
&m_args,
keystr(ssn->query.query),
&storage,
&args,
&results) == 0)
// Perform redirect to correct direct page
if (keyint(ssn->query.type))
{
// Statuses, make sure to set the highlight word
struct construct_statuses_args statuses_args = {
.highlight_word = keystr(ssn->query.query),
};
statuses_html = construct_statuses(ssn, api, results.statuses, results.statuses_len, &statuses_args, NULL);
if (!statuses_html)
statuses_html = construct_error("No statuses", E_ERROR, 1, NULL);
// Accounts
accounts_html = construct_accounts(api, results.accts, results.accts_len, 0, NULL);
if (!accounts_html)
accounts_html = construct_error("No accounts", E_ERROR, 1, NULL);
// Hashtags
tags_html = construct_hashtags(results.tags, results.tags_len, NULL);
if (!tags_html)
tags_html = construct_error("No hashtags", E_ERROR, 1, NULL);
tags_bars = construct_hashtags_graph(results.tags,
results.tags_len,
14,
NULL);
if (tags_bars)
tags_graph = construct_bar_graph_container(tags_bars, NULL);
free(tags_bars);
char* query = keystr(ssn->query.query);
query = curl_easy_escape(api->curl, query, 0);
char* url;
// Note: This can be zero, which is just "nothing"
switch (keyint(ssn->query.type))
{
case 1:
easprintf(&url, "/search/statuses?q=%s", query);
redirect(req, REDIRECT_303, url);
break;
case 2:
easprintf(&url, "/search/accounts?q=%s", query);
redirect(req, REDIRECT_303, url);
break;
case 3:
easprintf(&url, "/search/hashtags?q=%s", query);
redirect(req, REDIRECT_303, url);
break;
}
tb_free(url);
curl_free(query);
return;
}
easprintf(&tags_page, "%s%s", STR_NULL_EMPTY(tags_graph), tags_html);
mstdnt_search(api, &m_args, keystr(ssn->query.query), &storage, &args, &results);
PERL_STACK_INIT;
HV* session_hv = perlify_session(ssn);
XPUSHs(newRV_noinc((SV*)session_hv));
XPUSHs(newRV_noinc((SV*)template_files));
mXPUSHs(newRV_noinc((SV*)perlify_search_results(&results)));
// Construct search page
struct search_all_template tdata = {
.accounts = "Accounts",
.hashtags = "Hashtags",
.statuses = "Statuses",
.statuses_results = statuses_html,
.hashtags_results = tags_page,
.accounts_results = accounts_html
};
out_data = tmpl_gen_search_all(&tdata, NULL);
PERL_STACK_SCALAR_CALL("search::content_search");
// Duplicate so we can free the TMPs
char* dup = PERL_GET_STACK_EXIT;
struct base_page b = {
.category = BASE_CAT_NONE,
.content = out_data,
.content = dup,
.session = session_hv,
.sidebar_left = NULL
};
// Output
render_base_page(&b, req, ssn, api);
free(out_data);
free(statuses_html);
free(accounts_html);
free(tags_html);
free(tags_graph);
free(tags_page);
mstdnt_cleanup_search_results(&results);
mastodont_storage_cleanup(&storage);
mstdnt_storage_cleanup(&storage);
tb_free(dup);
}
void content_search_statuses(PATH_ARGS)
@ -178,35 +122,37 @@ void content_search_statuses(PATH_ARGS)
};
struct mstdnt_search_results results = { 0 };
if (mastodont_search(api,
&m_args,
keystr(ssn->query.query),
&storage,
&args,
&results) == 0)
{
struct construct_statuses_args statuses_args = {
.highlight_word = keystr(ssn->query.query),
};
statuses_html = construct_statuses(ssn, api, results.statuses, results.statuses_len, &statuses_args, NULL);
if (!statuses_html)
statuses_html = construct_error("No statuses", E_ERROR, 1, NULL);
}
else
statuses_html = construct_error("An error occured.", E_ERROR, 1, NULL);
mstdnt_search(api, &m_args, keystr(ssn->query.query), &storage, &args, &results);
PERL_STACK_INIT;
HV* session_hv = perlify_session(ssn);
XPUSHs(newRV_noinc((SV*)session_hv));
XPUSHs(newRV_noinc((SV*)template_files));
mXPUSHs(newRV_noinc((SV*)perlify_search_results(&results)));
search_page(req, ssn, api, SEARCH_STATUSES, STR_NULL_EMPTY(statuses_html));
if (statuses_html) free(statuses_html);
PERL_STACK_SCALAR_CALL("search::content_search_statuses");
// Duplicate so we can free the TMPs
char* dup = PERL_GET_STACK_EXIT;
struct base_page b = {
.category = BASE_CAT_NONE,
.content = dup,
.session = session_hv,
.sidebar_left = NULL
};
render_base_page(&b, req, ssn, api);
mstdnt_cleanup_search_results(&results);
mastodont_storage_cleanup(&storage);
mstdnt_storage_cleanup(&storage);
tb_free(dup);
}
void content_search_accounts(PATH_ARGS)
{
struct mstdnt_args m_args;
set_mstdnt_args(&m_args, ssn);
char* accounts_html;
struct mstdnt_storage storage = { 0 };
struct mstdnt_search_args args = {
.account_id = NULL,
@ -222,35 +168,37 @@ void content_search_accounts(PATH_ARGS)
};
struct mstdnt_search_results results = { 0 };
if (mastodont_search(api,
&m_args,
keystr(ssn->query.query),
&storage,
&args,
&results) == 0)
{
accounts_html = construct_accounts(api, results.accts, results.accts_len, 0, NULL);
if (!accounts_html)
accounts_html = construct_error("No accounts", E_ERROR, 1, NULL);
}
else
accounts_html = construct_error("An error occured.", E_ERROR, 1, NULL);
mstdnt_search(api, &m_args, keystr(ssn->query.query), &storage, &args, &results);
search_page(req, ssn, api, SEARCH_ACCOUNTS, STR_NULL_EMPTY(accounts_html));
PERL_STACK_INIT;
HV* session_hv = perlify_session(ssn);
XPUSHs(newRV_noinc((SV*)session_hv));
XPUSHs(newRV_noinc((SV*)template_files));
mXPUSHs(newRV_noinc((SV*)perlify_search_results(&results)));
PERL_STACK_SCALAR_CALL("search::content_search_accounts");
// Duplicate so we can free the TMPs
char* dup = PERL_GET_STACK_EXIT;
struct base_page b = {
.category = BASE_CAT_NONE,
.content = dup,
.session = session_hv,
.sidebar_left = NULL
};
render_base_page(&b, req, ssn, api);
if (accounts_html) free(accounts_html);
mstdnt_cleanup_search_results(&results);
mastodont_storage_cleanup(&storage);
mstdnt_storage_cleanup(&storage);
tb_free(dup);
}
void content_search_hashtags(PATH_ARGS)
{
struct mstdnt_args m_args;
set_mstdnt_args(&m_args, ssn);
char* tags_html;
char* tags_graph = NULL;
char* tags_bars = NULL;
char* tags_page;
struct mstdnt_storage storage = { 0 };
struct mstdnt_search_args args = {
.account_id = NULL,
@ -266,36 +214,23 @@ void content_search_hashtags(PATH_ARGS)
};
struct mstdnt_search_results results = { 0 };
if (mastodont_search(api,
&m_args,
keystr(ssn->query.query),
&storage,
&args,
&results) == 0)
{
tags_html = construct_hashtags(results.tags, results.tags_len, NULL);
if (!tags_html)
tags_html = construct_error("No hashtags", E_ERROR, 1, NULL);
tags_bars = construct_hashtags_graph(results.tags,
results.tags_len,
14,
NULL);
if (tags_bars)
tags_graph = construct_bar_graph_container(tags_bars, NULL);
if (tags_bars) free(tags_bars);
}
else
tags_html = construct_error("An error occured.", E_ERROR, 1, NULL);
easprintf(&tags_page, "%s%s", STR_NULL_EMPTY(tags_graph), tags_html);
mstdnt_search(api, &m_args, keystr(ssn->query.query), &storage, &args, &results);
search_page(req, ssn, api, SEARCH_HASHTAGS, tags_page);
// TODO
if (tags_html) free(tags_html);
if (tags_graph) free(tags_graph);
free(tags_page);
mstdnt_cleanup_search_results(&results);
mastodont_storage_cleanup(&storage);
mstdnt_storage_cleanup(&storage);
// tb_free(dup);
}
HV* perlify_search_results(struct mstdnt_search_results* results)
{
if (!results) return NULL;
HV* search_hv = newHV();
hvstores_ref(search_hv, "accounts", perlify_accounts(results->accts, results->accts_len));
hvstores_ref(search_hv, "statuses", perlify_statuses(results->statuses, results->statuses_len));
// TODO tags
return search_hv;
}

View file

@ -21,22 +21,12 @@
#include <mastodont.h>
#include "session.h"
#include "path.h"
#include "global_perl.h"
enum search_tab
{
SEARCH_STATUSES,
SEARCH_ACCOUNTS,
SEARCH_HASHTAGS,
};
void search_page(FCGX_Request* req,
struct session* ssn,
mastodont_t* api,
enum search_tab tab,
char* content);
void content_search_all(PATH_ARGS);
void content_search_statuses(PATH_ARGS);
void content_search_accounts(PATH_ARGS);
void content_search_hashtags(PATH_ARGS);
HV* perlify_search_results(struct mstdnt_search_results* results);
#endif /* SEARCH_H */

View file

@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "account.h"
#include "session.h"
#include "../config.h"
@ -29,3 +30,23 @@ const char* const get_token(struct session* ssn)
{
return keystr(ssn->cookies.access_token);
}
HV* perlify_session(struct session* ssn)
{
HV* ssn_hv = newHV();
hvstores_int(ssn_hv, "logged_in", ssn->logged_in);
HV* ssn_post_values = perlify_post_values(&(ssn->post));
HV* ssn_get_values = perlify_get_values(&(ssn->query));
HV* ssn_cookie_values = perlify_cookies(&(ssn->cookies));
HV* acct_hv = perlify_account(&(ssn->acct));
// Config
HV* ssn_config = perlify_config(&(ssn->config));
hvstores_ref(ssn_hv, "config", ssn_config);
hvstores_ref(ssn_hv, "cookies", ssn_cookie_values);
hvstores_ref(ssn_hv, "query", ssn_get_values);
hvstores_ref(ssn_hv, "post", ssn_post_values);
hvstores_ref(ssn_hv, "account", acct_hv);
return ssn_hv;
}

View file

@ -19,6 +19,7 @@
#ifndef SESSION_H
#define SESSION_H
#include <mastodont.h>
#include "global_perl.h"
#include "query.h"
#include "local_config.h"
#include "cookie.h"
@ -36,5 +37,6 @@ struct session
const char* const get_instance(struct session* ssn);
const char* const get_token(struct session* ssn);
HV* perlify_session(struct session* ssn);
#endif // SESSION_H

File diff suppressed because it is too large Load diff

View file

@ -23,6 +23,7 @@
#include "l10n.h"
#include "path.h"
#include "session.h"
#include "global_perl.h"
// Flags
#define STATUS_NOOP 0
@ -52,64 +53,17 @@ void content_status_create(PATH_ARGS);
void content_status_react(PATH_ARGS);
// HTML Builders
char* construct_status(struct session* ssn,
mastodont_t* api,
struct mstdnt_status* status,
size_t* size,
struct mstdnt_notification* notif,
struct construct_statuses_args* args,
uint8_t flags);
char* construct_statuses(struct session* ssn,
mastodont_t* api,
struct mstdnt_status* statuses,
size_t size,
struct construct_statuses_args* args,
size_t* ret_size);
char* construct_interaction_buttons(struct session* ssn,
struct mstdnt_status* status,
size_t* size,
uint8_t flags);
// Reply to
/** Deprecated: May be used in the future for Mastodon only */
char* get_in_reply_to(mastodont_t* api,
struct session* ssn,
struct mstdnt_status* status,
size_t* size);
char* construct_in_reply_to(struct mstdnt_status* status,
struct mstdnt_account* account,
size_t* size);
char* construct_status_interactions(char* status_id,
int fav_count,
int reblog_count,
struct mstdnt_account* fav_accounts,
size_t fav_accounts_len,
struct mstdnt_account* reblog_accounts,
size_t reblog_accounts_len,
size_t* size);
char* construct_status_interaction_profiles(struct mstdnt_account* reblogs,
struct mstdnt_account* favourites,
size_t reblogs_len,
size_t favourites_len,
size_t* ret_size);
char* construct_status_interaction_profile(struct interact_profile_args* args, size_t index, size_t* size);
char* construct_status_interactions_label(char* status_id,
int is_favourites,
char* header,
int val,
size_t* size);
char* reformat_status(struct session* ssn,
char* content,
struct mstdnt_emoji* emos,
size_t emos_len);
char* greentextify(char* content);
char* make_mentions_local(char* content);
void status_view_reblogs(PATH_ARGS);
void status_view_favourites(PATH_ARGS);
const char* status_visibility_str(enum l10n_locale locale, enum mstdnt_visibility_type visibility);
void content_status_interactions(FCGX_Request* req,
struct session* ssn,
mastodont_t* api,
@ -134,4 +88,9 @@ void notice_redirect(PATH_ARGS);
// API
void api_status_interact(PATH_ARGS);
// Perl
HV* perlify_status_pleroma(const struct mstdnt_status_pleroma* pleroma);
HV* perlify_status(const struct mstdnt_status* status);
AV* perlify_statuses(const struct mstdnt_status* statuses, size_t len);
#endif // STATUS_H

View file

@ -20,6 +20,7 @@
#define _DEFAULT_SOURCE
#include <time.h>
#include <math.h>
#include "global_perl.h"
#include "easprintf.h"
#include <stdlib.h>
#include <stddef.h>
@ -110,7 +111,7 @@ char* strrepl(char* source, char* find, char* repl, int flags)
str_size += curr - last;
// Create and copy
result = realloc(result, str_size + (!is_last ? repl_len : 0) + 1);
result = tb_realloc(result, str_size + (!is_last ? repl_len : 0) + 1);
strncpy(result + last_str_size, last, curr - last);
if (!is_last)
{
@ -134,9 +135,9 @@ char* sanitize_html(char* html)
char* left = strrepl(amp, "<", "&lt;", STRREPL_ALL);
char* right = strrepl(left, ">", "&gt;", STRREPL_ALL);
char* quot = strrepl(right, "\"", "&quot;", STRREPL_ALL);
if (quot != right && right != html && right != left) free(right);
if (left != html && left != amp) free(left);
if (amp != html) free(amp);
if (quot != right && right != html && right != left) tb_free(right);
if (left != html && left != amp) tb_free(left);
if (amp != html) tb_free(amp);
return quot;
}

View file

@ -17,6 +17,7 @@
*/
#include <fcgi_stdio.h>
#include "global_perl.h"
#include <string.h>
#include <stdlib.h>
#include "string_helpers.h"
@ -42,24 +43,24 @@ char* construct_func_strings(char* (*func)(void*, size_t, size_t*),
if (parse_size == -1) /* Malloc error */
{
if (result) free(result);
if (result) tb_free(result);
return NULL;
}
last_parse_size = curr_parse_size;
curr_parse_size += parse_size;
result = realloc(result, curr_parse_size + 1);
result = tb_realloc(result, curr_parse_size + 1);
if (result == NULL)
{
perror("malloc");
free(res_html);
tb_free(res_html);
return NULL;
}
// Copy res_html to result in correct position
strncpy(result + last_parse_size, res_html, parse_size);
// Cleanup
free(res_html);
tb_free(res_html);
}
if (result)

View file

@ -1,295 +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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
// TODO error handling
enum args
{
ARG_FILENAME = 1,
ARG_VARIABLE
};
enum tmpl_type
{
TMPL_INT,
TMPL_UINT,
TMPL_STR,
TMPL_STRLEN,
TMPL_FLOAT,
};
struct tmpl_token
{
enum tmpl_type type;
char* token;
int used; // Internal use only
};
long filesize(FILE* file)
{
long orig = ftell(file);
fseek(file, 0, SEEK_END);
long size = ftell(file);
fseek(file, orig, SEEK_SET);
return size;
}
void chexput(const char* buf, size_t size)
{
for (size_t i = 0; i < size && buf; ++i)
{
printf("0X%hhX,", buf[i]);
}
}
char* strnws(char* str)
{
for (; isblank(*str); ++str);
return str;
}
char* strwsc(char* str, char stop)
{
for (; !isblank(*str) && *str != stop; ++str);
return str;
}
char* tkn_typetostr(enum tmpl_type tkn)
{
switch (tkn)
{
case TMPL_INT:
return "int";
case TMPL_STR:
return "const char*";
case TMPL_STRLEN:
return "char*";
case TMPL_UINT:
return "unsigned";
case TMPL_FLOAT:
return "float";
}
return "";
}
enum tmpl_type tkn_type(char* str)
{
if (strcmp(str, "string") == 0 ||
strcmp(str, "str") == 0 ||
strcmp(str, "%s") == 0)
return TMPL_STR;
else if (strcmp(str, "stringlen") == 0 ||
strcmp(str, "strlen") == 0 ||
strcmp(str, "%.s") == 0)
return TMPL_STRLEN;
else if (strcmp(str, "int") == 0 ||
strcmp(str, "i") == 0 ||
strcmp(str, "%d") == 0)
return TMPL_INT;
else if (strcmp(str, "unsigned") == 0 ||
strcmp(str, "uint") == 0 ||
strcmp(str, "%u") == 0)
return TMPL_UINT;
else if (strcmp(str, "float") == 0 ||
strcmp(str, "%f") == 0)
return TMPL_FLOAT;
// TODO Real error handling
return TMPL_INT;
}
char* parse_tmpl_token(char* buf, struct tmpl_token* tkn)
{
tkn->used = 0;
char* type_begin;
char* type_end;
char* tkn_begin;
char* tkn_end;
// skip {{
buf += 2;
type_begin = strnws(buf);
type_end = strwsc(type_begin, ':');
if (*type_end != ':') buf = strchr(buf, ':');
else buf = type_end;
*type_end = '\0';
tkn->type = tkn_type(type_begin);
++buf;
tkn_begin = strnws(buf);
tkn_end = strwsc(tkn_begin, '}');
if (*tkn_end == '}') buf = tkn_end + 2;
else buf = strstr(buf, "}}") + 2;
*tkn_end = '\0';
tkn->token = tkn_begin;
return buf;
}
void print_template(char* var, char* buf)
{
char* buf_prev = buf;
char* buf_curr = buf;
// Store result
struct tmpl_token* tokens = NULL;
size_t tokens_len = 0;
printf("#ifndef __%s\n"
"#define __%s\n"
"#include <stddef.h>\n"
"static const char data_%s[] = {", var, var, var);
while (1)
{
buf_curr = strstr(buf_curr, "{{");
if (!buf_curr) break;
// Create tokens array
tokens = realloc(tokens, sizeof(struct tmpl_token) * ++tokens_len);
if (!tokens)
{
perror("realloc");
break;
}
// Print up to this point
chexput(buf_prev, buf_curr - buf_prev);
buf_prev = buf_curr = parse_tmpl_token(buf_curr, tokens + (tokens_len-1));
// Print type
switch (tokens[tokens_len-1].type)
{
case TMPL_INT:
// I'm lazy so we'll use this
chexput("%d", 2);
break;
case TMPL_STR:
chexput("%s", 2);
break;
case TMPL_STRLEN:
chexput("%.s", 3);
break;
case TMPL_UINT:
chexput("%u", 2);
break;
case TMPL_FLOAT:
chexput("%f", 2);
break;
}
}
// Print remainder if any
chexput(buf_prev, strlen(buf_prev));
puts("0};");
// Only create struct and function when there are tokens detected
if (tokens_len)
{
printf("struct %s_template {", var);
int should_print = 0;
// Print tokens
for (size_t i = 0; i < tokens_len; ++i)
{
should_print = 1;
// Check if used
for (size_t j = 0; j < tokens_len; ++j)
{
if (i != j &&
strcmp(tokens[i].token, tokens[j].token) == 0 &&
tokens[j].used)
should_print = 0;
}
if (should_print)
{
printf("%s %s;\n", tkn_typetostr(tokens[i].type), tokens[i].token);
if (tokens[i].type == TMPL_STRLEN)
printf("size_t %s_len;\n", tokens[i].token);
tokens[i].used = 1;
}
}
// Generate function
printf("};\n");
printf("char* tmpl_gen_%s(struct %s_template* data, size_t* size);", var, var);
// Pipe the contents of the real function code into stderr, then we can redirect it
// We could also just write the file directly but this works better with the Makefile
// and I am lazy
fprintf(stderr, "#include \"%s.ctmpl\"\n"
"#include \"../src/easprintf.h\"\n"
"char* tmpl_gen_%s(struct %s_template* data, size_t* size){\n"
"char* ret;\n"
"size_t s = easprintf(&ret, data_%s, ", var, var, var, var);
for (size_t i = 0; i < tokens_len; ++i)
{
fprintf(stderr, "data->%s", tokens[i].token);
// No (null) strings, make them empty
if (tokens[i].type == TMPL_STR || tokens[i].type == TMPL_STRLEN)
fprintf(stderr, "?data->%s:\"\"", tokens[i].token);
fputs(i < tokens_len-1 ? ", " : "", stderr);
}
fputs(");\n"
"if (size) *size = s;\n"
"return ret;\n}", stderr);
}
// Done!
puts("\n#endif");
// Cleanup
free(tokens);
}
int main(int argc, char** argv)
{
char* buf;
FILE* file = fopen(argv[ARG_FILENAME], "rb");
long size = filesize(file);
if (!(buf = malloc(size)))
{
perror("malloc");
return 1;
}
if (fread(buf, 1, size, file) != size)
{
fputs("Didn't read correctly!", stderr);
free(buf);
return 1;
}
fclose(file);
buf[size-1] = '\0';
print_template(argv[ARG_VARIABLE], buf);
free(buf);
return 0;
}

View file

@ -1,80 +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 <stdlib.h>
#include <string.h>
#include "test.h"
#include "../config.h"
#include "base_page.h"
#include "easprintf.h"
// Pages
#include "../static/test.ctmpl"
#define ENV_NOT_FOUND "<span style=\"color:red;\">ENV Not Found</span>"
enum env_tbl_index
{
ENV_HTTP_COOKIE = 0,
ENV_PATH_INFO,
ENV_QUERY_STRING,
ENV_REQUEST_METHOD,
ENV_SCRIPT_NAME,
ENV_HTTP_REFERER,
ENV_HTTP_USER_AGENT,
ENV_CONTENT_LENGTH,
};
#define ENV_TBL_GET(index) (env_tbl[(index)] ? env_tbl[(index)] : ENV_NOT_FOUND)
void content_test(PATH_ARGS)
{
char* env_tbl[] = {
GET_ENV("HTTP_COOKIE", req),
GET_ENV("PATH_INFO", req),
GET_ENV("QUERY_STRING", req),
GET_ENV("REQUEST_METHOD", req),
GET_ENV("SCRIPT_NAME", req),
GET_ENV("HTTP_REFERER", req),
GET_ENV("HTTP_USER_AGENT", req),
GET_ENV("CONTENT_LENGTH", req)
};
char* page;
struct test_template tdata = {
.HTTP_COOKIE = ENV_TBL_GET(ENV_HTTP_COOKIE),
.PATH_INFO = ENV_TBL_GET(ENV_PATH_INFO),
.QUERY_STRING = ENV_TBL_GET(ENV_QUERY_STRING),
.REQUEST_METHOD = ENV_TBL_GET(ENV_REQUEST_METHOD),
.SCRIPT_NAME = ENV_TBL_GET(ENV_SCRIPT_NAME),
.HTTP_REFERER = ENV_TBL_GET(ENV_HTTP_REFERER),
.HTTP_USER_AGENT = ENV_TBL_GET(ENV_HTTP_USER_AGENT),
.CONTENT_LENGTH = ENV_TBL_GET(ENV_CONTENT_LENGTH)
};
page = tmpl_gen_test(&tdata, NULL);
struct base_page b = {
.category = BASE_CAT_NONE,
.content = page,
.sidebar_left = NULL
};
// Output
render_base_page(&b, req, ssn, api);
if (page) free(page);
}

View file

@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "global_perl.h"
#include "timeline.h"
#include <stdlib.h>
#include "helpers.h"
@ -25,16 +26,11 @@
#include "index.h"
#include "status.h"
#include "easprintf.h"
#include "reply.h"
#include "navigation.h"
#include "query.h"
#include "error.h"
#include "string_helpers.h"
#include "../static/timeline_options.ctmpl"
#include "../static/navigation.ctmpl"
void content_timeline(FCGX_Request* req,
void content_timeline(REQUEST_T req,
struct session* ssn,
mastodont_t* api,
struct mstdnt_storage* storage,
@ -42,67 +38,34 @@ void content_timeline(FCGX_Request* req,
size_t statuses_len,
enum base_category cat,
char* header_text,
int show_post_box)
int show_post_box,
int fake_timeline)
{
size_t statuses_html_count = 0;
char* status_format = NULL,
* header = NULL,
* post_box = NULL,
* navigation_box = NULL,
* timeline_options,
* output = NULL,
* start_id;
if (storage->error)
status_format = construct_error(storage->error, E_ERROR, 1, NULL);
else
{
// Construct statuses into HTML
status_format = construct_statuses(ssn, api, statuses, statuses_len, NULL, &statuses_html_count);
if (!status_format)
status_format = construct_error("No statuses", E_NOTICE, 1, NULL);
}
// Create post box
if (show_post_box)
post_box = construct_post_box(NULL, "", NULL);
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);
}
// Create timeline options/menubar
struct timeline_options_template todata = {
.only_media = "Only media?",
.replies = "Replies?",
.only_media_active = keyint(ssn->post.only_media) ? "checked" : NULL,
};
timeline_options = tmpl_gen_timeline_options(&todata, NULL);
// Display a header bar, usually customized for specific pages
if (header_text)
{
easprintf(&header, "<div class=\"simple-page simple-page-header\"><h1>%s</h1></div>",
header_text);
}
PERL_STACK_INIT;
HV* session_hv = perlify_session(ssn);
mXPUSHs(newRV_inc((SV*)session_hv));
mXPUSHs(newRV_inc((SV*)template_files));
easprintf(&output, "%s%s%s%s%s",
STR_NULL_EMPTY(header),
STR_NULL_EMPTY(post_box),
STR_NULL_EMPTY(timeline_options),
STR_NULL_EMPTY(status_format),
STR_NULL_EMPTY(navigation_box));
if (statuses)
mXPUSHs(newRV_noinc((SV*)perlify_statuses(statuses, statuses_len)));
else ARG_UNDEFINED();
if (header_text)
mXPUSHs(newSVpv(header_text, 0));
else ARG_UNDEFINED();
mXPUSHi(show_post_box);
mXPUSHi(fake_timeline);
PERL_STACK_SCALAR_CALL("timeline::content_timeline");
// Duplicate to free temps
char* dup = PERL_GET_STACK_EXIT;
struct base_page b = {
.category = cat,
.content = output,
.content = dup,
.session = session_hv,
.sidebar_left = NULL
};
@ -110,17 +73,12 @@ void content_timeline(FCGX_Request* req,
render_base_page(&b, req, ssn, api);
// Cleanup
mastodont_storage_cleanup(storage);
mstdnt_storage_cleanup(storage);
mstdnt_cleanup_statuses(statuses, statuses_len);
free(status_format);
free(post_box);
free(header);
free(timeline_options);
free(navigation_box);
free(output);
tb_free(dup);
}
void tl_home(FCGX_Request* req, struct session* ssn, mastodont_t* api, int local)
void tl_home(REQUEST_T req, struct session* ssn, mastodont_t* api, int local)
{
struct mstdnt_args m_args = { 0 };
set_mstdnt_args(&m_args, ssn);
@ -146,12 +104,12 @@ void tl_home(FCGX_Request* req, struct session* ssn, mastodont_t* api, int local
try_post_status(ssn, api);
mastodont_timeline_home(api, &m_args, &args, &storage, &statuses, &statuses_len);
mstdnt_timeline_home(api, &m_args, &args, &storage, &statuses, &statuses_len);
content_timeline(req, ssn, api, &storage, statuses, statuses_len, BASE_CAT_HOME, NULL, 1);
content_timeline(req, ssn, api, &storage, statuses, statuses_len, BASE_CAT_HOME, NULL, 1, 0);
}
void tl_direct(FCGX_Request* req, struct session* ssn, mastodont_t* api)
void tl_direct(REQUEST_T req, struct session* ssn, mastodont_t* api)
{
struct mstdnt_args m_args = { 0 };
set_mstdnt_args(&m_args, ssn);
@ -174,12 +132,12 @@ void tl_direct(FCGX_Request* req, struct session* ssn, mastodont_t* api)
try_post_status(ssn, api);
mastodont_timeline_direct(api, &m_args, &args, &storage, &statuses, &statuses_len);
mstdnt_timeline_direct(api, &m_args, &args, &storage, &statuses, &statuses_len);
content_timeline(req, ssn, api, &storage, statuses, statuses_len, BASE_CAT_DIRECT, "Direct", 0);
content_timeline(req, ssn, api, &storage, statuses, statuses_len, BASE_CAT_DIRECT, "Direct", 0, 0);
}
void tl_public(FCGX_Request* req, struct session* ssn, mastodont_t* api, int local, enum base_category cat)
void tl_public(REQUEST_T req, struct session* ssn, mastodont_t* api, int local, enum base_category cat)
{
struct mstdnt_args m_args = { 0 };
set_mstdnt_args(&m_args, ssn);
@ -204,12 +162,12 @@ void tl_public(FCGX_Request* req, struct session* ssn, mastodont_t* api, int loc
try_post_status(ssn, api);
mastodont_timeline_public(api, &m_args, &args, &storage, &statuses, &statuses_len);
mstdnt_timeline_public(api, &m_args, &args, &storage, &statuses, &statuses_len);
content_timeline(req, ssn, api, &storage, statuses, statuses_len, cat, NULL, 1);
content_timeline(req, ssn, api, &storage, statuses, statuses_len, cat, NULL, 1, 0);
}
void tl_list(FCGX_Request* req, struct session* ssn, mastodont_t* api, char* list_id)
void tl_list(REQUEST_T req, struct session* ssn, mastodont_t* api, char* list_id)
{
struct mstdnt_args m_args;
set_mstdnt_args(&m_args, ssn);
@ -231,13 +189,13 @@ void tl_list(FCGX_Request* req, struct session* ssn, mastodont_t* api, char* lis
try_post_status(ssn, api);
mastodont_timeline_list(api, &m_args, list_id, &args, &storage, &statuses, &statuses_len);
mstdnt_timeline_list(api, &m_args, list_id, &args, &storage, &statuses, &statuses_len);
content_timeline(req, ssn, api, &storage, statuses, statuses_len, BASE_CAT_LISTS, "List timeline", 0);
content_timeline(req, ssn, api, &storage, statuses, statuses_len, BASE_CAT_LISTS, "List timeline", 0, 0);
}
void tl_tag(FCGX_Request* req, struct session* ssn, mastodont_t* api, char* tag_id)
void tl_tag(REQUEST_T req, struct session* ssn, mastodont_t* api, char* tag_id)
{
struct mstdnt_args m_args;
set_mstdnt_args(&m_args, ssn);
@ -258,12 +216,12 @@ void tl_tag(FCGX_Request* req, struct session* ssn, mastodont_t* api, char* tag_
.limit = 20,
};
mastodont_timeline_tag(api, &m_args, tag_id, &args, &storage, &statuses, &statuses_len);
mstdnt_timeline_tag(api, &m_args, tag_id, &args, &storage, &statuses, &statuses_len);
easprintf(&header, "Hashtag - #%s", tag_id);
content_timeline(req, ssn, api, &storage, statuses, statuses_len, BASE_CAT_NONE, header, 0);
free(header);
content_timeline(req, ssn, api, &storage, statuses, statuses_len, BASE_CAT_NONE, header, 0, 0);
tb_free(header);
}
void content_tl_home(PATH_ARGS)

View file

@ -18,28 +18,67 @@
#ifndef TIMELINE_H
#define TIMELINE_H
#include <fcgi_stdio.h>
#include <fcgiapp.h>
#include <stddef.h>
#include <mastodont.h>
#include "path.h"
#include "session.h"
#include "base_page.h"
#include "cgi.h"
#include "request.h"
// Federated and local are here
void tl_home(FCGX_Request* req, struct session* ssn, mastodont_t* api, int local);
void tl_direct(FCGX_Request* req, struct session* ssn, mastodont_t* api);
void tl_public(FCGX_Request* req, struct session* ssn, mastodont_t* api, int local, enum base_category cat);
void tl_list(FCGX_Request* req, struct session* ssn, mastodont_t* api, char* list_id);
void tl_tag(FCGX_Request* req, struct session* ssn, mastodont_t* api, char* tag);
/** Wrapper for content_tl_federated */
void tl_home(REQUEST_T req, struct session* ssn, mastodont_t* api, int local);
/** Wrapper for content_tl_direct */
void tl_direct(REQUEST_T req, struct session* ssn, mastodont_t* api);
/** Wrapper for content_tl_federated */
void tl_public(REQUEST_T req, struct session* ssn, mastodont_t* api, int local, enum base_category cat);
/** Wrapper for content_tl_list */
void tl_list(REQUEST_T req, struct session* ssn, mastodont_t* api, char* list_id);
/** Wrapper for content_tl_tag */
void tl_tag(REQUEST_T req, struct session* ssn, mastodont_t* api, char* tag);
/* ------------------------------------------------ */
/** Federated timeline */
void content_tl_federated(PATH_ARGS);
/** Home timeline. Shows federated timeline if not logged in */
void content_tl_home(PATH_ARGS);
/** Direct message timeline */
void content_tl_direct(PATH_ARGS);
/** Local/instance timeline */
void content_tl_local(PATH_ARGS);
/** List timeline */
void content_tl_list(PATH_ARGS);
/** Hashtag timeline */
void content_tl_tag(PATH_ARGS);
void content_timeline(FCGX_Request* req,
/**
* Used to create generic timeline content. This timeline includes other features
* such as viewing only media, hiding muted, etc. as options on the top of the
* timeline, so this should only be used for API's which are considered "timelines"
* to Pleroma/Mastodon.
*
* @param req This request
* @param ssn This session
* @param api The api
* @param storage The storage for statuses, will be cleaned up in this function, do NOT
* cleanup yourself.
* @param statuses The statuses, will be cleaned up in this function, do NOT cleanup yourself.
* @param statuses_len Length of `statuses`
* @param cat The category to "highlight" on the sidebar
* @param header A header that is displayed above the timeline.
* @param show_post_box If the post box should be shown or not.
*/
void content_timeline(REQUEST_T req,
struct session* ssn,
mastodont_t* api,
struct mstdnt_storage* storage,
@ -47,6 +86,7 @@ void content_timeline(FCGX_Request* req,
size_t statuses_len,
enum base_category cat,
char* header,
int show_post_box);
int show_post_box,
int fake_timeline);
#endif // TIMELINE_H

View file

@ -1,67 +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 "type_string.h"
// Icons
#include "../static/like_svg.ctmpl"
#include "../static/repeat_svg.ctmpl"
#include "../static/follow_svg.ctmpl"
const char* notification_type_str(mstdnt_notification_t type)
{
switch (type)
{
case MSTDNT_NOTIFICATION_FOLLOW: return L10N[L10N_EN_US][L10N_NOTIF_FOLLOW];
case MSTDNT_NOTIFICATION_FOLLOW_REQUEST: return L10N[L10N_EN_US][L10N_NOTIF_FOLLOW_REQUEST];
case MSTDNT_NOTIFICATION_REBLOG: return L10N[L10N_EN_US][L10N_NOTIF_REPEATED];
case MSTDNT_NOTIFICATION_FAVOURITE: return L10N[L10N_EN_US][L10N_NOTIF_LIKED];
case MSTDNT_NOTIFICATION_POLL: return L10N[L10N_EN_US][L10N_NOTIF_POLL];
case MSTDNT_NOTIFICATION_EMOJI_REACT: return L10N[L10N_EN_US][L10N_NOTIF_REACTED_WITH];
default: return "";
}
}
const char* notification_type_compact_str(mstdnt_notification_t type)
{
switch (type)
{
case MSTDNT_NOTIFICATION_FOLLOW: return L10N[L10N_EN_US][L10N_NOTIF_COMPACT_FOLLOW];
case MSTDNT_NOTIFICATION_FOLLOW_REQUEST: return L10N[L10N_EN_US][L10N_NOTIF_COMPACT_FOLLOW_REQUEST];
case MSTDNT_NOTIFICATION_REBLOG: return L10N[L10N_EN_US][L10N_NOTIF_COMPACT_REPEATED];
case MSTDNT_NOTIFICATION_FAVOURITE: return L10N[L10N_EN_US][L10N_NOTIF_COMPACT_LIKED];
case MSTDNT_NOTIFICATION_POLL: return L10N[L10N_EN_US][L10N_NOTIF_COMPACT_POLL];
case MSTDNT_NOTIFICATION_EMOJI_REACT: return L10N[L10N_EN_US][L10N_NOTIF_COMPACT_REACTED_WITH];
default: return "";
}
}
const char* notification_type_svg(mstdnt_notification_t type)
{
switch (type)
{
case MSTDNT_NOTIFICATION_FOLLOW: return data_follow_svg;
case MSTDNT_NOTIFICATION_FOLLOW_REQUEST: return "";
case MSTDNT_NOTIFICATION_REBLOG: return data_repeat_svg;
case MSTDNT_NOTIFICATION_FAVOURITE: return data_like_svg;
case MSTDNT_NOTIFICATION_POLL: return "";
case MSTDNT_NOTIFICATION_EMOJI_REACT: return "";
default: return "";
}
}

View file

@ -1,28 +0,0 @@
<div class="simple-page">
<div class="about-header">
<h1><img id="about-icon" src="/treebird_logo.png"> <span>Treebird</span></h1>
</div>
<div class="about-content">
<p>Treebird is a Pleroma frontend that is lightweight, efficient, and true to the web. It's written in C with FCGI making it simple and fast to work with and deploy. It is very tight to the philosophy of how the internet has always worked; Javascript provides extra sugar (or scripting) to improve the experience but not a requirement, while still being simple enough that anyone can use it.</p>
<p>Treebird was created in response to PleromaFE performance issues and ironically a lack of PleromaAPI backend support. Treebird resembles GNU Social in appearance by default, but keeps the familiarity that many are used to of PleromaFE.</p>
<p>Treebird was created by <a href="/@nekobit@rdrama.cc">Nekobit</a>, who created <a href="https://fossil.nekobit.net/mastodont-c/home">mastodont-c</a>, a library that can communicate with Revolver's, Pleroma's, and Mastodon's REST APIs.</p>
<h3>Other contributors</h3>
<ul>
<li><a href="/@coyote@pl.lain.sh" alt="Created Solarized themes for Treebird">Coyote</a></li>
<li><a href="/@grumbulon@freecumextremist.com" alt="Helped with the original dark theme, and created the original treebird.dev website">Grumbulon</a></li>
<li><a href="/@khan@sleepy.cafe" alt="Started idea of treebird">Khan</a></li>
<li><a href="/@sam@froth.zone" alt="Testing with Nginx and documentation">SamTherapy</a></li>
<li><a href="/@pch_xyz@seediqbale.xyz" alt="Chinese (Traditional) translations">Pacific Coast Highway (pch_xyz)</a></li>
</ul>
<p>Treebird is licensed in AGPLv3, and mastodont-c is LGPLv3 licensed. Other licenses apply to libraries as well. <a href="/about/license">View the AGPLv3 license here.</a></p>
<a class="btn btn-single" href="https://fossil.nekobit.net/treebird/home">View the Fossil Repository</a> <a class="btn btn-single" href="/about/license">View the License</a>
</div>
</div>
<!-- Scripts -->
<script src="/js/worm.js"></script>

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