Compare commits

...

2106 Commits

Author SHA1 Message Date
ashley
36a885d3e1 add telemetry 2025-11-16 20:47:42 +01:00
ashley
0dfca92cdf move telemetry to somewhere else 2025-11-16 20:46:42 +01:00
ashley
5880f2faa8 Add src/libpoketube/init/telemetry.js 2025-11-16 20:44:53 +01:00
ashley
6c698153b6 Update src/libpoketube/init/pages-404-and-main.js 2025-11-15 20:39:16 +01:00
ashley
cec653d921 Update src/libpoketube/init/pages-404-and-main.js 2025-11-15 20:38:27 +01:00
ashley
d6b90620ea Update src/libpoketube/init/pages-404-and-main.js 2025-11-15 20:36:58 +01:00
ashley
49c25ddb1a Update src/libpoketube/init/pages-404-and-main.js 2025-11-15 20:34:27 +01:00
ashley
66d267be05 Update html/discover.ejs 2025-11-15 20:31:36 +01:00
ashley
a810897b3c Update src/libpoketube/libpoketube-youtube-player.js 2025-11-13 21:49:51 +01:00
ashley
2ed3762f19 Update html/watch.ejs 2025-11-13 21:22:36 +01:00
ashley
cbd6b155fb Update src/libpoketube/init/pages-404-and-main.js 2025-11-13 21:19:08 +01:00
ashley
4b953a1edd Update src/libpoketube/init/pages-api.js 2025-11-07 20:41:42 +01:00
ashley
eb6d2f0ea1 Update src/libpoketube/init/pages-api.js 2025-11-07 20:38:49 +01:00
ashley
44a8f60c04 Update html/watch.ejs 2025-11-07 18:16:17 +01:00
ashley
00e9c0fe4f Update css/stats.js 2025-11-07 18:14:23 +01:00
ashley
87b75d1c0e Update html/watch.ejs 2025-11-07 17:39:33 +01:00
ashley
958fe7c900 Add css/stats.js 2025-11-07 17:37:17 +01:00
ashley
93ca204522 Update html/watch.ejs 2025-11-07 15:44:46 +01:00
ashley
29f78bfbb7 Update css/player-base.js 2025-11-07 14:20:55 +01:00
ashley
504f2aeb27 Update css/player-base.js 2025-11-07 13:04:19 +01:00
ashley
145cd1a694 Update html/watch.ejs 2025-11-07 12:53:02 +01:00
ashley
276d698626 Add css/improving-poke.js 2025-11-07 12:48:29 +01:00
ashley
6ec6012b2b Update css/player-base.js 2025-11-07 12:36:53 +01:00
ashley
06030b63c6 Update css/player-base.js 2025-11-07 00:57:42 +01:00
ashley
0229550364 Update html/watch.ejs 2025-11-07 00:52:37 +01:00
ashley
ef114f3aab Update html/watch.ejs 2025-11-07 00:51:12 +01:00
ashley
49e5b9182f Update html/terms.ejs 2025-11-07 00:41:06 +01:00
ashley
f8145b95b3 Update html/terms.ejs 2025-11-07 00:40:36 +01:00
ashley
8a810225cf Update html/watch.ejs 2025-11-07 00:31:39 +01:00
ashley
bc1b9e10da Update css/player-base.js 2025-11-07 00:30:45 +01:00
ashley
1b8f7f1a34 Update html/watch.ejs 2025-11-07 00:17:17 +01:00
ashley
b7e8eb4ac5 Update html/priv.ejs 2025-11-07 00:13:17 +01:00
ashley
a607b5088a Update src/libpoketube/init/pages-api.js 2025-11-07 00:11:00 +01:00
ashley
4bbe3f3161 Update html/watch.ejs 2025-11-07 00:09:37 +01:00
ashley
5bc604c420 Update src/libpoketube/init/pages-api.js 2025-11-07 00:08:23 +01:00
ashley
dadfbc2183 Update src/libpoketube/init/pages-api.js 2025-11-07 00:05:10 +01:00
ashley
690b97c99a Update src/libpoketube/init/pages-api.js 2025-11-06 23:23:03 +01:00
ashley
99ffd2643f Update src/libpoketube/init/pages-api.js 2025-11-06 23:20:59 +01:00
ashley
e2e3646d77 Update src/libpoketube/init/pages-api.js 2025-11-06 23:19:20 +01:00
ashley
2f46876559 Update src/libpoketube/init/pages-api.js 2025-11-06 23:14:29 +01:00
ashley
67138a72f9 Update src/libpoketube/init/pages-api.js 2025-11-06 23:10:43 +01:00
ashley
29b0a6e6e5 Update src/libpoketube/init/pages-api.js 2025-11-06 23:07:08 +01:00
ashley
e7d46c2d70 Update html/priv.ejs 2025-11-06 23:04:08 +01:00
ashley
0990581f20 Update html/priv.ejs 2025-11-06 22:59:12 +01:00
ashley
11094bb820 Update html/priv.ejs 2025-11-06 22:55:01 +01:00
ashley
024a88e802 Update src/libpoketube/init/pages-api.js 2025-11-06 22:49:46 +01:00
ashley
b03057c726 Update src/libpoketube/init/pages-api.js 2025-11-06 22:48:15 +01:00
ashley
3f4f7c7aa2 Update html/watch.ejs 2025-11-06 22:47:15 +01:00
ashley
0b1fa1d5ba Update src/libpoketube/init/pages-api.js 2025-11-06 22:46:48 +01:00
ashley
d67072b7ec Update html/watch.ejs 2025-11-06 22:37:58 +01:00
ashley
0d18e56221 Update src/libpoketube/init/pages-api.js 2025-11-06 22:35:00 +01:00
ashley
f4c428e2d4 Update html/watch.ejs 2025-11-06 22:22:17 +01:00
ashley
485bf527df Update html/landing.ejs 2025-10-26 01:15:56 +02:00
ashley
3922e225fb Update html/watch.ejs 2025-10-23 01:55:14 +02:00
ashley
e43db67db8 Update html/watch.ejs 2025-10-23 01:54:44 +02:00
ashley
20cb8df258 add stuff 2025-10-23 01:52:01 +02:00
ashley
1be26d2f1d Update css/player-base.js 2025-10-22 21:38:02 +02:00
ashley
4ca6bbf7e1 Update css/player-base.js 2025-10-22 20:39:01 +02:00
ashley
a2a3149a5b Update html/landing.ejs 2025-10-19 16:58:19 +02:00
ashley
68141b789b Update html/landing.ejs 2025-10-19 16:56:18 +02:00
ashley
edf0a8d3d3 Update html/account-me.ejs 2025-10-18 22:41:22 +02:00
ashley
9aeeb9cfbc Update html/account-me.ejs 2025-10-18 22:40:02 +02:00
ashley
1e8ee4cdb6 Update html/account-me.ejs 2025-10-18 22:39:29 +02:00
ashley
302fa76fa7 Update html/account-me.ejs 2025-10-18 22:35:17 +02:00
ashley
130bca9e9e Update html/account-me.ejs 2025-10-18 22:30:33 +02:00
ashley
d8c709468e Update html/account-me.ejs 2025-10-18 22:27:32 +02:00
ashley
6fb3bb1240 Update html/account-me.ejs 2025-10-18 22:26:01 +02:00
ashley
d639579c54 Update html/account-me.ejs 2025-10-18 22:25:29 +02:00
ashley
c6f565eb16 Update html/account-me.ejs 2025-10-18 22:14:25 +02:00
ashley
31f7aefd66 Update html/channel.ejs 2025-10-18 22:12:12 +02:00
ashley
568db986e4 Update html/channel.ejs 2025-10-18 22:11:15 +02:00
ashley
af0dd0ac14 Update src/libpoketube/init/pages-channel-and-download.js 2025-10-18 22:09:11 +02:00
ashley
f4071138f0 Update html/channel.ejs 2025-10-18 22:08:28 +02:00
ashley
59b2369507 Update html/account-me.ejs 2025-10-18 22:05:29 +02:00
ashley
b170ea4713 Update html/account-me.ejs 2025-10-18 22:03:45 +02:00
ashley
e5ba2252cc Update html/account-me.ejs 2025-10-18 22:02:03 +02:00
ashley
beaa771e86 Update html/watch.ejs 2025-10-18 21:46:01 +02:00
ashley
4d18cd11c0 Update css/poketube.css 2025-10-18 21:45:22 +02:00
ashley
fcaa2db7fa Update html/watch.ejs 2025-10-18 21:36:55 +02:00
ashley
911623b4b4 Update css/poketube.css 2025-10-18 21:36:18 +02:00
ashley
af6f0091ba Update html/discover.ejs 2025-10-18 16:05:04 +02:00
ashley
f9b025ea8b Update html/discover.ejs 2025-10-18 16:02:16 +02:00
ashley
b2a3f74042 Update html/discover.ejs 2025-10-18 15:57:22 +02:00
ashley
06def11d2b Update css/player-base.js 2025-10-18 09:20:05 +02:00
ashley
aa18ad6395 Update html/search.ejs 2025-10-18 08:54:43 +02:00
ashley
e350e7671c Update html/watch.ejs 2025-10-18 08:51:35 +02:00
ashley
bcd4756c96 Update html/search.ejs 2025-10-18 08:51:03 +02:00
ashley
504ba2216d add blur here 2025-10-18 08:14:16 +02:00
ashley
d4049757e0 Update html/terms.ejs 2025-10-17 22:51:36 +02:00
ashley
8a85e9d0fa Update html/terms.ejs 2025-10-17 22:41:05 +02:00
ashley
787d95f8c9 Update css/player-base.js 2025-10-17 22:08:00 +02:00
ashley
58fe11a069 Update css/player-base.js 2025-10-17 22:07:05 +02:00
ashley
fec40d2063 Update css/player-base.js 2025-10-17 21:45:09 +02:00
ashley
b8fe4d611d Update css/player-base.js 2025-10-17 21:26:29 +02:00
ashley
423ad8ef89 Update css/player-base.js 2025-10-17 21:12:38 +02:00
ashley
4051272a6c Update css/player-base.js 2025-10-17 21:04:39 +02:00
ashley
c879b83a77 Update css/player-base.js 2025-10-17 20:45:58 +02:00
ashley
3a4737518f Update css/player-base.js 2025-10-17 20:10:11 +02:00
ashley
a9977ffb08 Update css/player-base.js 2025-10-17 20:04:08 +02:00
ashley
a67f4ba75e Update css/player-base.js 2025-10-17 19:54:45 +02:00
ashley
241d221728 Update css/player-base.js 2025-10-17 19:53:40 +02:00
ashley
0e97b07a3c make stuff work good 2025-10-17 19:49:11 +02:00
ashley
7a15b8c143 Update css/player-base.js 2025-10-17 19:38:08 +02:00
ashley
835ae253fd Update css/player-base.js 2025-10-17 19:32:32 +02:00
ashley
39bdf65c73 Update css/player-base.js 2025-10-17 19:24:24 +02:00
ashley
29b7af61f1 make stuff work good 2025-10-17 19:18:16 +02:00
ashley
5ed4859b54 fix stuff add stuff 2025-10-17 19:17:15 +02:00
ashley
5e8a835450 add terms of service redirect 2025-10-17 16:44:36 +02:00
ashley
1f0acef506 add terms of service redirect 2025-10-17 16:42:47 +02:00
ashley
aa9710483f add joined 2025-10-15 20:37:46 +02:00
ashley
45d0f42b94 Update src/libpoketube/libpoketube-youtube-player.js 2025-10-11 21:09:41 +02:00
ashley
02e36e094c fix stuff + add stuff 2025-10-11 20:15:18 +02:00
ashley
76bd3f6591 fix stuff + add stuff 2025-10-11 20:05:24 +02:00
ashley
c29e018490 fix sources 2025-10-11 20:04:08 +02:00
ashley
33dafb5a9d fix stuff + add stuff 2025-10-11 19:27:45 +02:00
ashley
4ab7637bb5 fix stuff + add stuff 2025-10-11 19:20:56 +02:00
ashley
0dcaf6d931 add legal text summary 2025-10-11 19:20:05 +02:00
ashley
3c3cafe44a update code of conduct 2025-10-11 19:07:02 +02:00
ashley
3f4fdc8e6e fix stuff + add stuff 2025-10-11 15:09:31 +02:00
ashley
70aa3567f7 fix stuff + add stuff 2025-10-11 15:07:46 +02:00
ashley
f15ee011c4 cool stuff 2025-10-11 15:04:14 +02:00
ashley
96aecad6ee Update html/coc.ejs 2025-10-11 14:34:14 +02:00
ashley
a17a1322a3 Update html/coc.ejs 2025-10-11 14:32:14 +02:00
ashley
8980b700a5 fix stuff + add stuff 2025-10-11 14:31:29 +02:00
ashley
1d95fc2009 fix stuff + add stuff 2025-10-11 14:29:47 +02:00
ashley
b2c80c5ca7 update 2025-10-11 14:27:29 +02:00
ashley
36bfd8e547 update url 2025-10-11 14:18:26 +02:00
ashley
1b261d7476 Update CODE_OF_CONDUCT.md 2025-10-11 14:17:19 +02:00
ashley
6870dbb826 Update src/libpoketube/init/pages-static.js 2025-10-11 14:15:08 +02:00
ashley
fb7e15e459 add code of conduct 2025-10-11 14:14:31 +02:00
ashley
ca68dbf102 fix stuff + add stuff 2025-10-11 13:57:50 +02:00
ashley
fcb66ac1c6 fix stuff + add stuff 2025-10-11 13:56:36 +02:00
ashley
fa6abfeb8c fix stuff + add stuff 2025-10-11 13:52:36 +02:00
ashley
9ce02da001 fix stuff + add stuff 2025-10-11 13:50:05 +02:00
ashley
05c1f44c59 test something 2025-10-11 13:45:45 +02:00
ashley
b1ba914f83 Update html/priv.ejs 2025-10-11 13:41:07 +02:00
ashley
34f4b230bc Update html/priv.ejs 2025-10-11 13:35:18 +02:00
ashley
84154d063c fix stuff + add stuff 2025-10-11 12:47:45 +02:00
ashley
e849f90e79 // Hide ambient canvases when fullscreen and restore when exiting fullscreen 2025-10-11 12:45:23 +02:00
ashley
0980866bed Update html/piano.ejs 2025-10-11 12:35:05 +02:00
ashley
9e17b74851 fix stuff + add stuff 2025-10-11 12:29:53 +02:00
ashley
492d729247 Update html/piano.ejs 2025-10-11 12:27:02 +02:00
ashley
ae0ecba63f Update html/studio-landing.ejs 2025-10-11 12:20:54 +02:00
ashley
3ee25659b1 Update html/studio-landing.ejs 2025-10-11 12:20:19 +02:00
ashley
6f25a15f9b fix stuff + add stuff 2025-10-11 12:19:14 +02:00
ashley
d4d3193cc4 Update html/studio-landing.ejs 2025-10-11 12:09:16 +02:00
ashley
2487292e60 Update html/strings.ejs 2025-10-11 12:08:43 +02:00
ashley
dadd6836da fix stuff + add stuff 2025-10-11 12:06:32 +02:00
ashley
a03274fa20 fix stuff + add stuff 2025-10-11 12:05:15 +02:00
ashley
33b4135c4d fix stuff + add stuff 2025-10-11 12:02:58 +02:00
ashley
25f9267280 fix stuff + add stuff 2025-10-10 21:27:03 +02:00
ashley
078682ec77 Update src/libpoketube/init/pages-api.js 2025-10-10 21:20:38 +02:00
ashley
c74a1769c3 fix stuff + add stuff 2025-10-10 21:19:36 +02:00
ashley
df34dfa146 fix stuff + add stuff 2025-10-10 21:02:17 +02:00
ashley
10cac0110c fix stuff + add stuff 2025-10-10 20:58:27 +02:00
ashley
bf74f72274 Update src/libpoketube/init/pages-api.js 2025-10-10 20:55:37 +02:00
ashley
257147f3d4 Update html/watch.ejs 2025-10-10 20:49:21 +02:00
ashley
28a12f1116 fix stuff + add stuff 2025-10-10 20:48:06 +02:00
ashley
58ee26214e fix stuff + add stuff 2025-10-10 20:42:43 +02:00
ashley
0f0fa21026 fix stuff + add stuff 2025-10-10 20:41:57 +02:00
ashley
f0420192aa fix stuff + add stuff 2025-10-10 20:36:05 +02:00
ashley
5cb4f54947 Update css/player-base.js 2025-10-10 20:17:20 +02:00
ashley
6e095bbe30 fix stuff + add stuff 2025-10-10 20:14:57 +02:00
ashley
436a96eea9 Update css/player-base.js 2025-10-10 20:10:23 +02:00
ashley
3860ec7c97 fix stuff + add stuff 2025-10-10 20:07:16 +02:00
ashley
a22f2e2631 fix stuff + add stuff 2025-10-10 20:06:15 +02:00
ashley
02866266bd fix stuff + add stuff 2025-10-10 20:01:33 +02:00
ashley
2fd6d2f343 fix stuff + add stuff 2025-10-10 19:59:31 +02:00
ashley
850194524e fix stuff + add stuff 2025-10-10 19:53:00 +02:00
ashley
980dbb1cef fix stuff + add stuff 2025-10-10 19:33:05 +02:00
ashley
2f56fa27a6 fix stuff + add stuff 2025-10-10 19:07:12 +02:00
ashley
1fc55843f3 fix stuff + add stuff 2025-10-10 19:06:50 +02:00
ashley
712714f66c make it a webp 2025-10-09 17:44:35 +02:00
ashley
c21e5a5ccc remove redtape 2025-10-09 17:43:59 +02:00
ashley
5e38880110 fix stuff + add stuff 2025-10-09 12:35:45 +02:00
ashley
ae5906aeee fix stuff + add stuff 2025-10-09 12:23:49 +02:00
ashley
c9b36cf4f7 fix stuff + add stuff 2025-10-09 09:41:12 +02:00
ashley
56f2454136 Update src/libpoketube/init/pages-video.js 2025-10-09 09:37:32 +02:00
ashley
2e41019c40 make it wayyy better 2025-10-09 09:36:02 +02:00
ashley
a06d43ba56 test something 2025-10-09 09:31:03 +02:00
ashley
dad3607ddc make the border radius of the video thumbnail same as the watch page 2025-10-09 09:14:57 +02:00
ashley
1b6643c292 make the border radius of the video thumbnail same as the watch page 2025-10-09 09:14:09 +02:00
ashley
433d680987 make the border radius of the video thumbnail same as the watch page 2025-10-09 09:12:51 +02:00
ashley
cd2152238d make the border radius of the video thumbnail same as the watch page 2025-10-09 09:11:11 +02:00
ashley
17b070bf33 do some cool stuff 2025-10-08 23:02:31 +02:00
ashley
5f0172b6dc Update README.md 2025-10-08 15:58:23 +02:00
ashley
4da2550c5d oh well 2025-10-08 11:23:51 +02:00
ashley
817024e98e test something 2025-10-08 11:19:48 +02:00
ashley
43d2477e07 make the tags better 2025-10-07 22:42:22 +02:00
ashley
d1088e274b bump version(s) :3 2025-10-07 22:39:17 +02:00
ashley
4e09c44150 test something 2025-10-07 16:34:36 +02:00
ashley
42a1b60b0c fix stuff + add stuff 2025-10-07 16:32:23 +02:00
ashley
d46a712051 fix stuff + add stuff 2025-10-07 16:30:22 +02:00
ashley
39530d0d3e fix stuff + add stuff 2025-10-07 16:23:04 +02:00
ashley
c889ede304 Update src/libpoketube/init/pages-api.js 2025-10-07 16:21:07 +02:00
ashley
ee7e1b17a1 Update src/libpoketube/init/pages-api.js 2025-10-07 16:18:56 +02:00
ashley
b323ca0ea1 Update src/libpoketube/init/pages-api.js 2025-10-07 16:16:53 +02:00
ashley
31c3aebcdd Update src/libpoketube/init/pages-api.js 2025-10-07 16:16:14 +02:00
ashley
edaa64f121 Update src/libpoketube/init/pages-api.js 2025-10-07 16:15:48 +02:00
ashley
bd0335f598 Update package.json 2025-10-07 16:15:15 +02:00
ashley
b2713f69f2 Update src/libpoketube/init/pages-api.js 2025-10-07 16:14:47 +02:00
ashley
04aa0a4cf4 make it a file 2025-10-07 16:12:09 +02:00
ashley
e03eb818a0 remove 2025-10-07 16:11:20 +02:00
ashley
92163d6287 Update package.json 2025-10-07 16:07:12 +02:00
ashley
622f56862c fix stuff + add stuff 2025-10-07 16:06:11 +02:00
ashley
ae4bc72906 add /api/geo 2025-10-07 15:51:36 +02:00
ashley
1a57c96c42 add ip2country 2025-10-07 15:47:39 +02:00
ashley
c335ae9394 Update html/watch.ejs 2025-10-07 11:48:30 +02:00
ashley
f0258b04a3 fix stuff + add stuff 2025-10-07 11:46:55 +02:00
ashley
48c197f539 fix stuff + add stuff 2025-10-07 11:36:24 +02:00
ashley
495a97e7de bump version(s) :3 2025-10-07 11:35:54 +02:00
ashley
914f761143 bump video.js 2025-10-07 11:34:37 +02:00
ashley
cb4bbe445f add comments to explain videojs 2025-10-07 11:24:18 +02:00
ashley
833da99dec fix stuff + add stuff 2025-10-07 11:13:55 +02:00
ashley
705dfffe9f fix stuff + add stuff 2025-10-07 02:02:25 +02:00
ashley
8c3be36629 Update html/watch.ejs 2025-10-07 02:01:12 +02:00
ashley
2479fdbc87 fix stuff + add stuff 2025-10-07 01:58:37 +02:00
ashley
a219aa4192 fix stuff + add stuff 2025-10-07 01:56:31 +02:00
ashley
4056cf88dc add authorchannelname 2025-10-07 01:14:54 +02:00
ashley
7ea9d0436d fix stuff + add stuff 2025-10-06 20:52:33 +02:00
ashley
80039f6b3b test something 2025-10-06 20:49:12 +02:00
ashley
e741f67252 bump version(s) :3 2025-10-06 19:49:04 +02:00
ashley
f19c3c20f3 fix stuff + add stuff 2025-10-06 19:47:45 +02:00
ashley
89b405a639 Update css/player-base.js 2025-10-06 19:40:03 +02:00
ashley
bb0e1915d4 fix stuff + add stuff 2025-10-06 19:36:29 +02:00
ashley
fce480ddda Update css/player-base.js 2025-10-06 19:32:01 +02:00
ashley
278c7987f9 remove data.poketube.fun, unused 2025-10-06 19:27:38 +02:00
ashley
476b004eda Update css/player-base.js 2025-10-06 19:20:52 +02:00
ashley
b7da297df3 fix stuff + add stuff 2025-10-06 19:17:07 +02:00
ashley
88bfec6cb6 fix stuff + add stuff 2025-10-06 19:16:05 +02:00
ashley
e569462436 Update css/player-base.js 2025-10-06 19:12:41 +02:00
ashley
02334dc08b fix stuff + add stuff 2025-10-06 19:07:47 +02:00
ashley
1d07ca4bd1 fix stuff + add stuff 2025-10-06 19:05:31 +02:00
ashley
0cad0b3ff0 fix stuff + add stuff 2025-10-06 18:34:57 +02:00
ashley
6431bafae9 Update html/watch.ejs 2025-10-06 18:30:23 +02:00
ashley
af9c972496 fix stuff + add stuff 2025-10-06 18:29:08 +02:00
ashley
7b3759caa3 fix stuff + add stuff 2025-10-06 18:15:20 +02:00
ashley
6ba9b1d3f3 fix stuff + add stuff 2025-10-06 16:19:04 +02:00
ashley
4676f780d4 fix stuff + add stuff 2025-10-06 16:15:52 +02:00
ashley
1e1029c397 fix stuff + add stuff 2025-10-06 16:10:54 +02:00
ashley
56f99dd3a5 disallow /watch from crawlers 2025-10-06 16:06:22 +02:00
ashley
82a2afcece fix stuff + add stuff 2025-10-06 16:01:17 +02:00
ashley
43b660e3ca fix stuff + add stuff 2025-10-06 15:43:25 +02:00
ashley
efa44fbbb1 fix stuff + add stuff 2025-10-06 15:28:09 +02:00
ashley
ed44ea45a0 fix stuff + add stuff 2025-10-06 15:19:40 +02:00
ashley
b30ca97ef7 fix stuff + add stuff 2025-10-06 12:36:13 +02:00
ashley
9acb67bd1c fix stuff + add stuff 2025-10-06 12:29:23 +02:00
ashley
bf9a0ac97d fix stuff + add stuff 2025-10-06 12:16:31 +02:00
ashley
5f4689c3b9 Update css/player-base.js 2025-10-06 12:02:26 +02:00
ashley
a7126dbe73 Update css/player-base.js 2025-10-06 12:00:09 +02:00
ashley
3a5304644a Update css/player-base.js 2025-10-06 11:55:08 +02:00
ashley
ea7c3c0616 fix stuff + add stuff 2025-10-06 11:49:59 +02:00
ashley
110e94147e Update html/channel.ejs 2025-10-06 11:01:53 +02:00
ashley
c848a341fa Update html/channel.ejs 2025-10-06 10:58:13 +02:00
ashley
e2a06a88ff Update html/channel.ejs 2025-10-06 10:57:27 +02:00
ashley
0e2cd99703 Update html/channel.ejs 2025-10-06 10:55:32 +02:00
ashley
fdcd970c58 Update html/channel.ejs 2025-10-06 10:53:31 +02:00
ashley
f7145d8146 Update html/channel.ejs 2025-10-06 10:51:32 +02:00
ashley
bd349a2443 test something 2025-10-06 10:50:01 +02:00
ashley
2de0c69c70 add createdAccountGetDate 2025-10-06 10:48:57 +02:00
ashley
15d069a0b8 Update html/discover.ejs 2025-10-06 07:22:32 +02:00
ashley
36113b586d fix stuff + add stuff 2025-10-06 07:21:44 +02:00
ashley
6dce45833f Update html/discover.ejs 2025-10-06 06:44:05 +02:00
ashley
5091972cd3 fix stuff + add stuff 2025-10-06 06:43:07 +02:00
ashley
28d4ed673f add icons !! 2025-10-06 06:38:35 +02:00
ashley
036f2053a6 replace videos w now 2025-10-06 06:27:24 +02:00
ashley
f9dd77739c Update css/player-base.js 2025-10-05 21:19:00 +02:00
ashley
50fb670972 fix stuff + add stuff 2025-10-05 21:11:30 +02:00
ashley
9b70656a72 Update css/player-base.js 2025-10-05 21:04:04 +02:00
ashley
79a81d23a1 fix stuff + add stuff 2025-10-05 20:39:16 +02:00
ashley
17131047c3 fix stuff + add stuff 2025-10-05 20:32:24 +02:00
ashley
b5db4ecf80 fix stuff + add stuff 2025-10-05 20:25:05 +02:00
ashley
ee81da93bd fix stuff + add stuff 2025-10-05 20:15:02 +02:00
ashley
2dd8889c50 fix stuff + add stuff 2025-10-05 20:09:31 +02:00
ashley
45ce0c4daf fix stuff + add stuff 2025-10-05 20:04:40 +02:00
ashley
a3196c7e44 Update html/watch.ejs 2025-10-05 18:47:10 +02:00
ashley
59ca0e1602 fix stuff + add stuff 2025-10-05 18:46:44 +02:00
ashley
0084cbeb89 remove data.poketube.fun, unused 2025-10-05 18:42:51 +02:00
ashley
df6f0e7345 Update css/player-base.js 2025-10-05 18:28:26 +02:00
ashley
91740d46e0 Update css/player-base.js 2025-10-05 17:21:05 +02:00
ashley
76b0b0822c Update css/player-base.js 2025-10-05 17:19:17 +02:00
ashley
1b530f6293 Update css/player-base.js 2025-10-05 17:18:17 +02:00
ashley
3ad650244a Update css/player-base.js 2025-10-05 17:14:53 +02:00
ashley
e08b678640 remove data.poketube.fun, unused 2025-10-05 17:09:50 +02:00
ashley
2789ca1989 fix stuff + add stuff 2025-10-05 17:04:18 +02:00
ashley
f6720dc3c6 Update html/watch.ejs 2025-10-05 16:58:37 +02:00
ashley
4e9767d771 gefhvujg 2025-10-05 16:57:11 +02:00
ashley
62682324bd Update html/watch.ejs 2025-10-05 16:55:13 +02:00
ashley
11a928eeeb fix stuff + add stuff 2025-10-05 16:54:40 +02:00
ashley
2fef9fbc2b fix stuff + add stuff 2025-10-05 16:53:08 +02:00
ashley
aaed16a483 test something 2025-10-05 16:52:45 +02:00
ashley
26c1688242 Update html/watch.ejs 2025-10-05 16:50:05 +02:00
ashley
c58c26d6a6 test something 2025-10-05 16:07:00 +02:00
ashley
c8cd0b0d6c feat(api): add deterministic API selection strategy 2025-10-05 16:02:16 +02:00
ashley
0c8780051a make it 5000 2025-10-05 15:59:24 +02:00
ashley
9db541ac8d Update css/player-base-new.js 2025-10-05 15:40:32 +02:00
ashley
93dd8d9b78 fix stuff + add stuff 2025-10-05 15:34:40 +02:00
ashley
11988b00c8 fix stuff + add stuff 2025-10-05 15:28:31 +02:00
ashley
a75a2cf140 oopsies 2025-10-05 15:11:41 +02:00
ashley
1f004898e7 Update css/player-base-new.js 2025-10-05 14:50:54 +02:00
ashley
1d4bf3748b test something 2025-10-05 14:46:02 +02:00
ashley
418bd19804 test something 2025-10-05 14:42:16 +02:00
ashley
006d561f80 test something 2025-10-05 14:36:57 +02:00
ashley
d87ee1a55a test something 2025-10-05 14:32:13 +02:00
ashley
d6e1b4592c test something 2025-10-05 14:27:10 +02:00
ashley
410c3c9337 test something 2025-10-05 14:22:36 +02:00
ashley
e8689ca52b test something 2025-10-05 14:16:30 +02:00
ashley
5ed0d937af Update css/player-base-new.js 2025-10-05 14:04:26 +02:00
ashley
2b99c784bc test something 2025-10-05 14:00:46 +02:00
ashley
e8b0293eef fix stuff + add stuff 2025-10-05 13:50:02 +02:00
ashley
71bfe7eb10 test something 2025-10-05 13:42:26 +02:00
ashley
6c53ad3dba fix stuff + add stuff 2025-10-04 22:21:28 +02:00
ashley
34223b4bc8 use correct logo 2025-10-04 18:24:50 +02:00
ashley
3609b5e77e fix stuff + add stuff 2025-10-04 15:38:50 +02:00
ashley
3643b46a3f fix stuff + add stuff 2025-10-04 15:35:08 +02:00
ashley
fea5a6198f test something 2025-10-04 15:31:25 +02:00
ashley
858fea2777 REVERT: update privacy policy 2025-10-04 13:57:59 +02:00
ashley
3883eea281 fix stuff + add stuff 2025-10-04 13:17:47 +02:00
ashley
961b764c28 add footer!! 2025-10-04 13:14:15 +02:00
ashley
11091bcf9b fix stuff + add stuff 2025-10-04 12:22:16 +02:00
ashley
6ff3e08924 fix stuff + add stuff 2025-10-04 12:20:46 +02:00
ashley
54c767714a fix stuff + add stuff 2025-10-04 12:18:03 +02:00
ashley
0823379330 add more timezones 2025-10-04 10:33:54 +02:00
ashley
029716a0d8 Update html/search.ejs 2025-10-04 09:57:17 +02:00
ashley
807c36c713 Update html/search.ejs 2025-10-04 09:54:55 +02:00
ashley
c1a97b0f9d stufff cool 2025-10-04 09:52:31 +02:00
ashley
abe5311302 add ?. here 2025-10-03 23:22:49 +02:00
ashley
4795af5714 update privacy policy 2025-10-03 00:20:23 +02:00
PancakeSparkle
75b6ca19d7 Removed core directory 2025-10-03 01:06:13 +03:00
ashley
4fd07c9bf9 add poke-nginx-analytics.sh 2025-10-02 23:59:24 +02:00
ashley
0f13a2639b oopsies 2025-10-02 21:46:18 +02:00
ashley
b43fbf0348 fix stuff + add stuff 2025-10-02 21:45:18 +02:00
ashley
f9408fa4ad fix stuff + add stuff 2025-10-02 16:01:13 +02:00
ashley
fc161e7b0e fix stuff + add stuff 2025-10-02 13:01:53 +02:00
ashley
25a3c16f8c fix stuff + add stuff 2025-10-02 12:58:23 +02:00
ashley
1d571800e9 test something 2025-10-02 12:55:18 +02:00
ashley
5c18239ca2 Update src/libpoketube/libpoketube-youtube-player.js 2025-10-01 22:18:48 +02:00
ashley
01b170b36e fix stuff + add stuff 2025-10-01 18:14:10 +02:00
ashley
894d14d8f4 fix stuff + add stuff 2025-10-01 01:45:10 +02:00
PancakeSparkle
df962d5ea1 fixed 2025-10-01 01:43:36 +02:00
PancakeSparkle
c97a74c933 Added VPN switch script 2025-10-01 01:24:02 +02:00
ashley
3091e0ebec Update html/watch.ejs 2025-09-30 22:46:34 +02:00
ashley
d00ba6b102 Update html/watch.ejs 2025-09-30 21:52:38 +02:00
ashley
73e20f970a fix stuff + add stuff 2025-09-30 21:51:40 +02:00
ashley
acfcf1ba53 fix stuff + add stuff 2025-09-30 21:50:50 +02:00
ashley
88a768417d fix stuff + add stuff 2025-09-30 21:49:11 +02:00
ashley
7abf0d0f33 fix stuff + add stuff 2025-09-30 21:48:12 +02:00
ashley
ad88454174 fix stuff + add stuff 2025-09-30 21:45:52 +02:00
ashley
28e8a329aa fix stuff + add stuff 2025-09-30 21:18:37 +02:00
ashley
be2565b175 Update css/player-base-new.js 2025-09-30 21:07:38 +02:00
ashley
4e2b9c766a fix stuff + add stuff 2025-09-30 20:56:03 +02:00
ashley
ca40ab9149 fix stuff + add stuff 2025-09-30 19:45:38 +02:00
ashley
1282007039 fix stuff + add stuff 2025-09-30 19:44:28 +02:00
ashley
8604c8fe5f bump version(s) :3 2025-09-30 19:39:51 +02:00
ashley
743b9977b3 fix stuff + add stuff 2025-09-30 19:39:08 +02:00
ashley
4b11772514 use "getYouTubePlayerInfo" 2025-09-30 16:06:28 +02:00
ashley
33c6c95045 rename function to getYouTubePlayerInfo 2025-09-30 16:06:03 +02:00
ashley
3e0b96a9c6 Update src/libpoketube/libpoketube-youtube-player.js 2025-09-30 10:26:33 +02:00
ashley
a5a1306527 fix stuff + add stuff 2025-09-30 01:37:11 +02:00
ashley
e553d26b11 fix stuff + add stuff 2025-09-29 22:56:23 +02:00
ashley
9e6df9ba72 add X-Robots-Tag 2025-09-29 17:05:08 +02:00
ashley
e7d6aa9cdd disallow /watch from crawlers 2025-09-29 14:52:10 +02:00
ashley
50251e735a use 3500 2025-09-29 13:04:04 +02:00
ashley
32a9f36613 fix stuff + add stuff 2025-09-29 13:02:59 +02:00
ashley
2895d6890b remove unneded stuff 2025-09-29 12:47:51 +02:00
ashley
80d5b4a7db remove unneded stuff 2025-09-29 12:46:19 +02:00
ashley
0cf1a8c88f update stuff 2025-09-29 10:28:23 +02:00
ashley
a3e84dfbd9 fix stuff + add stuff 2025-09-29 09:42:30 +02:00
ashley
8483f3028a fix stuff + add stuff 2025-09-29 09:00:04 +02:00
ashley
036d3a80b8 fix stuff + add stuff 2025-09-29 08:49:10 +02:00
ashley
502af25aa7 fix stuff + add stuff 2025-09-29 08:12:36 +02:00
ashley
a1d19a8616 fix stuff + add stuff 2025-09-29 07:52:41 +02:00
ashley
73b2e4c40e fix stuff + add stuff 2025-09-29 07:49:18 +02:00
ashley
1eceb2ca1d fix stuff + add stuff 2025-09-29 07:29:29 +02:00
ashley
71409df15f fix stuff + add stuff 2025-09-29 07:26:24 +02:00
ashley
0bad881acb fix stuff + add stuff 2025-09-29 07:19:54 +02:00
ashley
485928f0d0 new name! 2025-09-28 22:45:40 +02:00
ashley
0d9c0549e2 change name! 2025-09-28 22:44:55 +02:00
ashley
a85c468b6d fix stuff + add stuff 2025-09-28 20:55:08 +02:00
ashley
59c1b432e3 make it 5000 2025-09-28 20:53:29 +02:00
ashley
7f92f21193 fix stuff + add stuff 2025-09-28 20:15:35 +02:00
ashley
c36eada475 fix stuff + add stuff 2025-09-28 20:13:19 +02:00
ashley
bd4b693f26 fix stuff + add stuff 2025-09-28 20:02:14 +02:00
ashley
e53804c8ad feat(api): add deterministic API selection strategy 2025-09-28 20:01:04 +02:00
ashley
b419fe97f4 fix stuff + add stuff 2025-09-28 18:02:26 +02:00
ashley
ad0d22b7b0 update privacy policy 2025-09-28 17:14:55 +02:00
ashley
be34d425b8 fix stuff + add stuff 2025-09-28 17:10:57 +02:00
ashley
cdbce03236 fix stuff + add stuff 2025-09-28 17:06:42 +02:00
ashley
fd435991d1 fix stuff + add stuff 2025-09-28 17:05:11 +02:00
ashley
956d6938fc add to js 2025-09-28 17:00:19 +02:00
ashley
5a2f072b02 fix stuff + add stuff 2025-09-28 13:13:30 +02:00
ashley
7cd870c9e0 fix stuff + add stuff 2025-09-28 10:43:05 +02:00
ashley
79e55e9224 new badges :D 2025-09-28 02:01:08 +02:00
ashley
d660adb908 Update html/watch.ejs 2025-09-28 01:20:44 +02:00
ashley
c1e5ec698c fix stuff + add stuff 2025-09-28 01:17:34 +02:00
ashley
7a9d6d3c17 fix stuff + add stuff 2025-09-28 00:51:00 +02:00
ashley
234169fc1c test something 2025-09-28 00:48:03 +02:00
ashley
f550a683fb Update html/license.ejs 2025-09-28 00:16:34 +02:00
ashley
5025d1d364 add stuff to license page :3 2025-09-28 00:14:18 +02:00
ashley
5eaa707f53 Update README.md 2025-09-28 00:10:58 +02:00
ashley
31aa78eb45 fix stuff + add stuff 2025-09-28 00:09:16 +02:00
ashley
7b574f8842 add stuff to license page :3 2025-09-28 00:05:24 +02:00
ashley
abec5c424c remove topic stuff lol :p 2025-09-27 23:43:48 +02:00
ashley
3f938de194 fix stuff + add stuff 2025-09-27 22:07:41 +02:00
ashley
846f6286cc bump version(s) :3 2025-09-27 21:45:10 +02:00
ashley
7dabea77d6 use "raw" 2025-09-27 21:44:01 +02:00
ashley
cfa6d97fba bump to 0004de42 2025-09-27 21:35:51 +02:00
ashley
65145c0a16 new web_20250917_22_RC00 2025-09-27 21:33:46 +02:00
ashley
ab977e017e oopsies 2025-09-27 21:18:46 +02:00
ashley
7a575035c4 cool stuff :3 2025-09-27 21:17:44 +02:00
ashley
6957d4872e fix stuff + add stuff 2025-09-27 21:15:58 +02:00
ashley
b1fe5ddb2f fix stuff + add stuff 2025-09-27 21:10:41 +02:00
ashley
b2849b673e oopsies 2025-09-27 21:08:39 +02:00
ashley
fbf754e9d7 test something 2025-09-27 21:06:04 +02:00
ashley
2609fa682c test something 2025-09-27 20:45:56 +02:00
ashley
49688b1fec add fallback 2025-09-27 11:30:39 +02:00
ashley
0d105e9561 fix stuff 2025-09-27 08:23:29 +02:00
ashley
fc3e629541 Update src/libpoketube/init/pages-channel-and-download.js 2025-09-27 08:17:03 +02:00
ashley
db2a611f39 fix this 2025-09-27 01:08:01 +02:00
ashley
ddf9e0dabb fix this issue 2025-09-25 15:10:34 +02:00
ashley
948ca0d967 test smth 2025-09-24 20:27:57 +02:00
ashley
d2f3a12c7c Update html/watch.ejs 2025-09-24 20:17:25 +02:00
ashley
35637da751 Update html/watch.ejs 2025-09-24 20:15:05 +02:00
ashley
b144085a6e Update html/watch.ejs 2025-09-24 20:12:00 +02:00
PancakeSparkle
1ada885fa4 fixed companion :3 2025-09-24 17:32:25 +02:00
PancakeSparkle
1ed511d8df fixed companion :3 2025-09-24 17:27:01 +02:00
PancakeSparkle
b8bd2f702a fixed companion :3 2025-09-24 17:18:39 +02:00
PancakeSparkle
4d190835c9 fixed companion :3 2025-09-24 17:07:08 +02:00
ashley
c43e7120ec Update src/libpoketube/libpoketube-core.js 2025-09-23 22:17:13 +02:00
ashley
2977b1dcf8 fix stuff + add stuff 2025-09-23 11:53:49 +02:00
ashley
595258005b Update html/watch.ejs 2025-09-23 11:51:25 +02:00
ashley
3c92f27f07 add 140 2025-09-23 11:50:29 +02:00
ashley
57c1224930 fix stuff + add stuff 2025-09-23 01:39:02 +02:00
ashley
699b90930a Update html/search.ejs 2025-09-23 01:37:57 +02:00
ashley
7089134f4a Update html/priv.ejs 2025-09-23 01:35:19 +02:00
ashley
9c1ead22cb Update html/landing.ejs 2025-09-23 01:34:28 +02:00
ashley
1af33d548c add poke.webp 2025-09-23 01:33:45 +02:00
ashley
f360ed2eda fix stuff + add stuff 2025-09-23 01:31:55 +02:00
ashley
b66cfbc89f fix stuff + add stuff 2025-09-22 21:58:36 +02:00
ashley
c6a32438a9 oopsies 2025-09-22 21:55:28 +02:00
ashley
68d82e57b9 fix stuff + add stuff 2025-09-22 21:52:29 +02:00
ashley
3608471db6 fix stuff + add stuff 2025-09-22 21:46:18 +02:00
ashley
73c570041c NOOO DONT 2025-09-22 21:14:39 +02:00
ashley
830af4a190 oopsies 2025-09-22 21:13:04 +02:00
ashley
3a50726d82 oopsies 2025-09-22 21:12:44 +02:00
ashley
2ab3c4596a yk the deal 2025-09-22 21:12:12 +02:00
ashley
ed06b3648c fix stuff + add stuff 2025-09-22 21:10:13 +02:00
ashley
c0cef2685c do it for search.ejs too 2025-09-22 21:08:20 +02:00
ashley
beecf4d754 oops 2025-09-22 21:02:36 +02:00
ashley
a624a89f83 add more stuff 2025-09-22 21:01:45 +02:00
ashley
3565f0aeb8 fix stuff + add stuff 2025-09-22 14:49:58 +02:00
ashley
be228996fd fix stuff + add stuff 2025-09-22 14:49:20 +02:00
ashley
ab019d0a49 fix stuff + add stuff 2025-09-22 14:48:54 +02:00
ashley
0cb6a98f27 Update html/watch.ejs 2025-09-22 14:48:18 +02:00
ashley
b68fb383f9 fix stuff + add stuff 2025-09-22 14:47:50 +02:00
ashley
ad45ab84fa fix stuff + add stuff 2025-09-22 14:38:04 +02:00
ashley
55cda55197 fix stuff + add stuff 2025-09-22 14:35:48 +02:00
ashley
12dc469495 fix stuff + add stuff 2025-09-22 14:33:26 +02:00
ashley
8ecdda3d16 fix stuff + add stuff 2025-09-22 14:32:34 +02:00
ashley
5d6600319f Update html/watch.ejs 2025-09-22 14:27:24 +02:00
ashley
5e941ddd9e fix stuff + add stuff 2025-09-22 14:08:43 +02:00
ashley
190ddd2d0d fix stuff + add stuff 2025-09-22 14:04:50 +02:00
ashley
c91c19435e fix stuff + add stuff 2025-09-22 13:56:25 +02:00
ashley
586732f15b retry window is armed ONLY after seeing a 500 or 502 response + format file 2025-09-22 13:21:35 +02:00
ashley
b6684cf08a Update src/libpoketube/libpoketube-core.js 2025-09-22 13:11:04 +02:00
ashley
d5c5e5c8cd test something 2025-09-22 13:08:37 +02:00
ashley
395289b4c9 Update README.md 2025-09-21 20:53:57 +02:00
ashley
3824c0eed9 do **5** seconds 2025-09-21 18:20:16 +02:00
ashley
8ceb1dcbd5 try 10 seconds 2025-09-21 14:45:31 +02:00
ashley
3afa6e879b make splashes one line 2025-09-21 12:51:35 +02:00
ashley
5051204480 use new variable name :3 2025-09-21 11:19:20 +02:00
ashley
dd507a43c6 make it so if total time since first attempt exceeds 25 seconds stops and throw error 2025-09-21 11:10:45 +02:00
ashley
2ae3575c87 retry indefinitely until the server doesn’t return a 500 2025-09-21 11:05:54 +02:00
ashley
c5ca241a4a test something 2025-09-21 11:03:38 +02:00
ashley
7cbcc30769 use proxy 2025-09-21 09:01:15 +02:00
ashley
25892622a8 use yt img 2025-09-21 08:54:26 +02:00
ashley
8ec50e88e7 improve error page :3c 2025-09-21 08:31:47 +02:00
ashley
08a2365a66 make the "open dyslexic mode" use OpenDyslexic3 2025-09-20 19:53:20 +02:00
ashley
bf4047f2bc bump version(s) :3 2025-09-20 19:50:33 +02:00
ashley
29755ebdf9 remove data.poketube.fun, unused 2025-09-20 19:49:14 +02:00
ashley
3e14002583 add alac 2025-09-20 16:42:54 +02:00
ashley
9363bd5142 fix stuff + add stuff 2025-09-20 15:51:18 +02:00
ashley
fa766a8608 add comments to become more easy to read 2025-09-20 13:17:53 +02:00
ashley
55a63bd774 test something 2025-09-19 17:52:13 +02:00
ashley
c70d065e3b test something 2025-09-19 17:47:12 +02:00
ashley
2f0ae48bd9 try this 2025-09-19 17:43:39 +02:00
ashley
a5f42f7806 add skiplanding 2025-09-19 17:29:58 +02:00
ashley
bef6bd224c add stuff 2025-09-19 17:01:24 +02:00
ashley
4644fff509 oopsies 2025-09-19 16:32:41 +02:00
ashley
1142707308 add bg 2025-09-19 16:32:12 +02:00
ashley
880f5d5032 Update html/license.ejs 2025-09-19 16:21:59 +02:00
ashley
2bcd8374eb NEW page for licesnes yay 2025-09-19 16:19:17 +02:00
ashley
2ca6559fe1 remove data.poketube.fun, unused 2025-09-18 19:31:51 +02:00
ashley
2653de796c remove data.poketube.fun, unused 2025-09-18 19:31:19 +02:00
ashley
80d1ca0c0c remove data.poketube.fun, unused 2025-09-18 19:30:45 +02:00
ashley
8d8de49108 remove data.poketube.fun, unused 2025-09-18 19:30:07 +02:00
ashley
3e025abe7c make it white fill 2025-09-18 18:41:35 +02:00
ashley
c2dec669dc stuff cool 2025-09-18 15:35:45 +02:00
ashley
b53f5f78fd fix typo 2025-09-18 15:29:40 +02:00
ashley
e7fe416172 refactor code 2025-09-18 15:18:20 +02:00
ashley
3af48b083a use high quality image 2025-09-18 13:48:53 +02:00
ashley
501859d996 oops 2025-09-18 12:24:51 +02:00
ashley
5dc89c934a oops 2025-09-18 12:23:58 +02:00
ashley
79c9f89ae6 oopises 2025-09-18 12:22:29 +02:00
ashley
ec5a74003a add "open youtube" 2025-09-18 12:20:48 +02:00
ashley
a1bb9a414f test something 2025-09-17 20:39:43 +02:00
ashley
9237f5a947 fix stuff + add stuff 2025-09-17 20:38:17 +02:00
ashley
6d73e50aa4 refactor code 2025-09-17 17:29:16 +02:00
ashley
aa56521e58 fix stuff + add stuff 2025-09-17 17:02:56 +02:00
ashley
bf05ae8b7d fix stuff + add stuff 2025-09-17 17:00:19 +02:00
ashley
c7982b0b34 add a href 2025-09-17 16:57:32 +02:00
ashley
d20caa5e30 new icon for verified 2025-09-17 16:55:52 +02:00
ashley
4816aa5b0e fix stuff + add stuff 2025-09-17 16:36:12 +02:00
ashley
074551d75c test something 2025-09-17 16:35:01 +02:00
ashley
10daef324b add authorVerifiedChecker3000!!!!!!!! 2025-09-17 15:00:38 +02:00
ashley
8f9dccc8a4 test something 2025-09-17 14:49:10 +02:00
ashley
e0c056952d add stuff :3 2025-09-17 00:19:18 +02:00
ashley
6b8e9a2871 Update html/priv.ejs 2025-09-16 23:44:38 +02:00
ashley
0a99e81171 update privacy policy 2025-09-16 23:41:49 +02:00
ashley
e917a7c59e Update html/landing.ejs 2025-09-16 22:26:47 +02:00
ashley
62d3001c83 Upload files to "css" 2025-09-16 22:25:57 +02:00
ashley
7e010df7c3 add v2 2025-09-16 22:00:04 +02:00
ashley
8b252ed7aa add a new landing image 2025-09-16 21:59:18 +02:00
ashley
b6dc4d9538 add "app.use(ieBlockMiddleware);" 2025-09-16 18:51:50 +02:00
ashley
eaa410bd01 create ie blocker 2025-09-16 18:50:06 +02:00
ashley
7d7d056db3 bump version(s) :3 2025-09-16 18:44:35 +02:00
ashley
1cc703ba55 fix internet explorer 2025-09-16 18:44:10 +02:00
ashley
817c0d419d use "•" instead 2025-09-16 14:19:56 +02:00
ashley
3b1ee34d59 add fit-content 2025-09-16 14:16:30 +02:00
ashley
24cfbe107d test 2 2025-09-16 14:13:46 +02:00
ashley
92c0dbfe23 add views test-1 2025-09-16 14:11:35 +02:00
ashley
7cdf12c273 Update html/search.ejs 2025-09-15 14:51:14 +02:00
ashley
2c9082de09 fix stuff + add stuff 2025-09-14 23:27:09 +02:00
ashley
f59cf5a838 Update html/watch.ejs 2025-09-14 23:24:36 +02:00
ashley
2acf57c60f remove autoplay 2025-09-14 23:23:18 +02:00
ashley
6f4394d1f5 bump version :3 2025-09-14 23:19:34 +02:00
ashley
6582d2560f fix stuff + add stuff 2025-09-14 23:19:16 +02:00
ashley
5ef1e6dc31 fix stuff + add stuff 2025-09-14 23:15:35 +02:00
ashley
b2c19b2930 fix comments and desc containers 2025-09-14 23:15:06 +02:00
ashley
2a907ee15d try 8.6em 2025-09-14 23:11:05 +02:00
ashley
bf4759429c bump version :3 2025-09-14 23:05:08 +02:00
ashley
cb9bfcbd42 fix stuff + add stuff 2025-09-14 23:04:40 +02:00
ashley
bd942e2dd7 bump version :3 2025-09-14 23:02:48 +02:00
ashley
0d122f9568 add border radius to the poster 2025-09-14 23:02:16 +02:00
ashley
ecf09f9398 fix stuff + add stuff 2025-09-14 22:41:08 +02:00
ashley
aa0482151d fix stuff + add stuff 2025-09-14 22:39:59 +02:00
ashley
72960b0378 Update html/watch.ejs 2025-09-14 22:36:57 +02:00
ashley
1756ad2dd0 test something 2025-09-14 22:35:52 +02:00
ashley
ddaed918d0 Update html/watch.ejs 2025-09-14 22:32:14 +02:00
ashley
bad78fff5f test something 2025-09-14 22:31:09 +02:00
ashley
4cc8c4b953 test 1qa 2025-09-14 22:28:15 +02:00
ashley
2d1e33f0ae test something 2025-09-14 22:25:57 +02:00
ashley
56ab5bdc16 new readme :3 2025-09-14 13:32:51 +02:00
ashley
db4d1ad422 new image :3 2025-09-14 10:37:30 +02:00
ashley
3703dedce8 add image for poke weather 2025-09-14 10:36:41 +02:00
ashley
f2403e2b9a Update html/layouts/error.ejs 2025-09-14 01:00:35 +02:00
ashley
e28e226ad9 remove light mode 2025-09-14 00:41:41 +02:00
ashley
5f6bc72430 remove this ***for now*** 2025-09-13 17:42:36 +02:00
ashley
4ea335b0dc Update html/account-me.ejs 2025-09-13 13:58:41 +02:00
ashley
cae7815aa7 Update html/account-me.ejs 2025-09-13 13:54:56 +02:00
ashley
d7d408b19e fix stuff + add stuff 2025-09-13 13:48:53 +02:00
ashley
8e2fd7f2d8 fix stuff + add stuff 2025-09-13 13:45:15 +02:00
ashley
0a5954c30d add poketube flex to some places :3 2025-09-13 11:50:06 +02:00
ashley
662c7eea3a add poketube flex 2025-09-13 11:46:25 +02:00
ashley
a133ffd6a6 add poketube flex to some places :3 2025-09-13 11:45:54 +02:00
ashley
b5d1f18043 Update html/weather.ejs 2025-09-13 11:41:16 +02:00
ashley
a210f5ae03 add poketube flex to some places :3 2025-09-13 11:40:09 +02:00
ashley
7c4cb90b0f add poketube flex 2025-09-13 11:30:49 +02:00
ashley
4fc35dd340 make text 40px 2025-09-13 11:01:01 +02:00
ashley
fa91df45f3 bump version :3 2025-09-13 10:05:55 +02:00
ashley
d8e06c1a6d bump version(s) :3 2025-09-13 09:41:43 +02:00
ashley
99916351e0 fix stuff + add stuff 2025-09-13 09:40:50 +02:00
ashley
c2cbd61343 bump version :3 2025-09-13 09:33:42 +02:00
ashley
5ba78b9144 test seek fix 2025-09-13 09:32:43 +02:00
ashley
6daefdf6fc fix stuff + add stuff 2025-09-12 20:33:11 +02:00
ashley
c9e4ab56fb fix 2025-09-12 18:30:40 +02:00
ashley
80c5a87c2a Update html/channel.ejs 2025-09-12 18:28:02 +02:00
ashley
2a8de8f011 Update html/channel.ejs 2025-09-12 18:23:46 +02:00
ashley
01a1a98a0f Update html/channel.ejs 2025-09-12 18:21:03 +02:00
ashley
6517b30db6 test smth 2025-09-12 18:19:12 +02:00
ashley
2bfbd7c6c8 oops 2025-09-12 18:14:27 +02:00
ashley
c52eae7900 test something 2025-09-12 18:12:49 +02:00
ashley
6ee4a7a9df add released 2025-09-12 18:09:37 +02:00
ashley
048040615a fix stuff + add stuff 2025-09-12 16:51:14 +02:00
ashley
4c6ab31a4d Upload files to "css" 2025-09-12 16:48:19 +02:00
ashley
15749eba44 oopises 2025-09-12 16:32:33 +02:00
ashley
2bab9c48ef add footer privacy 2025-09-12 16:29:58 +02:00
ashley
2a6a39c7b4 oops 2025-09-12 13:34:49 +02:00
ashley
00d98ff6c8 fix: fix Cannot read properties of null (reading 'split') 2025-09-12 13:33:57 +02:00
ashley
15f67b076d Update html/playlist.ejs 2025-09-12 10:56:37 +02:00
ashley
62830f269a Update html/playlist.ejs 2025-09-12 10:53:50 +02:00
ashley
2e449e5cc8 test something 2025-09-12 10:52:26 +02:00
ashley
5ad5090bfe fix stuff + add stuff 2025-09-11 17:07:11 +02:00
ashley
b90ba6d9cf Update html/watch.ejs 2025-09-11 17:00:32 +02:00
ashley
f360167b80 ooopps 2025-09-11 16:57:35 +02:00
ashley
e07ba004b1 add req.hostname 2025-09-11 16:56:10 +02:00
ashley
fbe8ee22b2 oops 2025-09-11 14:27:25 +02:00
ashley
6fa54773d7 Update html/priv.ejs 2025-09-11 14:26:26 +02:00
ashley
eb5a2d174d fix stuff + add stuff 2025-09-11 14:23:30 +02:00
ashley
ca1ebebe5a Update html/priv.ejs 2025-09-11 13:51:07 +02:00
ashley
6877c66605 Update html/priv.ejs 2025-09-11 13:45:35 +02:00
ashley
eefed523ff Update html/priv.ejs 2025-09-11 13:30:59 +02:00
ashley
42328e7aa1 Update html/priv.ejs 2025-09-11 13:11:21 +02:00
ashley
f09f929613 Update html/priv.ejs 2025-09-11 13:10:35 +02:00
ashley
a2bfc99e77 Update package.json 2025-09-11 11:49:24 +02:00
ashley
e363d293dc bump version(s) :3 2025-09-11 08:44:59 +02:00
ashley
9d613b8883 bump version(s) :3 2025-09-11 08:43:25 +02:00
ashley
da53df9dde bump version(s) :3 2025-09-11 08:41:05 +02:00
ashley
dc81841790 bump version(s) :3 2025-09-11 08:38:15 +02:00
ashley
0c1440524e Update html/channel.ejs 2025-09-10 01:27:11 +02:00
ashley
f83c5cee03 test something 2025-09-10 01:25:43 +02:00
ashley
03a2fca93a Update html/channel.ejs 2025-09-10 01:21:57 +02:00
ashley
c0245c2912 Update html/channel.ejs 2025-09-10 01:19:51 +02:00
ashley
b12f2b483b Update html/channel.ejs 2025-09-10 00:53:04 +02:00
ashley
48c4150c85 Update html/channel.ejs 2025-09-10 00:52:06 +02:00
ashley
7172e95c13 oopsie 2025-09-10 00:50:26 +02:00
ashley
6449d7c66a oopsie 2025-09-10 00:48:12 +02:00
ashley
9d52b7da95 fix stuff + add stuff 2025-09-10 00:46:45 +02:00
ashley
56fb40f525 fix stuff + add stuff 2025-09-10 00:40:42 +02:00
ashley
0903ac9512 Update html/watch.ejs 2025-09-10 00:36:24 +02:00
ashley
9d28b93b68 Update html/search.ejs 2025-09-10 00:07:24 +02:00
ashley
d5eb213d07 oops 2025-09-09 23:12:35 +02:00
ashley
9fcaec41ec cool stuff :3 2025-09-09 20:48:33 +02:00
ashley
3d7c6829ee bump version :3 2025-09-09 20:07:30 +02:00
ashley
44dc54e065 Update css/player-base-new.js 2025-09-09 20:06:48 +02:00
ashley
b7a690ca56 update stuff 2025-09-09 20:04:15 +02:00
ashley
d8afbe80e0 Update html/calendar.ejs 2025-09-09 17:36:01 +02:00
ashley
b736710ff0 test something 2025-09-09 12:13:23 +02:00
ashley
6ea7cb88c5 , 2025-09-09 11:56:23 +02:00
ashley
55af8b70b6 Upload files to "backend-services/scripts" 2025-09-09 11:49:21 +02:00
ashley
6990346014 add ip change 2025-09-09 11:48:53 +02:00
ashley
26a282d9e7 Update html/watch.ejs 2025-09-08 23:22:49 +02:00
ashley
7ad6de7f01 Update html/watch.ejs 2025-09-08 23:21:38 +02:00
ashley
a50a713ebf test something 2025-09-08 23:18:06 +02:00
ashley
9f92da5ed2 Update src/libpoketube/init/pages-video.js 2025-09-08 23:10:05 +02:00
ashley
6e1d0b6b27 test something 2025-09-08 22:54:27 +02:00
ashley
272f104fc5 Upload files to "css" 2025-09-08 22:51:52 +02:00
ashley
13d6879760 Update html/channel.ejs 2025-09-08 16:16:04 +02:00
ashley
6476cb1dcd Update html/channel.ejs 2025-09-08 16:14:51 +02:00
ashley
4da6fef429 new ui! 2025-09-08 16:11:58 +02:00
ashley
0a75113a5f fix mobile banners finaly! 2025-09-08 16:03:58 +02:00
ashley
1b6a4eaf1c Update html/translate.ejs 2025-09-07 15:25:31 +02:00
ashley
f91f9891ff make button nojs 2025-09-07 15:23:47 +02:00
ashley
7df44c37fd quality of life changes 2025-09-07 15:21:34 +02:00
ashley
1d619df1d5 Update css/player-base-new.js 2025-09-06 12:04:08 +02:00
ashley
54404d15a0 add stuff 2025-09-06 09:24:56 +02:00
ashley
d444a74b0e Update html/watch.ejs 2025-09-06 04:30:13 +02:00
ashley
7b6319927b add stuff 2025-09-05 20:55:33 +02:00
ashley
5c2f2caaf7 move stuff 2025-09-05 20:39:08 +02:00
ashley
459f17396b fix this finally 2025-09-04 14:37:12 +02:00
ashley
50f8d288a4 test something 2025-09-03 23:44:01 +02:00
ashley
423ffc3a1c update error message 2025-09-03 23:42:54 +02:00
ashley
502844f9dd add video ids 2025-09-03 23:41:01 +02:00
ashley
804ee9c820 Update html/studio-landing.ejs 2025-09-02 22:56:05 +02:00
ashley
c3e0ef9f22 Update src/libpoketube/init/pages-static.js 2025-09-02 22:53:30 +02:00
ashley
d9fbe7ffb4 Add html/strings.ejs 2025-09-02 22:52:54 +02:00
ashley
4b80e831fd Update html/drums.ejs 2025-09-02 22:49:10 +02:00
ashley
2397daed7a Update src/libpoketube/init/pages-static.js 2025-09-02 22:48:32 +02:00
ashley
71d94c9095 Add html/drums.ejs 2025-09-02 22:48:02 +02:00
ashley
c60e7d4e1c Add html/studio-landing.ejs 2025-09-02 22:44:22 +02:00
ashley
4debc92509 Update src/libpoketube/init/pages-static.js 2025-09-02 22:42:55 +02:00
ashley
4d614c7134 Update src/libpoketube/init/pages-static.js 2025-09-02 22:37:03 +02:00
ashley
0eb257e9d7 Update src/libpoketube/init/pages-static.js 2025-09-02 14:50:19 +02:00
ashley
3122d32767 poke piano test 2025-09-02 14:49:37 +02:00
ashley
e39a34ff01 new error page :3 2025-09-02 10:34:41 +02:00
ashley
297f10e2a1 Update html/account-me.ejs 2025-09-02 01:13:03 +02:00
ashley
7fbebf2488 Update src/libpoketube/init/pages-account.js 2025-09-02 01:12:42 +02:00
ashley
1a7f6a607b Update html/watch.ejs 2025-09-02 00:58:40 +02:00
ashley
e7d6776a1e Update src/libpoketube/init/pages-account.js 2025-09-02 00:53:01 +02:00
ashley
9825676adc Update src/libpoketube/init/pages-account.js 2025-09-02 00:49:27 +02:00
ashley
e2245ba14f Update html/account-me.ejs 2025-09-02 00:42:31 +02:00
ashley
8df833bfd4 new account page beta 2025-09-02 00:40:33 +02:00
ashley
af7950ae71 Update html/search.ejs 2025-09-02 00:18:54 +02:00
ashley
983f0bee1f cool stuff :3 2025-09-01 22:30:15 +02:00
ashley
828bace6d2 Update html/watch.ejs 2025-09-01 20:25:42 +02:00
ashley
e47f64dfee Update html/watch.ejs 2025-09-01 20:17:38 +02:00
ashley
f99d88c225 Update html/watch.ejs 2025-09-01 20:11:19 +02:00
ashley
d4470f5fa2 test code 2025-09-01 20:09:34 +02:00
ashley
cc1a3f4188 remove unused definitions 2025-09-01 19:45:57 +02:00
ashley
83dbb489fe Update html/watch.ejs 2025-09-01 10:57:43 +02:00
ashley
e36cf4da2d Update html/watch.ejs 2025-09-01 10:56:58 +02:00
ashley
1acff6b4fd cool stuff :3 2025-08-31 22:45:10 +02:00
ashley
eeae838e71 Update html/watch.ejs 2025-08-31 22:41:39 +02:00
ashley
71354e1468 Update html/watch.ejs 2025-08-31 22:36:31 +02:00
ashley
0b4f8c869f Update src/libpoketube/init/pages-static.js 2025-08-31 20:11:00 +02:00
ashley
9ae65b2a5c Upload files to "css" 2025-08-31 20:10:39 +02:00
ashley
fa24e508cf Update html/landing.ejs 2025-08-31 19:56:58 +02:00
ashley
ffd263e7e3 Update html/landing.ejs 2025-08-31 19:55:07 +02:00
ashley
11849e059e Update css/landing.css 2025-08-31 19:54:27 +02:00
ashley
042360d89f Update html/video-error.ejs 2025-08-31 19:47:00 +02:00
ashley
41b7f587f0 add error reasons :3c 2025-08-31 19:46:37 +02:00
ashley
7efa6abaf5 Update html/landing.ejs 2025-08-31 19:45:02 +02:00
ashley
b5206d5d5c Update html/watch.ejs 2025-08-31 16:53:57 +02:00
ashley
463d4d1d8a update stuff 2025-08-31 16:50:57 +02:00
ashley
ca85379194 Update html/watch.ejs 2025-08-31 16:22:24 +02:00
ashley
e484462c97 Update html/watch.ejs 2025-08-31 16:20:51 +02:00
ashley
85a91ab602 cool stuff :3 2025-08-31 12:49:32 +02:00
ashley
799eb8f9bc Update html/watch.ejs 2025-08-31 12:46:28 +02:00
ashley
ccd2fec8ba Update README.md 2025-08-30 23:30:48 +02:00
ashley
18cf68ed7d use 512x512 2025-08-29 18:34:34 +02:00
ashley
4a9273fcdf Update html/channel.ejs 2025-08-29 18:27:45 +02:00
ashley
e0329676f1 add channelurlfixer to channels's posts 2025-08-29 18:25:19 +02:00
ashley
bd5d8d5f90 add channelurlfixer to channels 2025-08-29 18:23:23 +02:00
ashley
2d7b1ab443 yipee 2025-08-29 18:21:26 +02:00
ashley
9b81a99938 Update html/weather.ejs 2025-08-29 15:01:38 +02:00
ashley
07fc714f50 Update html/weather.ejs 2025-08-29 14:57:44 +02:00
ashley
de7f5ef68d Update html/gamehub.ejs 2025-08-29 13:39:40 +02:00
ashley
7de0758d9b Update html/discover.ejs 2025-08-29 12:43:38 +02:00
ashley
ec656b7cc3 cool stuff :3 2025-08-29 12:43:17 +02:00
ashley
23dce5c4ba copyright updates :3 2025-08-29 12:42:41 +02:00
ashley
cccad8756d update :3 2025-08-29 12:41:39 +02:00
ashley
28f8614ef1 copyright updates 2025-08-29 12:40:50 +02:00
ashley
37b2f2af21 Update html/watch.ejs 2025-08-29 12:32:40 +02:00
ashley
4a6ab5b580 cool stuff :3 2025-08-29 12:30:38 +02:00
ashley
39bc5d5692 Update html/watch.ejs 2025-08-29 01:16:38 +02:00
ashley
390497aade Update html/watch.ejs 2025-08-29 01:14:28 +02:00
ashley
d375ec9ed0 Update html/watch.ejs 2025-08-29 00:38:28 +02:00
ashley
c1b3e82a95 Update html/watch.ejs 2025-08-29 00:32:38 +02:00
ashley
98d5e90bad Update html/watch.ejs 2025-08-29 00:26:07 +02:00
ashley
8a86cf3393 Update html/watch.ejs 2025-08-29 00:22:00 +02:00
ashley
357e54bcd3 Update html/watch.ejs 2025-08-28 23:44:05 +02:00
ashley
a8da331976 Update css/player-base-new.js 2025-08-28 23:43:32 +02:00
ashley
1688e61513 Update html/watch.ejs 2025-08-28 23:37:25 +02:00
ashley
bf3271c8dc Update html/watch.ejs 2025-08-28 23:36:19 +02:00
ashley
cf4186050f Update src/libpoketube/init/pages-static.js 2025-08-28 23:35:40 +02:00
ashley
ca1df111ac Update html/watch.ejs 2025-08-28 23:33:49 +02:00
ashley
f1f5e474b2 Update css/player-base-new.js 2025-08-28 23:33:29 +02:00
ashley
6e62f71516 Update html/watch.ejs 2025-08-28 23:32:13 +02:00
ashley
aee9da5c86 Update css/player-base-new.js 2025-08-28 23:30:40 +02:00
ashley
385c8a1486 Update html/watch.ejs 2025-08-28 23:28:51 +02:00
ashley
21367d1298 Update css/player-base-new.js 2025-08-28 23:28:28 +02:00
ashley
d2a330ba41 Update html/watch.ejs 2025-08-28 23:22:27 +02:00
ashley
b26c5f7560 Update src/libpoketube/init/pages-static.js 2025-08-28 23:21:08 +02:00
ashley
b574fc49a4 Update html/watch.ejs 2025-08-28 23:19:00 +02:00
ashley
a5fb5394ca Update css/player-base-new.js 2025-08-28 23:17:08 +02:00
ashley
486a7df998 Update package.json 2025-08-28 23:10:11 +02:00
ashley
619a911857 Update src/libpoketube/init/pages-static.js 2025-08-28 23:06:39 +02:00
ashley
e082f5515c Update html/watch.ejs 2025-08-28 23:00:07 +02:00
ashley
b8f1ee0d9b Update css/player-base-new.js 2025-08-28 22:59:45 +02:00
ashley
5a2e2cd510 Update html/watch.ejs 2025-08-28 22:54:09 +02:00
ashley
aef7f18b6f Update src/libpoketube/init/pages-static.js 2025-08-28 22:51:40 +02:00
ashley
4a98e9d31e Update css/player-base-new.js 2025-08-28 22:50:14 +02:00
ashley
1e4744a010 Update css/player-base-new.js 2025-08-28 22:45:09 +02:00
ashley
4297b22ed8 Update html/watch.ejs 2025-08-28 22:42:28 +02:00
ashley
1f492cdeaa Update css/player-base-new.js 2025-08-28 22:41:51 +02:00
ashley
5422a44a95 Update html/watch.ejs 2025-08-28 22:38:34 +02:00
ashley
498cdd1dcc Update css/player-base.js 2025-08-28 22:37:58 +02:00
ashley
1f8822f4e2 revert: Dash video
DASH video was way too buggy, so i am removing it for now :D
2025-08-28 22:21:56 +02:00
ashley
0d8650a55b Update css/player-base.js 2025-08-28 22:20:23 +02:00
ashley
58c0b84f11 Update html/watch.ejs 2025-08-28 22:17:14 +02:00
ashley
4170d7cd56 Update html/watch.ejs 2025-08-28 22:15:57 +02:00
ashley
e90975fd8d Update css/player-base.js 2025-08-28 22:15:03 +02:00
ashley
d92a7c281b Update html/watch.ejs 2025-08-28 22:14:28 +02:00
ashley
10649f70af Update src/libpoketube/libpoketube-core.js 2025-08-28 00:40:59 +02:00
ashley
facc69767f Update html/watch.ejs 2025-08-28 00:23:16 +02:00
ashley
9e8f6aa33f Update src/libpoketube/init/pages-api.js 2025-08-28 00:18:39 +02:00
ashley
bf68ec0382 Update html/watch.ejs 2025-08-27 23:36:24 +02:00
ashley
342291e6e6 Update css/player-base.js 2025-08-27 23:36:03 +02:00
ashley
0d67a6367d Update html/watch.ejs 2025-08-27 23:22:05 +02:00
ashley
e1ebe5bb2b Update css/player-base.js 2025-08-27 23:21:02 +02:00
ashley
c5dc6821e2 Update html/watch.ejs 2025-08-27 23:12:54 +02:00
ashley
95bfb4a141 Update css/player-base.js 2025-08-27 23:12:28 +02:00
ashley
8f2edef6b2 Update html/watch.ejs 2025-08-27 22:30:51 +02:00
ashley
103699e708 Update css/player-base.js 2025-08-27 22:29:00 +02:00
ashley
88f5ddd821 Update html/watch.ejs 2025-08-27 14:26:27 +02:00
ashley
aca3737ba0 Update css/player-base.js 2025-08-27 14:26:03 +02:00
ashley
2d8a946fde Update html/watch.ejs 2025-08-27 14:15:03 +02:00
ashley
e65e0f9e44 Update css/player-base.js 2025-08-27 14:14:30 +02:00
ashley
0f7874eec2 Update html/watch.ejs 2025-08-27 14:07:09 +02:00
ashley
bf2532ee48 Update css/player-base.js 2025-08-27 14:06:10 +02:00
ashley
cd49fb9954 Update html/watch.ejs 2025-08-27 14:00:57 +02:00
ashley
9c2f39455f Update css/player-base.js 2025-08-27 14:00:22 +02:00
ashley
9774115145 Update html/watch.ejs 2025-08-27 10:24:22 +02:00
ashley
cde0ce7059 Update html/watch.ejs 2025-08-27 10:13:47 +02:00
ashley
6061d37aac Update html/watch.ejs 2025-08-27 10:09:34 +02:00
ashley
54109ffbf9 Update css/player-base.js 2025-08-27 10:09:04 +02:00
ashley
da13d5b769 Update html/watch.ejs 2025-08-27 10:01:19 +02:00
ashley
06224ef8ba Update css/player-base.js 2025-08-27 10:00:59 +02:00
ashley
de7000e262 Update html/watch.ejs 2025-08-27 09:57:12 +02:00
ashley
65d0aad136 Update css/player-base.js 2025-08-27 09:56:20 +02:00
ashley
e0367aaafe Update html/watch.ejs 2025-08-27 09:55:52 +02:00
ashley
cef99dd660 Update html/watch.ejs 2025-08-27 09:53:43 +02:00
ashley
2df9e7a874 Update css/player-base.js 2025-08-27 09:53:24 +02:00
ashley
aca9dbec06 Update html/watch.ejs 2025-08-27 09:52:38 +02:00
ashley
6a1a9ce11e Update html/watch.ejs 2025-08-27 09:46:16 +02:00
ashley
889cdc3f3b Update css/player-base.js 2025-08-27 09:45:42 +02:00
ashley
15120a02df Update html/watch.ejs 2025-08-27 09:39:37 +02:00
ashley
4a71b24daa Update html/watch.ejs 2025-08-27 09:35:46 +02:00
ashley
713c96b99d Update html/watch.ejs 2025-08-27 09:31:55 +02:00
ashley
7811139ce5 Update html/watch.ejs 2025-08-27 09:29:14 +02:00
ashley
ba79a61902 Update html/watch.ejs 2025-08-27 09:28:04 +02:00
ashley
b06f34929f Update html/watch.ejs 2025-08-27 09:25:24 +02:00
ashley
4c9f17f6ed Update html/watch.ejs 2025-08-27 09:23:03 +02:00
ashley
bc7dcb4c58 Update html/watch.ejs 2025-08-27 09:21:22 +02:00
ashley
3ee33ab166 Update css/player-base.js 2025-08-27 09:21:05 +02:00
ashley
5ec27472a3 Update html/watch.ejs 2025-08-27 09:18:10 +02:00
ashley
d986f3e379 Update html/watch.ejs 2025-08-26 22:13:26 +02:00
ashley
942dc4552d Update css/player-base.js 2025-08-26 22:12:37 +02:00
ashley
1965fb47a5 Update html/watch.ejs 2025-08-26 21:51:43 +02:00
ashley
ab2b6e913d Update css/player-base.js 2025-08-26 21:51:06 +02:00
ashley
9f642a2416 Update src/libpoketube/libpoketube-core.js 2025-08-26 21:26:15 +02:00
ashley
d8df2bd0a6 Update html/watch.ejs 2025-08-26 18:53:15 +02:00
ashley
cceae65fcd Update css/player-base.js 2025-08-26 18:52:18 +02:00
ashley
f2894e10ed Update html/watch.ejs 2025-08-26 18:45:04 +02:00
ashley
8584703926 Update css/player-base.js 2025-08-26 18:44:36 +02:00
ashley
1378520d28 Update html/watch.ejs 2025-08-26 18:33:03 +02:00
ashley
044f27a92c Update css/player-base.js 2025-08-26 18:32:00 +02:00
ashley
7d1d94efa1 Update html/watch.ejs 2025-08-26 18:24:30 +02:00
ashley
8cbb5b645d Update css/player-base.js 2025-08-26 18:24:05 +02:00
ashley
1c51189782 Update html/watch.ejs 2025-08-26 18:18:05 +02:00
ashley
b0407a6bbd Update css/player-base.js 2025-08-26 18:17:41 +02:00
ashley
110db2970f Update html/watch.ejs 2025-08-26 18:12:42 +02:00
ashley
d0c829360e Update css/player-base.js 2025-08-26 18:12:24 +02:00
ashley
986c068c42 Update html/watch.ejs 2025-08-26 18:07:57 +02:00
ashley
74dcb64871 Update css/player-base.js 2025-08-26 18:07:37 +02:00
ashley
eceeb15ed7 Update html/watch.ejs 2025-08-26 18:01:55 +02:00
ashley
8591592d23 Update css/player-base.js 2025-08-26 18:01:23 +02:00
ashley
218d8d36fc Update html/watch.ejs 2025-08-26 17:48:43 +02:00
ashley
bf1efba130 Update css/player-base.js 2025-08-26 17:47:32 +02:00
ashley
1b94a8286e Update html/watch.ejs 2025-08-26 17:34:10 +02:00
ashley
70454737c7 Update css/player-base.js 2025-08-26 17:33:43 +02:00
ashley
1c9da2147c bump version :3 2025-08-26 17:19:34 +02:00
ashley
0bbf17ac0a make videojs retry if network fails :3 2025-08-26 17:18:30 +02:00
ashley
dac433c630 remove splashes from herer 2025-08-26 17:10:30 +02:00
ashley
bec75444f0 Update src/libpoketube/init/pages-404-and-main.js 2025-08-26 14:17:39 +02:00
ashley
2f2f736f22 Update src/libpoketube/init/pages-video.js 2025-08-25 17:58:25 +02:00
ashley
b0b3f3212e Update src/libpoketube/init/pages-video.js 2025-08-25 17:57:22 +02:00
ashley
e13e9ec7ce Update src/libpoketube/init/pages-video.js 2025-08-25 17:49:46 +02:00
ashley
f132b158b3 Update html/lite.ejs 2025-08-25 17:43:47 +02:00
ashley
85517a9a42 Update src/libpoketube/init/pages-video.js 2025-08-25 17:38:14 +02:00
ashley
c15070bb19 Update src/libpoketube/init/pages-video.js 2025-08-25 17:35:21 +02:00
ashley
0dd11da061 Update html/weather.ejs 2025-08-24 13:07:33 +02:00
ashley
b30bae5482 Update src/libpoketube/init/pages-api.js 2025-08-24 13:06:55 +02:00
ashley
a3b98b4d0a Update html/weather.ejs 2025-08-24 13:00:53 +02:00
ashley
17543ab9e8 oops 2025-08-24 12:40:10 +02:00
ashley
1b28dfb9ec Update html/weather.ejs 2025-08-24 12:38:53 +02:00
ashley
7133ea5ec1 Update html/weather.ejs 2025-08-24 12:36:55 +02:00
ashley
7edfdbe081 Update html/weather.ejs 2025-08-24 12:25:25 +02:00
ashley
4d0de8a748 Update html/weather.ejs 2025-08-24 12:24:15 +02:00
ashley
56c02e58b2 Update src/libpoketube/init/pages-static.js 2025-08-24 12:14:06 +02:00
ashley
256112ec03 Add html/weather.ejs 2025-08-24 12:13:04 +02:00
ashley
950c5e64bf Update html/watch.ejs 2025-08-24 01:13:25 +02:00
ashley
e48507b392 oops 2025-08-24 01:12:07 +02:00
ashley
7681a0117d Update html/watch.ejs 2025-08-24 01:09:08 +02:00
ashley
587a8a1b5c remove topic stuff lol :p 2025-08-24 00:13:59 +02:00
ashley
37e33657e9 Update html/landing.ejs 2025-08-23 19:22:08 +02:00
ashley
a407984de0 Update html/landing.ejs 2025-08-23 19:20:34 +02:00
ashley
a1167c66eb Update html/landing.ejs 2025-08-23 19:15:57 +02:00
ashley
2b991500d5 Update html/gamehub.ejs 2025-08-22 04:22:30 +02:00
ashley
a1fe0a19e2 add error reasons :3c 2025-08-21 11:12:46 +02:00
ashley
383c8612a7 Update html/search.ejs 2025-08-20 16:58:36 +02:00
ashley
b587a6fbb1 Update src/libpoketube/init/pages-video.js 2025-08-20 14:23:50 +02:00
ashley
f1f7fae7dc Update html/watch.ejs 2025-08-20 14:22:32 +02:00
ashley
e0392daf63 inv_vid --> VideoObject 2025-08-20 14:20:52 +02:00
ashley
9a618e29d0 Update html/download.ejs 2025-08-20 02:31:36 +02:00
ashley
53f2bd405c Update html/download.ejs 2025-08-20 02:29:58 +02:00
ashley
5ceec021d5 Update html/download.ejs 2025-08-20 02:26:20 +02:00
ashley
ea390a8d49 Update html/download.ejs 2025-08-20 02:20:29 +02:00
ashley
dfd1203125 Update html/download.ejs 2025-08-20 02:16:47 +02:00
ashley
c991c4e008 Update html/download.ejs 2025-08-20 02:12:47 +02:00
ashley
de46021a6d Update html/download.ejs 2025-08-20 02:10:56 +02:00
ashley
5997215742 Update html/download.ejs 2025-08-20 02:03:13 +02:00
ashley
dde204cf2a Update html/download.ejs 2025-08-20 02:02:35 +02:00
ashley
1dec09f23b Update html/download.ejs 2025-08-20 02:00:17 +02:00
ashley
9124c3a72d Update html/download.ejs 2025-08-20 01:57:42 +02:00
ashley
5f75623c8a Update html/download.ejs 2025-08-20 01:56:51 +02:00
ashley
9cada8a4ff Update html/download.ejs 2025-08-20 01:55:37 +02:00
ashley
e42e769c4f Update html/download.ejs 2025-08-20 01:53:25 +02:00
ashley
f908c21953 Update html/channel.ejs 2025-08-20 01:45:53 +02:00
ashley
6694fc8c0f Update html/channel.ejs 2025-08-20 01:44:15 +02:00
ashley
a2a704be8b Update html/channel.ejs 2025-08-20 01:42:09 +02:00
ashley
46f1480f17 Update html/channel.ejs 2025-08-20 01:41:11 +02:00
ashley
fbe8506aba Update html/channel.ejs 2025-08-20 01:39:05 +02:00
ashley
3add530c96 Update html/search.ejs 2025-08-20 01:05:53 +02:00
ashley
eba1530877 Update html/search.ejs 2025-08-20 01:02:36 +02:00
ashley
c4a01628d3 Update html/search.ejs 2025-08-20 00:56:43 +02:00
ashley
ef5d590139 Update html/search.ejs 2025-08-20 00:53:41 +02:00
ashley
f515e455b0 Update html/apps.ejs 2025-08-20 00:44:08 +02:00
ashley
62b43a92c6 Update html/apps.ejs 2025-08-20 00:41:46 +02:00
ashley
ce02d453f0 Update html/apps.ejs 2025-08-20 00:39:14 +02:00
ashley
9cfad3499e Update html/channel.ejs 2025-08-20 00:07:19 +02:00
ashley
5d41790bd8 Update html/channel.ejs 2025-08-20 00:06:36 +02:00
ashley
9f0a23882b Update html/channel.ejs 2025-08-20 00:05:01 +02:00
ashley
dfe9b5539d Update src/libpoketube/libpoketube-core.js 2025-08-19 23:01:21 +02:00
ashley
76db6382bd Update html/watch.ejs 2025-08-19 22:56:04 +02:00
ashley
6873975799 Update html/watch.ejs 2025-08-19 22:54:31 +02:00
ashley
1ca2a85fb8 Update html/watch.ejs 2025-08-19 22:52:31 +02:00
ashley
af6ddddd81 Update html/watch.ejs 2025-08-19 22:51:54 +02:00
ashley
0d3b378632 Update html/map.ejs 2025-08-19 19:04:07 +02:00
ashley
cf4c152c94 Update html/map.ejs 2025-08-19 18:38:52 +02:00
ashley
39541bb05f Update html/map.ejs 2025-08-19 18:37:43 +02:00
ashley
ef5eaa962b Update html/map.ejs 2025-08-19 18:31:45 +02:00
ashley
c560f7f234 Update html/map.ejs 2025-08-19 18:10:49 +02:00
ashley
f2e52a766d Update html/custom-css.ejs 2025-08-19 14:18:03 +02:00
ashley
69f957ec6c Update html/custom-css.ejs 2025-08-19 14:17:15 +02:00
ashley
9bc742d12d Update html/custom-css.ejs 2025-08-19 14:15:34 +02:00
ashley
42b45b406a Update html/custom-css.ejs 2025-08-19 14:14:30 +02:00
ashley
b760411d73 Update html/custom-css.ejs 2025-08-19 14:13:33 +02:00
ashley
13cca17991 Update html/custom-css.ejs 2025-08-19 14:12:20 +02:00
ashley
e5eda77327 Update html/custom-css.ejs 2025-08-19 14:11:36 +02:00
ashley
8d0300ae59 Update html/custom-css.ejs 2025-08-19 14:10:41 +02:00
ashley
4779ca32e5 Update html/custom-css.ejs 2025-08-19 14:08:52 +02:00
ashley
9e566a12e0 Update html/custom-css.ejs 2025-08-19 14:07:42 +02:00
ashley
f12fb58a3d Update html/custom-css.ejs 2025-08-19 13:57:20 +02:00
ashley
81345f1965 Update html/custom-css.ejs 2025-08-19 13:48:23 +02:00
ashley
7f7095e790 Update html/custom-css.ejs 2025-08-19 13:45:00 +02:00
ashley
7320c0c970 Update html/custom-css.ejs 2025-08-19 13:38:44 +02:00
ashley
7861dca5c0 Update html/custom-css.ejs 2025-08-19 13:28:15 +02:00
ashley
7d90dd01e9 Update html/custom-css.ejs 2025-08-19 13:20:44 +02:00
ashley
81434628a0 Update html/custom-css.ejs 2025-08-19 13:18:32 +02:00
ashley
2e186b0d25 Update html/custom-css.ejs 2025-08-19 13:16:48 +02:00
ashley
bea7a079a6 Update html/custom-css.ejs 2025-08-19 13:11:51 +02:00
ashley
bbac68de4a Update html/custom-css.ejs 2025-08-19 13:02:49 +02:00
ashley
0090429b21 Update html/custom-css.ejs 2025-08-19 12:48:25 +02:00
ashley
d1a51ba96b Update html/priv.ejs 2025-08-19 12:48:04 +02:00
ashley
0434fe6dae Update html/priv.ejs 2025-08-19 12:46:45 +02:00
ashley
a10ef76691 Update html/custom-css.ejs 2025-08-19 12:15:45 +02:00
ashley
aedf07a3bd Update html/priv.ejs 2025-08-19 12:06:22 +02:00
ashley
e555638370 Update html/priv.ejs 2025-08-19 04:17:59 +02:00
ashley
b3bb607a74 Update html/priv.ejs 2025-08-19 04:16:57 +02:00
ashley
de9c0ee6f1 Update html/priv.ejs 2025-08-19 04:14:14 +02:00
ashley
a2c4156a9b Update html/priv.ejs 2025-08-19 04:10:10 +02:00
ashley
24a5f7be40 Update html/priv.ejs 2025-08-19 04:06:41 +02:00
ashley
25a7f482bc Update html/priv.ejs 2025-08-19 03:59:48 +02:00
ashley
4bd8f964b1 Update html/priv.ejs 2025-08-19 03:57:09 +02:00
ashley
0963d92306 Update p/server.js 2025-08-19 03:00:47 +02:00
ashley
e30ae6f3fe Update p/server.js 2025-08-19 02:56:20 +02:00
ashley
38f22b2494 Update p/server.js 2025-08-19 02:56:04 +02:00
ashley
69c43bced1 Update html/apps.ejs 2025-08-19 02:39:46 +02:00
ashley
e608b5f269 Update html/apps.ejs 2025-08-19 02:38:56 +02:00
ashley
5883df8a4b Update html/apps.ejs 2025-08-19 02:37:47 +02:00
ashley
fca44c3670 Update html/apps.ejs 2025-08-19 02:23:30 +02:00
ashley
a149a85207 Update html/apps.ejs 2025-08-19 00:59:35 +02:00
ashley
d5b2dca406 Update html/apps.ejs 2025-08-19 00:57:44 +02:00
ashley
443872764e Update html/apps.ejs 2025-08-19 00:54:15 +02:00
ashley
ace29c53ba Update src/libpoketube/init/pages-static.js 2025-08-19 00:52:51 +02:00
ashley
49b50ee4c8 Update html/landing.ejs 2025-08-19 00:27:00 +02:00
ashley
153ffcad02 Upload files to "css" 2025-08-19 00:26:31 +02:00
ashley
90cb5017eb Update html/watch.ejs 2025-08-19 00:19:54 +02:00
ashley
511c593cda Update html/watch.ejs 2025-08-19 00:18:25 +02:00
ashley
8f703aea8c Update html/watch.ejs 2025-08-19 00:16:17 +02:00
ashley
af8405620d Update html/watch.ejs 2025-08-19 00:10:44 +02:00
ashley
3a2318c549 Update src/libpoketube/init/pages-404-and-main.js 2025-08-19 00:04:34 +02:00
ashley
9a5b8d2c11 Update html/partials/header.ejs 2025-08-19 00:02:07 +02:00
ashley
73af1786ce Update html/landing.ejs 2025-08-18 23:56:57 +02:00
ashley
0cb81c8050 Update html/landing.ejs 2025-08-18 23:55:32 +02:00
ashley
ff0ee705e5 Upload files to "css" 2025-08-18 23:54:29 +02:00
ashley
da91457e18 Update html/partials/header.ejs 2025-08-18 23:52:38 +02:00
ashley
0a65df6c2d Upload files to "css" 2025-08-18 23:50:45 +02:00
ashley
eb8e8734d1 Update html/landing.ejs 2025-08-18 16:22:46 +02:00
ashley
cd095d4602 Upload files to "css" 2025-08-18 16:22:12 +02:00
ashley
a444bbb0ff Update html/watch.ejs 2025-08-18 16:16:57 +02:00
ashley
d41185b587 Update CODE_OF_CONDUCT.md 2025-08-18 13:04:10 +02:00
ashley
4d5018c78f Update README.md 2025-08-18 12:47:50 +02:00
ashley
8cb05a498b Update html/landing.ejs 2025-08-18 12:34:01 +02:00
ashley
af27b2a724 Upload files to "css" 2025-08-18 12:33:25 +02:00
ashley
50b71ca6bc Upload files to "css" 2025-08-18 12:30:11 +02:00
ashley
459a6392f5 fix this finally 2025-08-18 12:25:46 +02:00
ashley
e25a9fc749 Update html/discover.ejs 2025-08-18 10:35:14 +02:00
ashley
1d763a4ede Update html/discover.ejs 2025-08-18 10:34:02 +02:00
ashley
9e0edab88a Update html/channel.ejs 2025-08-18 10:32:03 +02:00
ashley
fdb083aa6b Update src/libpoketube/init/pages-channel-and-download.js 2025-08-18 10:29:37 +02:00
ashley
ef626ced8e Update src/libpoketube/init/pages-channel-and-download.js 2025-08-18 10:28:44 +02:00
ashley
5404b866ea Update src/libpoketube/init/pages-channel-and-download.js 2025-08-18 10:27:15 +02:00
ashley
ce0a4beb7b Update src/libpoketube/init/pages-channel-and-download.js 2025-08-18 10:26:25 +02:00
ashley
1de34b0a4d Update src/libpoketube/init/pages-channel-and-download.js 2025-08-18 10:25:35 +02:00
ashley
3918bd53b5 Update src/libpoketube/init/pages-404-and-main.js 2025-08-18 10:21:36 +02:00
ashley
7056a92122 Update src/libpoketube/init/pages-404-and-main.js 2025-08-18 10:19:53 +02:00
ashley
e640fcb6b5 Update html/discover.ejs 2025-08-18 10:19:06 +02:00
ashley
a3231d511d Update src/libpoketube/init/pages-404-and-main.js 2025-08-18 10:18:33 +02:00
ashley
81a26df355 Update src/libpoketube/init/pages-404-and-main.js 2025-08-18 10:14:42 +02:00
ashley
116c58ff66 Update src/libpoketube/init/pages-404-and-main.js 2025-08-18 10:11:52 +02:00
ashley
b7ae014c00 Update src/libpoketube/init/pages-404-and-main.js 2025-08-18 10:10:20 +02:00
ashley
6ab9bd6ab1 Update src/libpoketube/init/pages-404-and-main.js 2025-08-18 10:06:36 +02:00
ashley
4d0e0b102e Update src/libpoketube/init/pages-404-and-main.js 2025-08-18 10:03:20 +02:00
ashley
82d5ba1c3e Update html/discover.ejs 2025-08-18 10:01:24 +02:00
ashley
23904301e6 Update src/libpoketube/init/pages-404-and-main.js 2025-08-18 09:56:46 +02:00
ashley
6fbeea0c06 Update html/watch.ejs 2025-08-18 03:25:15 +02:00
ashley
e138e33c4e Update html/watch.ejs 2025-08-18 03:22:37 +02:00
ashley
7d55ca2b7d Update html/watch.ejs 2025-08-18 03:13:53 +02:00
ashley
8c42fa356d Update html/watch.ejs 2025-08-18 02:53:32 +02:00
ashley
1557d47228 Update html/watch.ejs 2025-08-18 02:49:07 +02:00
ashley
02340e7f03 Update src/libpoketube/init/pages-video.js 2025-08-18 02:41:24 +02:00
ashley
c082234bc7 Update html/watch.ejs 2025-08-18 02:40:20 +02:00
ashley
85ce7ccced Update src/libpoketube/init/pages-video.js 2025-08-18 02:36:23 +02:00
ashley
da1c676e54 Update html/video-error.ejs 2025-08-18 02:34:59 +02:00
ashley
7ea292cba4 Update src/libpoketube/init/pages-video.js 2025-08-18 02:34:28 +02:00
ashley
ad9ae62fe4 Update html/video-error.ejs 2025-08-18 02:33:02 +02:00
ashley
b00796cf31 Update src/libpoketube/libpoketube-core.js 2025-08-18 02:16:39 +02:00
ashley
93277671ed Update src/libpoketube/init/pages-video.js 2025-08-18 02:15:52 +02:00
ashley
a42265b0fb Update html/playlist.ejs 2025-08-18 02:13:28 +02:00
ashley
c1576b73cb Update src/libpoketube/libpoketube-core.js 2025-08-18 02:12:06 +02:00
ashley
1cbb0833f7 Update src/libpoketube/init/pages-404-and-main.js 2025-08-18 02:00:52 +02:00
ashley
c7eaf8b5aa Update html/watch.ejs 2025-08-18 01:48:46 +02:00
ashley
e66b20b25c Update src/libpoketube/libpoketube-core.js 2025-08-18 01:32:34 +02:00
ashley
258130fbea Update src/libpoketube/libpoketube-core.js 2025-08-18 01:29:54 +02:00
ashley
c3796da337 Update src/libpoketube/init/pages-video.js 2025-08-18 01:25:33 +02:00
ashley
664afe5f20 Update src/libpoketube/libpoketube-core.js 2025-08-18 01:25:09 +02:00
ashley
3e552d7bc4 Update src/libpoketube/libpoketube-core.js 2025-08-18 01:18:54 +02:00
ashley
18dae9b530 Update src/libpoketube/libpoketube-core.js 2025-08-18 01:12:18 +02:00
ashley
40f60a8490 Update src/libpoketube/libpoketube-core.js 2025-08-18 01:06:49 +02:00
ashley
4e0f05b856 Update src/libpoketube/libpoketube-core.js 2025-08-18 01:04:58 +02:00
ashley
5af078a9e7 Update src/libpoketube/libpoketube-core.js 2025-08-18 00:56:41 +02:00
ashley
efe486252e Update html/layouts/error-video.ejs 2025-08-18 00:53:44 +02:00
ashley
b207030539 Update html/watch.ejs 2025-08-18 00:44:40 +02:00
ashley
eb0d3c17a3 Update src/libpoketube/libpoketube-core.js 2025-08-18 00:40:48 +02:00
ashley
cf30dc2e8f Update html/layouts/error-video.ejs 2025-08-18 00:39:46 +02:00
ashley
b4623613c7 Update html/video-error.ejs 2025-08-18 00:38:50 +02:00
ashley
c94eeae22e Add why-error.md 2025-08-18 00:37:45 +02:00
ashley
ef4e2aeb2a Update html/watch.ejs 2025-08-18 00:34:31 +02:00
ashley
c750efe193 Update html/watch.ejs 2025-08-18 00:29:32 +02:00
ashley
8f67f12b1f Update html/video-error.ejs 2025-08-18 00:26:44 +02:00
ashley
b7a954a3b3 Update html/map.ejs 2025-08-18 00:02:50 +02:00
ashley
728db38dde Update html/map.ejs 2025-08-17 23:43:59 +02:00
ashley
be18259262 Update html/map.ejs 2025-08-17 23:33:40 +02:00
ashley
e326331cf8 Update html/map.ejs 2025-08-17 22:43:48 +02:00
ashley
f9d5cce9cf Update html/map.ejs 2025-08-17 22:23:08 +02:00
ashley
e25d020f6c Update html/map.ejs 2025-08-17 22:22:10 +02:00
ashley
d6972c6671 Update html/map.ejs 2025-08-17 22:20:14 +02:00
ashley
18a9e170b8 Update html/map.ejs 2025-08-17 22:14:41 +02:00
ashley
c2a9071615 Update html/map.ejs 2025-08-17 22:06:46 +02:00
ashley
928d3573c1 Update html/map.ejs 2025-08-17 21:57:06 +02:00
ashley
b8cdc69d85 Update src/libpoketube/libpoketube-core.js 2025-08-17 21:44:26 +02:00
ashley
3da03fb5f6 Update src/libpoketube/libpoketube-core.js 2025-08-17 21:42:24 +02:00
ashley
9e07cc9989 Update src/libpoketube/libpoketube-core.js 2025-08-17 21:34:17 +02:00
ashley
e676b11410 Update html/map.ejs 2025-08-17 21:19:10 +02:00
ashley
2f332d2e3c Update html/map.ejs 2025-08-17 21:04:37 +02:00
ashley
83ab99d0d7 Update html/map.ejs 2025-08-17 15:21:05 +02:00
ashley
ceb84e0aea Update html/map.ejs 2025-08-17 15:18:34 +02:00
ashley
8b77affd47 Update html/map.ejs 2025-08-17 15:15:56 +02:00
ashley
ef8b7ddf35 Update html/map.ejs 2025-08-17 15:12:31 +02:00
ashley
5cd811369c Update html/map.ejs 2025-08-17 15:11:26 +02:00
ashley
1b50ee4804 Update html/map.ejs 2025-08-17 14:54:45 +02:00
ashley
da3e38947c Update html/map.ejs 2025-08-17 14:19:27 +02:00
ashley
9ae59b52cc Update html/map.ejs
Signed-off-by: ashley <iamashley@duck.com>
2025-08-17 14:04:52 +02:00
ashley
61c77fd978 Update html/map.ejs 2025-08-17 13:47:15 +02:00
ashley
2337cd39b9 Update html/map.ejs 2025-08-17 13:33:38 +02:00
ashley
2b546053a1 Update html/map.ejs 2025-08-17 13:22:33 +02:00
ashley
cb945d12c6 Update html/map.ejs 2025-08-17 13:15:32 +02:00
ashley
daa625e711 Update html/map.ejs 2025-08-17 13:07:30 +02:00
ashley
9f5e9b021e Update html/map.ejs 2025-08-17 13:00:57 +02:00
ashley
d84dee487b Update html/map.ejs 2025-08-17 12:53:17 +02:00
ashley
f7a10cf20b Update html/map.ejs 2025-08-17 12:48:29 +02:00
ashley
43f3fc69a2 Update html/channel.ejs 2025-08-17 00:43:46 +02:00
ashley
8c3b6ce7f9 Update html/channel.ejs 2025-08-17 00:37:54 +02:00
ashley
a752cc63d4 Update html/channel.ejs 2025-08-17 00:36:49 +02:00
ashley
ef780e4eb5 Update html/channel.ejs 2025-08-17 00:35:31 +02:00
ashley
82be53a86a Update html/channel.ejs 2025-08-17 00:25:43 +02:00
ashley
d16b5d7d7f Update html/channel.ejs 2025-08-16 23:23:52 +02:00
ashley
c2aa651b68 Update src/libpoketube/init/pages-static.js 2025-06-03 21:02:56 +02:00
ashley
33cb84b340 Add html/pokepad.ejs 2025-06-03 21:01:16 +02:00
ashley
23cf962230 meta : improve wording in og:description 2025-06-01 14:29:28 +02:00
ashley
2be8727949 Update html/calendar.ejs 2025-05-27 19:20:35 +02:00
ashley
7677d27698 Update html/calendar.ejs 2025-05-27 19:17:03 +02:00
ashley
0fdb869af7 Update html/watch.ejs 2025-05-27 18:30:18 +02:00
ashley
7ee79d8013 Update html/watch.ejs 2025-05-27 18:25:53 +02:00
ashley
79804c063c Update html/watch.ejs 2025-05-27 18:24:12 +02:00
ashley
98d5df7a96 Update html/watch.ejs 2025-05-27 18:19:09 +02:00
ashley
d451401186 Update html/watch.ejs 2025-05-27 18:18:27 +02:00
ashley
046c35119b Update html/watch.ejs 2025-05-27 18:10:35 +02:00
ashley
11faafeaff Update html/watch.ejs 2025-05-27 17:49:53 +02:00
ashley
ec4d53cbed Update html/watch.ejs 2025-05-27 17:46:30 +02:00
ashley
35b83a5d3c Update html/watch.ejs 2025-05-27 17:43:55 +02:00
ashley
64b0529e1b Update html/watch.ejs 2025-05-27 17:26:48 +02:00
ashley
bbdb3be59d Update html/landing.ejs 2025-05-27 17:15:12 +02:00
ashley
10eee1e4b7 Update html/search.ejs 2025-05-27 17:13:26 +02:00
ashley
d564e49d09 oop 2025-05-27 17:09:26 +02:00
ashley
43b8b641a7 Update css/player-base.js 2025-05-27 17:09:02 +02:00
ashley
a3b44a58ee Update html/watch.ejs 2025-05-27 17:05:36 +02:00
ashley
b75dc35a6b Update css/player-base.js 2025-05-27 17:05:10 +02:00
ashley
f7831ad85e Update html/watch.ejs 2025-05-27 17:02:07 +02:00
ashley
07490ec727 Update css/player-base.js 2025-05-27 17:01:41 +02:00
ashley
0654c381c7 Update html/watch.ejs 2025-05-27 16:58:09 +02:00
ashley
26558507db Update css/player-base.js 2025-05-27 16:57:43 +02:00
ashley
daae5abff1 Update html/watch.ejs 2025-05-27 16:27:44 +02:00
ashley
50597f59a7 test code 2025-05-27 16:26:44 +02:00
ashley
10a18905ec Update html/watch.ejs 2025-05-27 02:41:59 +02:00
Ashley ////
f661ee76a9 hai 2025-05-27 02:27:31 +02:00
Ashley ////
024bea2b33 hai 2025-05-27 02:24:52 +02:00
ashley
488d98d1f1 update versions 2025-05-27 01:51:47 +02:00
ashley
9ec1a3a718 fix download pages 2025-05-27 01:47:57 +02:00
ashley
bb28ed0de6 Update html/discover.ejs 2025-05-27 01:42:09 +02:00
ashley
43c2b1111e Update html/discover.ejs 2025-05-27 01:41:09 +02:00
ashley
3f471059f8 Update html/discover.ejs 2025-05-27 01:38:30 +02:00
ashley
46dc456562 Update html/discover.ejs 2025-05-27 01:34:52 +02:00
ashley
0c346781b8 oops 2025-05-27 01:31:03 +02:00
ashley
6824df229b try 2025-05-27 01:28:19 +02:00
ashley
00749f907d Update html/discover.ejs 2025-05-27 01:23:06 +02:00
ashley
e50e7b730a Add bg 2025-05-27 01:19:30 +02:00
ashley
dcf3b20fac Update html/map.ejs 2025-04-30 21:40:49 +00:00
ashley
b91128ff11 Update html/map.ejs 2025-04-30 21:38:53 +00:00
ashley
46abf16976 Update html/map.ejs 2025-04-30 21:34:37 +00:00
ashley
f9d434903d Update html/map.ejs 2025-04-30 21:27:46 +00:00
ashley
dd5aea5304 Update html/map.ejs 2025-04-30 21:24:37 +00:00
ashley
84b92ae3ac Update html/map.ejs 2025-04-30 21:18:07 +00:00
ashley
f05f3e5b3a Update html/translate.ejs 2025-04-30 17:47:21 +00:00
ashley
b4d7b3da11 stuff 2025-04-30 17:09:01 +00:00
ashley
bb1d09ea20 oops 2025-04-30 16:36:31 +00:00
ashley
d708f964eb add stuff 2025-04-30 16:35:26 +00:00
ashley
e4da5d48d4 Update src/libpoketube/init/pages-api.js 2025-04-30 16:08:06 +00:00
ashley
ac979c3bdc Update src/libpoketube/libpoketube-core.js 2025-04-29 19:55:15 +00:00
ashley
f31ff98a0b Update src/libpoketube/libpoketube-core.js 2025-04-29 19:52:34 +00:00
ashley
cc3922ad0e Update src/libpoketube/libpoketube-core.js 2025-04-29 19:50:23 +00:00
ashley
6e1aecaeb4 Update src/libpoketube/libpoketube-core.js 2025-04-29 19:48:46 +00:00
ashley
c9e8b8f85d Update src/libpoketube/libpoketube-core.js 2025-04-29 19:47:04 +00:00
ashley
99f4d03bbb Update src/libpoketube/libpoketube-core.js 2025-04-29 19:39:14 +00:00
ashley
efd3c59df7 Update package.json 2025-04-29 19:32:33 +00:00
ashley
a5a18ba383 use youtubei 2025-04-29 19:31:18 +00:00
ashley
c531678a18 add stuff 2025-04-27 17:13:14 +00:00
ashley
c9bd527527 Update html/gamehub.ejs 2025-04-27 13:59:24 +00:00
ashley
495367eace Update html/layouts/error-video.ejs 2025-04-27 13:13:34 +00:00
ashley
547f68c882 oops 2025-04-27 12:52:28 +00:00
ashley
6268bdebfb new and improved snake 2025-04-27 12:50:08 +00:00
ashley
7527e49c9c stuff 2025-04-27 11:39:29 +00:00
ashley
7125ecc36e Update html/video-error.ejs 2025-04-27 11:12:17 +00:00
ashley
4058e8ef11 Update html/layouts/error-video.ejs 2025-04-27 11:11:49 +00:00
ashley
0527a9e52f remove 2025-04-27 11:10:19 +00:00
ashley
230d371ced fix this :3 2025-04-27 11:05:37 +00:00
ashley
4a8070e65c Update html/gamehub.ejs 2025-04-26 23:43:29 +00:00
ashley
556285a8d1 fix game 2025-04-26 23:38:32 +00:00
ashley
3076fb9393 fix stuff stuff 2025-04-26 23:36:34 +00:00
ashley
777e8522b2 Update html/gamehub.ejs 2025-04-26 23:32:25 +00:00
ashley
efe0f7d758 Update html/gamehub.ejs 2025-04-26 23:28:37 +00:00
ashley
38094732ba Update html/gamehub.ejs 2025-04-26 23:23:23 +00:00
ashley
b8ce39698a oopsie 2025-04-26 23:19:40 +00:00
ashley
4519933c9e Update html/gamehub.ejs 2025-04-26 23:16:33 +00:00
ashley
18635eca02 Update src/libpoketube/init/pages-api.js 2025-04-26 22:55:49 +00:00
ashley
2202193d23 remove unused imports 2025-04-26 22:46:59 +00:00
ashley
b1de0358a7 new stuff 2025-04-26 22:45:40 +00:00
ashley
739d64f684 Update html/watch.ejs 2025-04-26 21:57:04 +00:00
ashley
d539547fd3 Update html/watch.ejs 2025-04-26 21:54:06 +00:00
ashley
c5e67b7f05 Update html/watch.ejs 2025-04-26 21:52:43 +00:00
ashley
d31253551c Update html/partials/card.ejs 2025-04-26 21:41:55 +00:00
ashley
0f40a4a152 add replies 2025-04-26 21:37:42 +00:00
ashley
6e627b48a7 addcool stuff 2025-04-26 21:01:06 +00:00
ashley
704b7bc806 Update html/search.ejs 2025-04-23 20:55:15 +00:00
ashley
f77d5e0260 human friendly reason :3 2025-04-23 20:48:27 +00:00
ashley
5ab4cc30a5 Update html/layouts/error-video.ejs 2025-04-23 20:48:07 +00:00
ashley
afa738f8c2 Update html/search.ejs 2025-04-22 23:55:06 +00:00
ashley
3f19854798 Update html/search.ejs 2025-04-22 23:54:03 +00:00
ashley
6801889f64 Update html/search.ejs 2025-04-22 23:51:16 +00:00
ashley
ce5428eccb a 2025-04-22 23:48:28 +00:00
ashley
3f66e88dae fix stuff stuff 2025-04-22 23:42:32 +00:00
ashley
7ef352f3cb fix stuff 2025-04-22 23:39:39 +00:00
ashley
38871bba36 Update html/search.ejs 2025-04-22 23:29:48 +00:00
ashley
e755ef3f4c oops 2025-04-22 23:28:23 +00:00
ashley
64433797f3 add alot of cool stuff :3 2025-04-22 23:24:35 +00:00
ashley
3f69953875 add error here :3 2025-04-22 18:48:41 +00:00
ashley
b69b2a590e Update css/maps.js 2025-04-22 18:42:37 +00:00
ashley
3558b47301 update
Signed-off-by: ashley <iamashley@duck.com>
2025-04-19 22:53:48 +00:00
ashley
acef288683 sorry for weird formatting mobile 2025-04-19 22:47:39 +00:00
ashley
27becd05a6 test1 2025-04-08 17:24:30 +00:00
ashley
0347a9ab94 update :3 2025-04-08 16:57:17 +00:00
ashley
e775e617ed :3 2025-04-08 16:49:38 +00:00
ashley
59dd95fa39 add error here :3 2025-04-07 14:38:31 +00:00
ashley
fd1f3ea864 Update src/libpoketube/init/pages-static.js 2025-04-06 19:40:09 +00:00
ashley
310ffb643c Update src/libpoketube/init/pages-channel-and-download.js 2025-04-06 19:36:45 +00:00
ashley
4bf687b4a3 Update src/libpoketube/init/pages-channel-and-download.js 2025-04-06 19:33:16 +00:00
ashley
4acf840d80 Update src/libpoketube/init/pages-api.js 2025-04-06 19:27:37 +00:00
ashley
4b33b38e1f p2 2025-04-06 19:22:36 +00:00
ashley
7188bfdf03 Update .gitignore 2025-04-06 19:16:39 +00:00
ashley
440431abbd Update config.json.example 2025-04-06 19:16:18 +00:00
ashley
172a7175ec add useragent to config 2025-04-06 19:02:54 +00:00
ashley
b5e09f60eb add useragent : p1 2025-04-06 19:02:08 +00:00
ashley
906f7b237e Update src/libpoketube/libpoketube-dislikes.js 2025-02-16 19:08:37 +00:00
ashley
28e4ffb4eb add some cool stuff 2025-02-06 05:21:02 +00:00
ashley
9747dc7f47 new stuff 2025-02-06 05:13:47 +00:00
Ashley ////
9f1a52d840 Merge pull request 'Update README | Install Instructions and Mirrors' (#122) from Korbs/poke:main into main
Reviewed-on: https://codeberg.org/ashley/poke/pulls/122
2025-02-06 04:53:37 +00:00
Korbs
461cec77b2 Update install instructiosn for Fedora/RHEL, add line for Alpine Linux 2025-01-22 06:30:28 +00:00
ashley
73278c70ee ehe 2024-12-18 21:36:22 +00:00
ashley
953ee0daaf fix stuff 2024-12-18 21:33:20 +00:00
ashley
e5e55ce26e ehe 2024-12-18 21:32:10 +00:00
ashley
5104cbc3c7 poketube.ejs ---> watch.ejs, 2024-12-18 21:15:28 +00:00
ashley
de0afd6266 poketube.ejs ---> watch.ejs 2024-12-18 21:14:17 +00:00
ashley
4c6f21c5ee fix stuff - make stuff good 2024-12-14 15:13:22 +00:00
ashley
b42ad79716 oops lmao 2024-12-09 04:02:08 +00:00
ashley
a9339ff5ae Update src/libpoketube/libpoketube-dislikes.js 2024-12-05 20:25:55 +00:00
ashley
1ff9b80fdd remove stuff 2024-11-19 17:49:04 +00:00
ashley
07bf8bc42d bump bundle version :3 2024-11-19 17:35:30 +00:00
ashley
64e258bb5b update base player 2024-11-19 17:33:35 +00:00
ashley
ad3ca4635e lol 2024-11-18 20:14:03 +00:00
ashley
1331fb4c43 add margin 2024-11-17 17:21:37 +00:00
ashley
f9c22c8bf8 aaaaaaaa 2024-11-17 17:19:28 +00:00
ashley
46a2763802 oopsie 2024-11-17 17:12:30 +00:00
ashley
49d399e388 aaaü 2024-11-17 17:07:58 +00:00
ashley
0fd7ea8849 add thumns 2024-11-17 17:00:45 +00:00
ashley
16dabbd087 oops sorry 2024-11-12 20:14:28 +00:00
ashley
a638c930cc should fix channels 2024-11-12 20:07:04 +00:00
ashley
22e804850b remove the header because it makes stuff bad 2024-11-12 09:55:28 +00:00
ashley
a28f8e2536 addcool stuff 2024-11-03 15:22:38 +00:00
ashley
d477b56af0 add config. 2024-11-03 15:16:08 +00:00
ashley
fce3396c30 oops 2024-11-03 09:59:48 +00:00
ashley
85cee23ab0 lol sorry 2024-11-02 19:03:53 +00:00
ashley
96b01e74b9 add smth 2024-11-02 19:01:26 +00:00
ashley
84fdbb5c40 fix stuff 2024-11-02 18:12:53 +00:00
ashley
7cdfdb9359 add chatbot 2024-11-02 18:08:31 +00:00
ashley
f50386a94f add license for apple 2024-10-29 13:42:32 +00:00
ashley
9e7240ecdb fix typo and add splash text!! 2024-10-28 19:25:27 +00:00
ashley
bc7ffd16e3 actually use 404 LOL 2024-10-28 19:15:11 +00:00
ashley
663cbe3cde add branding here too!! :3 2024-10-28 19:10:14 +00:00
ashley
b4264b7bf3 fix stuff - make stuff good 2024-10-28 18:59:06 +00:00
ashley
6f96374fa4 o 2024-10-28 05:17:07 +00:00
ashley
8ca00ef881 add alot of cool stuff :3 2024-10-28 04:29:07 +00:00
ashley
cad92a9ad2 add h2 2024-10-25 23:03:09 +00:00
ashley
dfe5608681 fix 2024-10-25 23:02:13 +00:00
ashley
a931643afc use set instead 2024-10-25 23:00:22 +00:00
ashley
fef0cc3edc forgor 2024-10-25 22:58:54 +00:00
ashley
faad2b4d80 oops 2024-10-25 22:57:15 +00:00
ashley
001a22e325 lol 2024-10-25 22:55:28 +00:00
ashley
9d62bb5a88 oops 2024-10-25 22:21:04 +00:00
ashley
e9d140876c test a 2024-10-25 22:18:39 +00:00
ashley
36b447ed3a add stuff :3 2024-10-24 20:35:52 +00:00
ashley
c06c1ddb0e rtyhrgdg sorry again 2024-10-24 19:55:19 +00:00
ashley
78b42e1de4 REFDGHFDG AM STUPID 2024-10-24 19:54:54 +00:00
ashley
b997f4a9ba oops 2024-10-24 19:53:34 +00:00
ashley
6fcc6e0b40 oops 2024-10-24 19:51:14 +00:00
ashley
aff56b1e10 Update html/layouts/error-video.ejs 2024-10-24 19:46:28 +00:00
ashley
610d58390d bump bundle version :3 2024-10-24 19:42:55 +00:00
ashley
5b9da606fd add limit speed cpu 2024-10-24 19:41:07 +00:00
ashley
bbc27dc37f add alot of cool stuff :3 2024-10-24 19:39:53 +00:00
ashley
d622ed4f38 add a better reason :3 2024-10-24 19:36:04 +00:00
ashley
ae1ef1f057 add alot of cool stuff :3 2024-10-24 19:33:40 +00:00
ashley
4adf09b4c9 :3 2024-10-24 19:23:14 +00:00
ashley
38a7d7c3af a 2024-10-24 14:10:14 +00:00
ashley
b601d2354e sorry 2024-10-24 14:09:30 +00:00
ashley
7ed5e35be8 lol 2024-10-24 14:06:58 +00:00
ashley
60f4c2adfa a 2024-10-24 14:04:12 +00:00
ashley
edb1f1a4c8 test a 2024-10-24 14:00:28 +00:00
ashley
3872e7cb64 oops 2024-10-24 13:55:49 +00:00
ashley
1cde1c23ef test 2024-10-24 13:47:30 +00:00
ashley
de98d86101 oops :p 2024-10-23 17:15:59 +00:00
ashley
a0191ae39a make it 500 lol 2024-10-23 15:39:37 +00:00
ashley
9a9d00e73f add 502 2024-10-23 15:35:52 +00:00
ashley
2fd11f9679 :p 2024-10-23 15:33:17 +00:00
ashley
fca41df817 add video-title lol 2024-10-23 15:30:59 +00:00
ashley
35d175e2d5 fix css 2024-10-23 15:30:08 +00:00
ashley
5a476089b4 lol 2024-10-23 15:22:07 +00:00
ashley
bcd634e683 add license 2024-10-23 15:02:39 +00:00
ashley
e3b68cb658 add stuff :3 2024-10-22 13:54:47 +00:00
ashley
4827e9c626 bump version :3 2024-10-21 20:07:36 +00:00
ashley
9b4660da5c oops sorry 2024-10-21 20:07:06 +00:00
ashley
002ca6a4b4 bump version!! :3 2024-10-21 20:06:23 +00:00
ashley
f2c9a99245 bump version :3 2024-10-21 20:05:49 +00:00
ashley
8a10da5a24 bump version :3 2024-10-21 20:05:08 +00:00
ashley
d94933dd25 Update css/yt-ukraine.svg 2024-10-21 19:59:39 +00:00
ashley
69decd0db9 lol 2024-10-21 18:41:54 +00:00
ashley
f871564712 new color stuff :3 2024-10-21 18:41:18 +00:00
ashley
981d23bc4a add stuff :3 2024-10-21 18:38:37 +00:00
ashley
42feec4d78 Update html/poketube.ejs 2024-10-20 20:44:35 +00:00
ashley
373b915a79 add a br 2024-10-20 20:41:46 +00:00
ashley
b94a57d1fa awa 2024-10-20 20:40:05 +00:00
ashley
3f323d5d40 bump bundle version :3 2024-10-20 20:36:13 +00:00
ashley
c0d847250a cool stuff :3 2024-10-20 20:35:11 +00:00
ashley
df4d228861 alot fof cool stuff 2024-10-20 20:34:01 +00:00
ashley
8228907bfe cool stuff 2024-10-20 18:37:42 +00:00
ashley
1462834ad2 test 2024-10-20 18:12:10 +00:00
ashley
82b141717d cool stuff 2024-10-20 17:58:27 +00:00
ashley
53b75a10fb add alot of cool stuff 2024-10-20 17:53:07 +00:00
ashley
65219997c9 add license 2024-10-20 09:15:16 +00:00
ashley
1fae26c530 add the timer 2024-10-20 09:14:34 +00:00
ashley
2c4f30a813 remove spaces lol 2024-10-20 09:13:48 +00:00
ashley
4fc34f1a4f Add backend-services/services/yt-block-protect.service 2024-10-20 09:13:31 +00:00
ashley
d8e56cf530 Add backend-services/scripts/inv-restart-docker.sh 2024-10-20 09:12:36 +00:00
ashley
4314ae9b01 add refresh token 2024-10-20 09:10:10 +00:00
ashley
cd6eac25a8 Add backend-services/README.md 2024-10-20 09:09:04 +00:00
ashley
3796095cbb make ratelimit more 2024-10-19 19:13:47 +00:00
ashley
d8f93dedd1 change logo lol 2024-10-19 15:01:12 +00:00
ashley
aeddb5e612 Update html/download.ejs 2024-10-19 14:58:57 +00:00
ashley
a298fd5e4a Update css/chatgptlogo.svg 2024-10-19 12:41:02 +00:00
ashley
2702071e11 Update css/chatgptlogo.svg 2024-10-19 12:36:05 +00:00
ashley
ebb1bf369b Add css/chatgptlogo.svg 2024-10-19 12:35:37 +00:00
ashley
53cb8665ff add ask gpt 2024-10-19 12:20:37 +00:00
ashley
4e4dee2879 remove 2024-10-18 18:23:47 +00:00
ashley
7e98929101 Update README.md 2024-10-17 19:08:38 +00:00
ashley
19f43779b1 test a 2024-10-17 18:22:30 +00:00
ashley
ffab316b42 add stuff 2024-10-17 18:07:18 +00:00
ashley
e0e0200568 stuff fixes 2024-10-17 18:04:09 +00:00
ashley
c393cd3ca7 a 2024-10-17 17:59:39 +00:00
ashley
2e763de66b hm 2024-10-17 17:59:00 +00:00
ashley
b0e760325f test 2024-10-17 17:57:19 +00:00
ashley
d2bcde4944 add stuff 2024-10-17 17:55:11 +00:00
ashley
d6e35c5aa3 refresh lol 2024-10-12 13:53:44 +00:00
ashley
c21f028786 Update html/video-error.ejs 2024-10-12 13:52:48 +00:00
ashley
fec30d4d92 add fetchWithRetry 2024-10-12 10:16:58 +00:00
ashley
737da26f6f Update README.md 2024-10-11 15:19:28 +00:00
ashley
5ad8f63563 Update docker-compose.yml 2024-10-11 15:15:21 +00:00
ashley
0b0aa6326c add new splashes ehe 2024-10-09 19:36:25 +00:00
ashley
b742f91764 oops 2024-10-06 20:42:57 +00:00
ashley
67a98c9d5d add share button :3 2024-10-06 20:35:11 +00:00
ashley
f207ea4998 awa 2024-10-06 12:42:56 +00:00
ashley
8b5d3fa0cc last update trust 2024-10-06 12:36:10 +00:00
ashley
e7d3b07130 ehe 2024-10-06 12:34:46 +00:00
ashley
0cd5c523f9 sorry qwq 2024-10-06 12:30:55 +00:00
ashley
28dd44c91e wawa 2024-10-06 12:28:21 +00:00
ashley
93f3efdb3e add this ehe 2024-10-06 12:23:17 +00:00
ashley
df6bfafedb add gif 2024-10-06 12:21:57 +00:00
Ashley ////
8b0134bb11 Merge pull request 'Update instances.json' (#119) from Binzy_Boi/poke:main into main
Reviewed-on: https://codeberg.org/ashley/poke/pulls/119
2024-10-05 20:32:37 +00:00
Binzy_Boi
af49f2c4ae Merge branch 'main' into main 2024-10-05 20:30:18 +00:00
Binzy_Boi
f9199b1d5d Update instances.json
Removed ashley0143 instance
2024-10-05 20:28:04 +00:00
Binzy_Boi
b783cfaab1 Update instances.json 2024-10-05 16:34:47 +00:00
Binzy_Boi
2be1cf2d95 Update instances.json
Removing alphexo.dev instance, as it is no longer online.
2024-10-05 15:21:36 +00:00
ashley
c930e55c46 fix goober bug 2024-10-04 19:39:11 +00:00
ashley
72fe45cb9e stuff :3 2024-10-04 19:33:00 +00:00
ashley
3635d37245 stuff :3 2024-10-04 19:30:20 +00:00
ashley
c91b1423a4 test2 2024-10-04 19:15:11 +00:00
ashley
7f9933ae16 update css stuff :3 2024-10-04 19:07:49 +00:00
ashley
e4f33378bd css stuff 2024-10-04 16:42:55 +00:00
ashley
d4c24d7241 responsive stuff 2024-10-04 15:24:28 +00:00
ashley
1403c2ab24 sorry qwq 2024-10-04 15:18:45 +00:00
ashley
7d8b4945d3 add month stuff 2024-10-04 15:15:48 +00:00
ashley
6a9f35dae1 p 2024-10-04 14:59:06 +00:00
ashley
2aa943f141 awa 2024-10-04 14:58:35 +00:00
ashley
06d1770c0e work 2024-10-04 14:54:44 +00:00
ashley
3ddc1e072d pls work 2024-10-04 14:52:57 +00:00
ashley
0a35ab0cf0 stuff stuff 2024-10-04 14:51:31 +00:00
ashley
9cdc85ca91 :3 2024-10-04 14:48:10 +00:00
ashley
3c2564f170 silly 2024-10-04 14:45:23 +00:00
ashley
786704bcde make it months instead wawa :3 2024-10-04 14:43:35 +00:00
ashley
27ae6acc87 woaw stuff 2024-10-04 14:42:22 +00:00
ashley
d5dd9303b8 Update html/calendar.ejs 2024-10-04 14:35:33 +00:00
ashley
3e75a28a12 cool stuff 2024-10-04 14:33:46 +00:00
ashley
5d9880304a cool stuff 2024-10-04 14:27:08 +00:00
Binzy_Boi
ed06fcb92c Update instances.json
This change does the following:

* Removes offline instances
* Corrects URL of existing instance
* Adds new instance hosted in Poland
* Changes typos of "lastest" to "latest"
* Renames "uri" fields to "url"
* Changes one mention of "PokeTube" to "Poke"
2024-10-04 03:30:22 +00:00
ashley
2d78a1a7f6 update some stuff :3 2024-10-03 19:05:39 +00:00
ashley
4e8af0337a bump version!! :3 2024-10-02 19:52:21 +00:00
ashley
ddbb533e6d add formats lol 2024-10-02 19:51:24 +00:00
ashley
c1faf2bbcd bump bundle version :3 2024-10-02 19:26:06 +00:00
ashley
8e6cade155 add alot of stuff :3 2024-10-02 19:24:41 +00:00
ashley
59b746fd1c fix download yayaya 2024-10-02 18:35:52 +00:00
ashley
4b1765757f Update config.json 2024-09-28 17:48:22 +00:00
ashley
5bb5dacd30 bump 2024-09-28 13:19:35 +00:00
ashley
f5f88fe072 stuff stuff 2024-09-28 13:19:14 +00:00
ashley
b4d566f41f Update config.json 2024-09-27 04:19:24 +00:00
ashley
038b44cf98 Update config.json 2024-09-26 18:29:45 +00:00
ashley
8d3d4961e5 lol 2024-09-26 17:58:48 +00:00
ashley
4a821d700d test a 2024-09-26 17:56:50 +00:00
ashley
1df1b8af2d raah korea 2024-09-25 18:49:24 +00:00
ashley
fa8038f001 test c 2024-09-25 18:40:28 +00:00
ashley
9de47441af test commit tbh 2024-09-25 18:38:16 +00:00
ashley
76a6f4b101 hehe 2024-09-25 14:18:38 +00:00
ashley
1a19f51072 bwa 2024-09-25 14:16:53 +00:00
Ashley ////
4bb8d742da fix stuff 2024-09-25 09:10:32 +00:00
ashley
c92caf80c5 bwa 2024-09-25 04:32:53 +00:00
ashley
f5e98c8fe2 bwa 2024-09-24 20:35:00 +00:00
ashley
4a6c62f68b add canplay 2024-09-24 20:30:39 +00:00
ashley
e1be2a6979 Update html/poketube.ejs 2024-09-24 19:49:04 +00:00
ashley
98f80c3ced bwa 2024-09-24 19:46:25 +00:00
ashley
4a55d5c92c awa 2024-09-24 19:37:44 +00:00
ashley
adc02ed151 test 1 2024-09-24 19:31:18 +00:00
ashley
00a7738a1d ehe 2024-09-24 18:46:11 +00:00
ashley
5bf3da0dc0 add syuff 2024-09-24 18:33:10 +00:00
ashley
4c2001ceee usa proxy 2 : electric boogallo 2024-09-24 15:12:26 +00:00
ashley
0082765bf6 add stuff 2024-09-24 13:48:22 +00:00
ashley
2008aabb74 lol 2024-09-23 13:20:16 +00:00
ashley
c33916b5db add qucik escape 2024-09-23 13:16:27 +00:00
ashley
3cee9ae5b5 awa 2024-09-22 11:15:54 +00:00
ashley
cf6420cde1 use usa 2 now 2024-09-22 11:15:38 +00:00
ashley
dc66c4582f add banner config 2024-09-21 16:03:15 +00:00
ashley
48c00ce59f add banner 2024-09-21 16:00:23 +00:00
ashley
e9c9cac4fd Update config.json 2024-09-21 15:57:10 +00:00
ashley
75d05d3c85 log it 2024-09-21 09:17:36 +00:00
ashley
2941fa7b86 remoev 2024-09-21 05:06:43 +00:00
ashley
308518f6cd Update html/poketube.ejs 2024-09-20 22:19:07 +00:00
ashley
2e02fd3e23 test 2024-09-20 22:15:34 +00:00
ashley
8bd284f67e use eu 2024-09-20 04:18:27 +00:00
ashley
5887f05fe6 qwq 2024-09-19 19:15:54 +00:00
ashley
0183286bb2 sorry qwq 2024-09-19 19:11:21 +00:00
ashley
0f54d186d1 add mastodon qwq 2024-09-19 19:09:03 +00:00
ashley
6cb5ea0ba1 hostname qwq 2024-09-19 19:03:11 +00:00
ashley
ae06025b0e < 2024-09-18 17:03:01 +00:00
ashley
8b596806a5 pol1 moment 2024-09-18 17:01:01 +00:00
ashley
bda8921b34 stuff 2024-09-18 16:01:07 +00:00
ashley
e5bf497462 lol 2024-09-18 14:47:17 +00:00
ashley
3612e5e837 bump version!! :3 2024-09-17 18:18:16 +00:00
ashley
96ece4f570 add annations 2024-09-17 18:16:22 +00:00
ashley
727a6a6727 do the thing 2024-09-16 20:24:10 +00:00
ashley
db26363c95 add this lol 2024-09-16 19:40:16 +00:00
ashley
50eaf51c5f qwq 2024-09-16 14:13:55 +00:00
Ashley ////
5727480778 fix url 2024-09-16 09:04:43 +00:00
Ashley ////
4d1080fcd2 remove old url 2024-09-16 09:03:21 +00:00
Ashley ////
1f457865df fix url 2024-09-16 09:01:49 +00:00
ashley
a2177f2c3d awa 2024-09-15 12:49:53 +00:00
ashley
6692cc525b fix stuff 2024-09-15 12:44:39 +00:00
ashley
5a486263e1 awa 2024-09-15 12:10:55 +00:00
ashley
0c628a9cd0 add invite here qwq 2024-09-15 12:10:20 +00:00
ashley
809a0d57c9 lol 2024-09-15 12:08:56 +00:00
ashley
750dc09ef6 add reason for fail 2024-09-15 12:08:28 +00:00
ashley
56fa4b8993 fix css :3 2024-09-14 22:05:46 +00:00
ashley
aea2181389 add other url 2024-09-14 22:03:23 +00:00
ashley
bb8c50df8e add notice here 2024-09-14 21:43:39 +00:00
ashley
1ce1ae5837 use korea 2024-09-14 16:15:33 +00:00
ashley
4e2f1b052e use eu proxy instead 2024-09-14 15:26:16 +00:00
ashley
f5c31e24a9 add kr proxy instead 2024-09-14 09:20:42 +00:00
ashley
9d8de3d898 bwaa 2024-09-14 07:30:18 +00:00
ashley
5dc6a9e435 remove suggesttions for now ig 2024-09-14 07:20:13 +00:00
ashley
a771300e84 u dont need this 😭
see https://codeberg.org/ashley/poke/src/branch/main/src/libpoketube/init/pages-static.js#L160 oh my god.............
2024-09-12 19:52:41 +00:00
ashley
c2a693abb2 awa 2024-09-11 20:26:25 +00:00
ashley
ba73e43725 add new stuff :3 2024-09-11 20:24:49 +00:00
ashley
2f0da0e6cc add video error layour :3 2024-09-11 20:22:54 +00:00
ashley
0c033002d4 add video time :4 2024-09-11 20:02:56 +00:00
ashley
0956a9b66e url change 2024-09-11 19:19:12 +00:00
ashley
bf2af710c3 add refresh here :3 2024-09-11 16:19:42 +00:00
ashley
e59d1c0e1c :p 2024-09-11 15:54:26 +00:00
ashley
878cca2760 wa 2024-09-11 15:52:42 +00:00
ashley
6ffcbb1683 oops 2024-09-11 15:51:34 +00:00
ashley
49de19ca5f add err here 2024-09-11 15:48:32 +00:00
ashley
c2682cdeac add video error 2024-09-11 15:47:36 +00:00
ashley
feb3242638 add cool stuff :3 2024-09-08 00:02:27 +00:00
ashley
b25841a667 add cpu warning 2024-09-07 23:56:41 +00:00
ashley
fdb2c1d12e return 404 if errors 2024-09-07 12:54:03 +00:00
ashley
d477c04ce4 Update src/libpoketube/init/pages-video.js 2024-09-07 12:51:27 +00:00
ashley
9f1cbd77d8 test b 2024-09-07 12:50:37 +00:00
ashley
aa3de021c7 Update src/libpoketube/init/pages-video.js 2024-09-07 12:47:55 +00:00
ashley
54b92b1e29 a 2024-09-07 12:46:11 +00:00
ashley
93898da514 Update src/libpoketube/init/pages-video.js 2024-09-07 12:43:01 +00:00
ashley
f3a5c690e3 test a 2024-09-07 12:39:25 +00:00
ashley
75affcf7c7 lol 2 2024-09-07 10:56:28 +00:00
ashley
4000c2d5ad lol 2024-09-07 10:56:07 +00:00
ashley
43581eaa59 fix bug :3 2024-09-06 03:44:03 +00:00
ashley
442ae884a3 Update src/libpoketube/libpoketube-dislikes.js 2024-09-05 22:53:56 +00:00
ashley
a4223a2772 new url 2024-09-05 22:51:32 +00:00
ashley
670343d2ff change url 2024-09-05 22:50:54 +00:00
ashley
38dec52081 move up code.lgbt 2024-09-05 19:59:06 +00:00
Ashley ////
f792fe9172 Merge pull request 'add: code.lgbt as an unofficial mirror' (#116) from ryanamay/poke:main into main
Reviewed-on: https://codeberg.org/ashley/poke/pulls/116
2024-09-05 19:23:31 +00:00
ryanamay
d66d6fc0ff add: code.lgbt an an unofficial mirror 2024-09-05 19:20:15 +00:00
ashley
d7ca2fd473 add alot of quality of life improvements!! 2024-09-04 12:02:38 +00:00
ashley
9665d8c79f add new banned channels 2024-09-02 23:13:16 +00:00
ashley
79ce4dc835 bump version - move location 2024-09-02 22:32:00 +00:00
ashley
102ba2b9be add window.pokeplayer object :3 2024-09-02 22:31:03 +00:00
ashley
1a6accc21b Update html/channel.ejs 2024-09-02 22:19:24 +00:00
ashley
b86af7ec97 Update html/search.ejs 2024-09-02 22:18:56 +00:00
nin0dev
8d59e75054 Update issue_template/player-bug.yml 2024-09-02 18:49:28 +00:00
ashley
2f56a1cc9a Update html/poketube.ejs 2024-09-02 13:21:40 +00:00
ashley
9602febccb aaaaa sorry 2024-09-02 13:20:16 +00:00
ashley
ae7ad7bb31 add href here :3 2024-09-02 12:39:52 +00:00
ashley
a6961fb849 Update html/search.ejs 2024-09-02 10:56:14 +00:00
ashley
4a0513ff53 Update html/search.ejs 2024-09-02 10:55:32 +00:00
ashley
6cc78e33d3 Update html/search.ejs 2024-09-02 10:53:04 +00:00
ashley
09ddd96d95 oopsies 2024-09-02 10:52:29 +00:00
ashley
12be559b2f add a lil guy 2024-09-02 10:51:39 +00:00
ashley
9d5a3db53a change api url :3 2024-09-01 23:57:26 +00:00
ashley
bdca2bc4c6 Update html/poketube.ejs 2024-09-01 23:55:57 +00:00
ashley
e2388890a8 Update html/poketube.ejs 2024-09-01 23:54:20 +00:00
ashley
70f02e3162 add no tags 2024-09-01 23:49:45 +00:00
ashley
f30f724bac make it lowercase 2024-09-01 23:02:16 +00:00
ashley
93da9b7078 a 2024-09-01 22:48:38 +00:00
ashley
5f7321e96d doesnt work revert 2024-09-01 22:48:23 +00:00
ashley
134a9d9e0e bump bundle version :3 2024-09-01 22:44:39 +00:00
ashley
61428f2c5d double-click 2024-09-01 22:43:07 +00:00
ashley
524715b393 oopsies 2024-09-01 22:19:41 +00:00
ashley
eccd7ded4c add translate 2024-09-01 22:17:31 +00:00
ashley
3956897066 add ?. 2024-09-01 22:14:27 +00:00
ashley
8f5d7de44d fix stuff 2024-09-01 21:36:43 +00:00
ashley
0d08532765 fix css stuff 2024-09-01 21:35:31 +00:00
ashley
e90976a2a4 a 2024-09-01 21:33:33 +00:00
ashley
62702ef128 final commmit hopefull 2024-09-01 21:30:56 +00:00
ashley
deedb165e7 add a div 2024-09-01 21:26:34 +00:00
ashley
be66d1aafc move 2024-09-01 21:23:49 +00:00
ashley
a2fd10382b test 2 2024-09-01 21:22:27 +00:00
ashley
77be7688e5 1 a 2024-09-01 21:20:33 +00:00
ashley
58405cfeaf change poster quality 2024-09-01 20:14:39 +00:00
ashley
0898586a70 bwa 2024-08-31 15:58:41 +00:00
ashley
bb95c21a78 ehe 2024-08-31 12:47:02 +00:00
ashley
690f6eaf94 add no id warning 2024-08-31 12:45:50 +00:00
ashley
4a9f666624 add "userScore" 2024-08-31 12:36:18 +00:00
ashley
eb7f0e6019 api changes 2024-08-31 12:30:22 +00:00
ashley
79ba510788 test 1 2024-08-31 12:16:20 +00:00
ashley
f97b582260 add t flag 2024-08-31 11:22:01 +00:00
ashley
2bfdb92de1 change url 2024-08-31 10:35:22 +00:00
ashley
211d0f6305 oops 2024-08-30 22:23:06 +00:00
ashley
bfa34ebe74 fix twitter logo 2024-08-30 22:21:19 +00:00
ashley
d74c9cb2e0 add twitter logo 2024-08-30 22:18:42 +00:00
ashley
d1aca04dfb make it 24h 2024-08-30 12:01:24 +00:00
ashley
9183634701 Update html/landing.ejs 2024-08-30 01:53:10 +00:00
ashley
2d6ccb5cf6 Update html/gamehub.ejs 2024-08-30 01:52:27 +00:00
ashley
c0bb5d7179 Update html/channel.ejs 2024-08-30 01:51:34 +00:00
ashley
c3f1f485e8 Update html/download.ejs 2024-08-30 01:48:39 +00:00
ashley
8069c21f94 :p 2024-08-30 01:47:48 +00:00
ashley
291f4eed43 stuff p1 2024-08-30 01:46:57 +00:00
Korbs
5aca984206 Update README.md 2024-08-29 19:44:46 +00:00
nin0dev
97d706f60b changed nin0git sync duration to 10m 2024-08-29 17:29:37 +00:00
ashley
df50735df7 remove this div :3 2024-08-29 10:34:07 +00:00
ashley
7df849531e :p 2024-08-29 09:59:31 +00:00
ashley
9b9f42837a new stuff :3 2024-08-29 09:56:33 +00:00
ashley
2c9b109c74 Update html/poketube.ejs 2024-08-28 21:27:01 +00:00
ashley
fbbf9a46db fix typo 2024-08-28 21:04:38 +00:00
ashley
450b3900eb add nin0git 2024-08-28 21:02:57 +00:00
ashley
9cfdfde9a9 h 2024-08-28 20:56:23 +00:00
ashley
03fb903de7 add git.lgbt mirror 2024-08-28 19:17:34 +00:00
ashley
f20efd8b16 add DisablePokeChan :3 2024-08-28 17:40:18 +00:00
ashley
055e64ba5d add DisablePokeChan :3 2024-08-28 17:38:39 +00:00
ashley
513e028a73 remove unneeded stuff :3 2024-08-28 17:28:43 +00:00
ashley
8d3e3ff5fc fix typo 2024-08-28 14:57:24 +00:00
ashley
d5e5c49f38 fix stuff :3 2024-08-28 14:53:03 +00:00
ashley
ac408b3dfa lol 2024-08-28 14:47:08 +00:00
ashley
97eee7f1e3 fix this :3 2024-08-28 14:44:41 +00:00
ashley
73d728650b add the discover button :3 2024-08-28 14:23:31 +00:00
ashley
23ab483a7d add spacing ehe 2024-08-28 08:21:23 +00:00
ashley
d66de07e20 fix stuff 2024-08-28 08:20:21 +00:00
ashley
6a73db8ab3 nop 2024-08-28 08:19:11 +00:00
ashley
7b61bb3b4e add comment count :3 2024-08-28 08:17:14 +00:00
ashley
fb9f5a5cd4 Update html/poketube.ejs 2024-08-27 23:16:33 +00:00
ashley
b828caa86e Update html/poketube.ejs 2024-08-27 23:15:01 +00:00
ashley
d01a34f9b5 awa 2024-08-27 23:11:07 +00:00
ashley
1370c899fa awa 2024-08-27 23:08:56 +00:00
ashley
9e3eeb277e test 4 2024-08-27 22:58:25 +00:00
ashley
cb67431323 test 3 2024-08-27 22:55:15 +00:00
ashley
c3b0209489 awa 2024-08-27 22:47:46 +00:00
ashley
9e5dd4f5f1 test commit 1 2024-08-27 22:43:57 +00:00
ashley
1642b99e1b add to here 2024-08-27 22:19:12 +00:00
ashley
bf7073597c Add css/player-base.js 2024-08-27 22:18:13 +00:00
ashley
ab61946366 ehe 2024-08-27 22:15:05 +00:00
ashley
587132d85a stuff 2024-08-27 22:07:53 +00:00
ashley
8d62e93d50 :p 2024-08-27 22:06:12 +00:00
ashley
4d0a66bcbd bwa 2024-08-27 22:03:21 +00:00
ashley
13b34adc6b css is homofobic 2024-08-27 22:01:52 +00:00
ashley
3e0af4d807 add blur optimization :3 2024-08-27 21:46:33 +00:00
ashley
86be21db2c bump version!! :3 2024-08-27 21:35:59 +00:00
ashley
76e90c109e add blur optimization :3 2024-08-27 21:35:35 +00:00
ashley
5b7ed14da8 add blur optimization :3 2024-08-27 21:32:47 +00:00
ashley
78802224c5 lol 2024-08-27 17:07:40 +00:00
ashley
9278f62c3b optimize stuff 2024-08-27 16:35:31 +00:00
ashley
31933b9199 oops 2024-08-27 09:58:11 +00:00
ashley
780963cbbe add the thing 2024-08-27 09:55:58 +00:00
ashley
f124787087 gwa 2024-08-26 20:56:09 +00:00
ashley
d648f1242b remove unneeded stuff :3 2024-08-26 15:24:23 +00:00
ashley
8c0332cbd5 improvments 2024-08-26 09:17:48 +00:00
ashley
dcaa51a303 awa 2024-08-24 00:13:56 +00:00
ashley
6b1a1008ea oops 2024-08-24 00:11:11 +00:00
ashley
a215c19082 test ignore 2024-08-23 23:12:41 +00:00
ashley
3b9cff1c24 Update src/libpoketube/init/pages-api.js 2024-08-23 22:39:47 +00:00
ashley
c2866de248 fix brainrot stuff 2024-08-22 21:24:02 +00:00
ashley
5e982b7301 i am le stupid and this is my message 2024-08-21 20:20:41 +00:00
ashley
9901c091bc Update src/libpoketube/init/pages-api.js 2024-08-21 13:10:52 +00:00
ashley
28767cb850 oops lol 2024-08-21 12:54:21 +00:00
ashley
599b5cac54 change api url :3 2024-08-21 12:24:34 +00:00
ashley
acb2cd0db3 remove censorship :3 2024-08-21 12:22:40 +00:00
Ashley ////
37745ccd05 Merge pull request 'main' (#115) from Shavix/poke:main into main
Reviewed-on: https://codeberg.org/ashley/poke/pulls/115
2024-08-21 10:04:30 +00:00
Shavix
24810b4cfe add letter "n" to fix a grammar mistake lol 2024-08-21 09:54:05 +00:00
Shavix
6bf345e94f update margin-bottom in the comment section
this fixes the "Go To Top" almost overlapping a bit with another text when there are no comments under a video.
2024-08-21 09:52:13 +00:00
ashley
8a67797707 Update src/libpoketube/init/pages-channel-and-download.js 2024-08-20 22:39:42 +00:00
ashley
1ce1bfa7aa lol 2024-08-20 22:37:46 +00:00
ashley
df04819b26 Update html/channel.ejs 2024-08-20 22:14:03 +00:00
ashley
49cf22446c lol 2024-08-20 21:49:49 +00:00
ashley
36f750fd11 lol 2024-08-20 21:46:41 +00:00
ashley
11c3e561e9 ehe 2024-08-20 19:36:35 +00:00
ashley
4f901e602a Update html/poketube.ejs 2024-08-19 22:18:23 +00:00
ashley
ad28847a67 test commit ignore pls 2024-08-19 22:12:04 +00:00
ashley
db151c1ec2 Update html/poketube.ejs 2024-08-19 22:05:55 +00:00
ashley
18785ff8c7 :3 2024-08-19 16:04:37 +00:00
ashley
adecc668f1 add more splash texts :3 2024-08-18 21:19:31 +00:00
ashley
8d43e65e5b bwa 2024-08-18 13:48:27 +00:00
ashley
826e4dfc1a try 500 2024-08-18 13:41:53 +00:00
ashley
7be4184ac0 stuff 2024-08-17 22:43:22 +00:00
ashley
3dad202d86 lol 2024-08-17 22:39:09 +00:00
ashley
2ad1df61a8 remove plugin 2024-08-17 20:04:21 +00:00
ashley
2fd231f1d9 propaganda --> disinformation 2024-08-17 19:22:42 +00:00
ashley
47009054da oops 2024-08-17 19:19:33 +00:00
ashley
1db888809c Update src/libpoketube/init/pages-channel-and-download.js 2024-08-17 19:18:25 +00:00
ashley
0b8f307632 . 2024-08-17 19:17:42 +00:00
ashley
f74efd2db0 Update src/libpoketube/init/pages-channel-and-download.js 2024-08-17 19:16:43 +00:00
ashley
c53d1a57c9 Update src/libpoketube/init/pages-channel-and-download.js 2024-08-17 19:13:24 +00:00
ashley
9c77e01076 add banned channels :3 2024-08-17 18:40:21 +00:00
ashley
05b753c210 make the css gud :3 2024-08-15 11:21:02 +00:00
ashley
e9fbc65755 Update html/channel.ejs 2024-08-15 11:11:12 +00:00
ashley
b17f7f9548 Update html/channel.ejs 2024-08-15 10:55:36 +00:00
ashley
70254d8965 test smth 2024-08-15 10:46:11 +00:00
ashley
d3d6f6a91c make it segfault :blobcatcozy: 2024-08-12 08:21:42 +00:00
ashley
83f24de9dd Update html/poketube.ejs 2024-08-11 09:29:58 +00:00
ashley
7849af14f6 remove enableModifiersForNumbers 2024-08-11 09:29:36 +00:00
ashley
872ba191b3 Update html/poketube.ejs 2024-08-11 09:22:48 +00:00
ashley
93243ed7db Update html/poketube.ejs 2024-08-11 09:21:23 +00:00
ashley
79b209fabe Update html/poketube.ejs 2024-08-11 09:18:20 +00:00
ashley
ffa846d96d Update html/poketube.ejs 2024-08-11 08:27:51 +00:00
ashley
6ca3d6a716 Update html/poketube.ejs 2024-08-11 08:25:47 +00:00
ashley
3eba5ec6f5 use video instead lol 2024-08-11 08:24:28 +00:00
ashley
455d93b15b oops 2024-08-11 08:22:40 +00:00
ashley
0e1d3b2961 try this :3 2024-08-11 08:21:27 +00:00
ashley
be142c3695 gwa 2024-08-11 08:07:39 +00:00
ashley
55cdded315 remove for nome 2024-08-11 08:06:16 +00:00
ashley
50f0345f91 use iytimg 2024-08-10 19:24:42 +00:00
ashley
20d6128206 Update src/libpoketube/libpoketube-core.js 2024-08-10 18:47:10 +00:00
ashley
a60ce8a9d9 try this
Signed-off-by: ashley <iamashley@duck.com>
2024-08-10 18:43:50 +00:00
ashley
90f5f08160 bump bundle version :3 2024-08-10 17:52:22 +00:00
ashley
13b28550e9 re-license :3 2024-08-10 17:51:42 +00:00
ashley
819533f0a2 awa 2024-08-09 15:01:34 +00:00
ashley
4c00425f66 fix stuff :3 2024-08-09 14:58:20 +00:00
ashley
ed2620e44f Update html/poketube.ejs 2024-08-09 13:28:17 +00:00
ashley
4006cf9c93 bwa 2024-08-09 13:26:54 +00:00
ashley
4a94d4b290 .. 2024-08-09 13:25:44 +00:00
ashley
5f5fe6d555 bwa 2024-08-09 13:23:43 +00:00
ashley
e410bdc95a Update html/poketube.ejs 2024-08-09 13:19:10 +00:00
ashley
8badc1dab5 bump bundle version :3 2024-08-09 13:15:47 +00:00
ashley
2eb634d12e remove unneeded stuff :3 2024-08-09 13:14:52 +00:00
ashley
3b6c9789f9 add vjs version 2024-08-09 11:54:50 +00:00
ashley
cd811ba381 fix audio :3 2024-08-09 11:54:21 +00:00
ashley
f183ee0c18 bump bundle version :3 2024-08-09 11:48:29 +00:00
ashley
7019791f44 ehe 2024-08-09 11:34:00 +00:00
ashley
12604c4a46 Update html/poketube.ejs 2024-08-09 11:10:54 +00:00
ashley
430d27d303 add videojs-plugin-hotkeys.js 2024-08-09 11:08:42 +00:00
ashley
0a415383a9 add videojs-hotkeys 2024-08-09 11:06:29 +00:00
ashley
0a29a4de21 add threads :3 2024-08-08 22:33:49 +00:00
ashley
4f3ae7ca61 add threads :3 2024-08-08 22:32:05 +00:00
ashley
b2d5f7ef05 X --> twitter :blobcatcozy: 2024-08-08 22:00:40 +00:00
ashley
4fc92bb6bd sorry qwq 2024-08-07 11:07:13 +00:00
ashley
d8bf3d83df Update html/search.ejs 2024-08-07 11:00:47 +00:00
ashley
aba0ca0986 bwa 2024-08-07 10:56:19 +00:00
ashley
9bd369b553 stuff :3 2024-08-07 10:52:40 +00:00
ashley
5fe05620cc Update html/search.ejs 2024-08-07 10:22:33 +00:00
ashley
9275d6cfe8 :3 2024-08-07 10:20:54 +00:00
ashley
7dc7180870 makeadd compliments :3 2024-08-07 10:12:48 +00:00
ashley
5fc2a56b17 ehe 2024-08-06 19:53:18 +00:00
ashley
73ad994113 ehe ^_^ 2024-08-05 22:55:15 +00:00
ashley
e0d9a74907 solve racism 2024-08-05 18:36:59 +00:00
ashley
0f6f9d4ee8 idk what to commit name so umm use vencord ig https://vencord.dev ^_^ 2024-08-05 18:33:32 +00:00
ashley
92fd9c8f0f add media control stuff :3 2024-08-05 17:43:18 +00:00
ashley
84057e8649 twenyone 2024-08-05 10:39:25 +00:00
ashley
00e8afb72e change classname 2024-08-05 06:31:57 +00:00
ashley
50475cc654 nwaa 2024-08-05 05:27:16 +00:00
ashley
c1a77f2076 bundle 2024-08-05 05:23:57 +00:00
ashley
47fa155aa7 bump bundle version :3 2024-08-05 05:12:18 +00:00
ashley
ee73dd6770 add vjs to here :3 2024-08-05 05:10:54 +00:00
ashley
8da7fd6030 fix stuff :3 2024-08-04 20:34:11 +00:00
ashley
653de060c5 sorry 2024-08-04 20:25:28 +00:00
ashley
f204c94415 upsell test 2024-08-04 20:23:19 +00:00
ashley
d1d83c2dea ehe 2024-08-04 19:38:06 +00:00
ashley
cfd9e0636f add questions n stuff 2024-08-04 19:32:48 +00:00
ashley
54ad94ed13 fix 2024-08-04 16:01:03 +00:00
ashley
eeafcef5c9 fish 2024-08-04 13:19:25 +00:00
ashley
0be7e45bf8 added fish 2024-08-04 13:17:10 +00:00
ashley
29c16b386c make what bignutty said 2024-08-04 13:04:25 +00:00
ashley
a05d27f34f br go brr 2024-08-04 12:25:39 +00:00
ashley
8147eb8a00 add warning for dislikes lol 2024-08-04 12:20:56 +00:00
ashley
a617d8e887 gwa gwa 2024-08-04 09:21:09 +00:00
ashley
8dfaf532d8 fix stuff :3 2024-08-04 08:23:18 +00:00
ashley
adbf2784fc remove useless buttons :^ 2024-08-03 23:09:15 +00:00
ashley
56e66dbf10 add vjs to here :3 2024-08-03 23:07:36 +00:00
ashley
15e1d7e237 add vjs to here :3 2024-08-03 23:01:08 +00:00
ashley
7f8edf6b55 add vjs to here :3 2024-08-03 22:59:58 +00:00
ashley
e38f3dea87 fix this 2024-08-03 19:46:37 +00:00
ashley
1d0d76832d . 2024-08-03 19:28:32 +00:00
ashley
fa6a64c5f3 fix audio 2024-08-03 19:03:20 +00:00
nin0dev
2950cfb808 remove revolt off readme 2024-08-03 18:54:41 +00:00
ashley
890aec6a30 stuff stuff stuff 2024-08-03 18:22:48 +00:00
ashley
87eeac58a3 1 2024-08-03 17:56:49 +00:00
ashley
c7b01a6af3 ehe 2024-08-03 17:54:19 +00:00
ashley
eda2b4f887 oooooooops 2024-08-03 17:47:30 +00:00
ashley
5a1555f93e bwa 2024-08-03 17:46:18 +00:00
ashley
e4c3aab9ee bwaa :^ 2024-08-03 17:23:00 +00:00
ashley
c40c1de959 a 2024-08-03 17:15:49 +00:00
ashley
9d1a5c3e66 oops 2024-08-03 17:12:26 +00:00
ashley
eaacf84ea9 test if this works (DO NOT PULL) 2024-08-03 17:05:58 +00:00
ashley
4bc351de27 update issue template 2024-08-02 18:34:31 +00:00
ashley
1a9278bb49 :3 2024-08-02 16:38:32 +00:00
ashley
2720bd1c23 add error report link :3 2024-08-02 16:35:23 +00:00
nin0dev
def048716a removed videobundler 2024-08-02 10:38:51 -04:00
nin0dev
0f26c1904b Merge branch 'main' of https://codeberg.org/ashley/poke 2024-08-02 10:37:28 -04:00
nin0dev
7e702d93c5 added issue templates 2024-08-02 10:37:01 -04:00
ashley
7ea1d89495 ehe 2024-08-02 11:12:28 +00:00
ashley
4ac19500e3 Update html/search.ejs 2024-08-02 10:54:38 +00:00
ashley
c264af305d lmao 2024-08-02 10:53:59 +00:00
ashley
d383c1ec2f ehe :3 2024-08-02 10:52:52 +00:00
ashley
a43fbf5dfc fix css stuff 2024-08-02 10:48:18 +00:00
ashley
fb8b797891 a 2024-08-02 10:43:40 +00:00
ashley
92b398809f :3 2024-08-02 10:40:50 +00:00
ashley
7452a2c18e [IN TESTING] news source check 2024-08-02 10:32:08 +00:00
ashley
cef4b4c97d remove stufff 2024-08-01 15:03:59 +00:00
nin0dev
33838c0b1d Merge branch 'main' of https://codeberg.org/ashley/poke 2024-08-01 10:43:15 -04:00
nin0dev
84c49a0007 a 2024-08-01 10:43:12 -04:00
ashley
063d83d851 bwa 2024-08-01 14:42:42 +00:00
ashley
e0aa8cd3d5 1 2024-08-01 14:25:29 +00:00
ashley
538222e1a1 ehe 2024-08-01 14:23:12 +00:00
ashley
8166cf6f3c . 2024-08-01 13:51:08 +00:00
ashley
b0d58a61cb test commit ignore pls 2024-08-01 13:49:13 +00:00
ashley
99a59740ae a 2024-08-01 12:30:27 +00:00
ashley
2496c9efc3 test ignore 2024-08-01 12:06:35 +00:00
ashley
6c0a84fc99 bwa 2024-08-01 11:48:30 +00:00
ashley
f9474cb95e 1 2024-08-01 11:41:44 +00:00
ashley
fceb1f9622 1a1a1 2024-08-01 11:39:13 +00:00
ashley
db5685e54f 1a 2024-08-01 11:34:15 +00:00
ashley
8068a207ce . 2024-08-01 11:26:08 +00:00
ashley
6349ccf0aa h 2024-08-01 11:23:17 +00:00
ashley
34dcf5b91f Update html/poketube.ejs 2024-08-01 11:21:41 +00:00
ashley
a0daea7b6b Update html/poketube.ejs 2024-08-01 11:15:43 +00:00
ashley
837e1d159f ehe 2024-08-01 11:12:56 +00:00
ashley
ef4c2af36b Update html/poketube.ejs 2024-08-01 11:08:22 +00:00
ashley
1f8d6c394c h 2024-08-01 11:06:51 +00:00
ashley
d7f5808467 . 2024-08-01 10:59:20 +00:00
ashley
5b57061be1 Update html/poketube.ejs 2024-08-01 10:56:43 +00:00
ashley
deeec99d62 1a 2024-08-01 10:53:34 +00:00
ashley
63af6b4abf aaaaaaaaaaaaaaaaaaaaaaa 2024-07-31 21:30:16 +00:00
ashley
4723a36649 videojs )test 2024-07-31 21:14:04 +00:00
ashley
abed47554c ehe 2024-07-31 16:41:11 +00:00
ashley
6847ee37ce add funny easter egg :3 2024-07-31 16:37:45 +00:00
ashley
50d2ff9955 gwa gwa 2024-07-31 15:29:58 +00:00
ashley
5529ea0727 make the sh filter and stuff better :3 2024-07-31 15:25:46 +00:00
ashley
220e3bdea0 add hr clasesssssssssssssss 2024-07-31 11:36:48 +00:00
ashley
7b634e9fe3 o 2024-07-31 11:32:29 +00:00
ashley
2ac328dcd0 gwa 2024-07-31 11:30:53 +00:00
ashley
4b1b5e9dd1 bwa v3 2024-07-31 11:27:10 +00:00
ashley
0faf254338 bwa v1 2024-07-31 11:24:29 +00:00
ashley
27827f2576 privacy policy update 2 2024-07-31 11:17:34 +00:00
ashley
3d23f2c18d privacy policy update 1 2024-07-31 11:11:15 +00:00
ashley
943307177a gwagwa 2024-07-28 14:07:38 +00:00
ashley
6639fafbcc embed test a 2024-07-28 13:53:43 +00:00
ashley
cbdfed5eb1 require 18 and above 2024-07-28 12:27:39 +00:00
ashley
7267e706b5 add facebook and threads regex 2024-07-28 11:33:51 +00:00
ashley
fe1bed37df revert teh thing 2024-07-27 11:27:59 +00:00
ashley
e71322bf0d [IN TESTING] attempt to fix #106 2024-07-27 11:26:12 +00:00
ashley
769a6dea2d [IN TESTING] attempt to fix #106 2024-07-27 11:23:38 +00:00
ashley
edf8991914 test ignore 2024-07-27 11:21:39 +00:00
ashley
5dd37cc23e gwa gwa 2024-07-27 11:12:37 +00:00
ashley
0b48788174 [IN TESTING] attempt to fix #106 2024-07-26 21:37:58 +00:00
ashley
8054805054 [IN TESTING] attempt to fix #103 2024-07-26 20:40:10 +00:00
ashley
64b52c36d9 add gaza esims link :3 2024-07-26 17:57:01 +00:00
ashley
29aea0be35 bwa bwa 2024-07-25 22:22:01 +00:00
ashley
dbde6dbfb9 ehe 2024-07-25 20:54:19 +00:00
ashley
b5307ddfc8 bwa! 2024-07-25 17:02:44 +00:00
ashley
eb26dbd0e2 oopsie 2024-07-24 14:41:12 +00:00
ashley
6842c3c622 add blur owo :3 2024-07-24 14:39:26 +00:00
ashley
98a0799e27 bwa 2024-07-24 14:25:22 +00:00
ashley
319bdd20c8 add blur bg :3 2024-07-24 14:05:38 +00:00
ashley
0b2917e40d fix the contunation bug :p 2024-07-24 12:49:18 +00:00
ashley
98dd3d1135 bump version!! :3 2024-07-24 10:42:55 +00:00
ashley
49429389ad add blur owo :3 2024-07-24 10:41:18 +00:00
ashley
dae6282b85 remove channelfirstvideoobject :3 2024-07-24 10:12:48 +00:00
ashley
e5ec4e6679 bwa 2024-07-23 19:39:38 +00:00
ashley
dac8758fb8 bwaa!!! 2024-07-23 14:27:36 +00:00
ashley
dd96734ffd bwa 2024-07-23 12:41:05 +00:00
ashley
e6075e7c56 add stuff 2024-07-23 12:40:30 +00:00
ashley
e39f50c930 add background color and radius!!! 2024-07-23 10:18:35 +00:00
ashley
d7f76f3a2c test commit ignore pls 2024-07-23 10:12:43 +00:00
ashley
29bd9352a0 add open on youtube link 2024-07-23 09:48:37 +00:00
ashley
2e99413e48 ehe 2024-07-22 20:10:34 +00:00
ashley
0b1d2c6b0e bwa!!! 2024-07-22 20:09:06 +00:00
ashley
c762d55946 remove broken code 2024-07-22 10:43:48 +00:00
ashley
bf03245830 new splash texts!! - 21J2024 2024-07-21 20:33:04 +00:00
ashley
7260558276 Update html/poketube.ejs 2024-07-20 15:36:47 +00:00
ashley
098c13b966 test if this works (DO NOT PULL) 2024-07-20 14:02:06 +00:00
ashley
740a5d82e1 bwa 2024-07-19 12:43:48 +00:00
ashley
431d458cb5 remove these 2024-07-18 14:10:36 +00:00
ashley
8187d5b389 oh my god lmafo 2024-07-18 13:59:27 +00:00
ashley
c16e19eb1b aaa 2024-07-18 13:56:59 +00:00
ashley
2445d8fb49 Update src/libpoketube/init/pages-video.js 2024-07-18 13:53:00 +00:00
ashley
cb02f8f255 last commit today trust 2024-07-18 13:46:11 +00:00
ashley
8a2d8eddc9 fix height :3 2024-07-18 13:42:46 +00:00
ashley
7303b9c241 gwa 2024-07-18 13:40:26 +00:00
ashley
999b18e4bb new splashes :3 2024-07-17 23:38:54 +00:00
ashley
e6f0591fc4 make it funnier 2024-07-17 15:24:08 +00:00
ashley
57eaae43aa lol 2024-07-17 15:20:42 +00:00
ashley
eae7d4500c test commit ignore pls 2024-07-17 10:22:23 +00:00
ashley
ac72e2ccb3 wdfgefjıtgfu 2024-07-16 20:31:42 +00:00
ashley
ccf32b9b43 Update html/poketube.ejs 2024-07-16 20:30:02 +00:00
ashley
da5bafacd4 Update html/poketube.ejs 2024-07-16 20:28:47 +00:00
ashley
8354108df7 add stuff 2024-07-16 20:26:24 +00:00
ashley
6c7f16f66f bwaa 2024-07-16 19:52:03 +00:00
ashley
27c9168f85 Update html/poketube.ejs 2024-07-16 19:51:19 +00:00
ashley
581137ec02 FIX: skill issue :3 2024-07-16 19:48:16 +00:00
ashley
2fa69f81ca add stream music button !!!!!! 2024-07-16 19:45:55 +00:00
ashley
d659e388fd update regex ^_^ 2024-07-16 19:44:19 +00:00
ashley
1d2fdc3ea2 Update src/libpoketube/init/pages-video.js 2024-07-16 19:38:05 +00:00
ashley
c89ba51f57 Update html/poketube.ejs 2024-07-16 12:40:42 +00:00
ashley
40a8e92e80 fix shitcode :3 2024-07-15 20:54:14 +00:00
ashley
51a2f042c1 Update src/libpoketube/init/pages-channel-and-download.js 2024-07-15 20:21:59 +00:00
ashley
7767c69ad1 Update src/libpoketube/init/pages-channel-and-download.js 2024-07-15 20:09:54 +00:00
ashley
e01174182c hm 2024-07-15 20:07:20 +00:00
ashley
cd27910117 bwa 2024-07-15 20:04:43 +00:00
ashley
1b2e86f5f8 mv 2024-07-15 20:02:58 +00:00
ashley
867bb45e42 test ignore 2024-07-15 20:00:18 +00:00
ashley
40095fe64e Update src/libpoketube/init/pages-channel-and-download.js 2024-07-15 17:55:56 +00:00
ashley
d449d412f1 remove j :3 2024-07-15 17:50:51 +00:00
ashley
4b76c5187b make the channel pages faster,, 2024-07-15 17:47:55 +00:00
ashley
e2120319f4 bwa 2024-07-15 15:41:52 +00:00
ashley
505e9c1c61 :3 2024-07-15 15:41:18 +00:00
ashley
5606cd7684 use the channel avatar :3 2024-07-15 15:17:33 +00:00
ashley
7ddbe0641a fix channel pages :3 2024-07-15 14:58:32 +00:00
ashley
cd9786f699 use ChannelFirstVideoObject 2024-07-15 14:53:00 +00:00
ashley
d4a7a2cded add the firstvideo thing :3 2024-07-15 14:46:53 +00:00
ashley
dafaedef2e stuff 2024-07-15 10:19:35 +00:00
ashley
f2fd62dce0 Update src/libpoketube/init/pages-channel-and-download.js 2024-07-14 11:07:59 +00:00
ashley
5e4097b88c Update src/libpoketube/init/pages-404-and-main.js 2024-07-14 11:03:51 +00:00
ashley
75164fce6f use the api key 2024-07-14 11:01:45 +00:00
ashley
798688d105 use the api key 2024-07-14 11:00:19 +00:00
ashley
a62433c6f9 Update config.json 2024-07-14 10:59:22 +00:00
ashley
153416e458 add version information :3 2024-07-13 12:23:30 +00:00
ashley
80403f650e add 60fps indicator :3 2024-07-13 12:02:19 +00:00
ashley
37ec1f248c Update html/poketube.ejs 2024-07-13 11:03:26 +00:00
ashley
4b21f43ba9 add volume :3 2024-07-12 15:37:07 +00:00
ashley
e3ab50216c use data-channel instead ^_^ 2024-07-10 12:23:12 +00:00
ashley
3e85cf91a8 use data-author instead ^_^ 2024-07-10 12:17:22 +00:00
ashley
5b7762f2cb test commit ignore pls 2024-07-08 15:06:33 +00:00
ashley
96c61f26d0 fix stuff 2024-07-07 20:49:24 +00:00
ashley
47833803b2 fix seekbar on fulscreen 2024-07-07 19:58:53 +00:00
ashley
f124efc8e3 fix stuff :3 2024-07-07 17:56:59 +00:00
ashley
fc08e94049 try this 2024-07-06 16:31:21 +00:00
ashley
90910bfee4 Update html/poketube.ejs 2024-07-06 14:27:58 +00:00
ashley
913ccef4a7 remove broken code 2024-07-06 14:14:54 +00:00
ashley
97bb31f304 fix stuff 2024-07-06 14:11:16 +00:00
ashley
8990672e62 fix : use readystate 2024-07-06 13:44:53 +00:00
ashley
673142052e fix: remove autosync for now 2024-07-06 11:16:02 +00:00
ashley
692fbf1d5d fix some stuff :3 2024-07-06 09:48:02 +00:00
ashley
c2625bd79d fix issue 2024-07-06 09:20:55 +00:00
ashley
9484df8780 FIX: Medium videoplayback having a skill issue :3 2024-07-05 18:59:54 +00:00
ashley
70221f1f41 fix some autosync issue lol 2024-07-05 17:18:38 +00:00
ashley
5be52a9d7b add error notices :3 2024-07-05 13:59:13 +00:00
ashley
8d60655fa7 oops 2024-07-03 23:14:01 +00:00
ashley
b765e8eb06 add masterpiece video :3 2024-07-03 23:11:22 +00:00
ashley
34c7b787e7 revert #91 bwaaaaa 2024-07-03 21:04:48 +00:00
ashley
5c62905ce5 Update src/libpoketube/init/pages-video.js 2024-07-03 21:00:40 +00:00
ashley
8cc53b114b put it to pt_version 2024-07-03 20:56:58 +00:00
ashley
9f52dbf33c bwa 2024-07-03 20:54:59 +00:00
ashley
2a59d97d62 use execSync instead of exec 2024-07-03 20:52:22 +00:00
ashley
63ac519d60 bwa 2024-07-03 20:36:31 +00:00
ashley
e28361bab3 Update html/poketube.ejs 2024-07-03 13:15:09 +00:00
ashley
38d4913834 oops 2024-07-03 10:49:12 +00:00
ashley
184c935251 test commit ignore pls 2024-07-03 10:47:56 +00:00
ashley
f7d614007a Update html/poketube.ejs 2024-07-03 10:32:42 +00:00
ashley
8071330fc6 Update src/libpoketube/init/pages-video.js 2024-07-03 10:30:50 +00:00
ashley
61db054304 bwa :3 2024-07-03 10:30:11 +00:00
ashley
41486bf8a3 oops 2024-07-03 10:22:46 +00:00
ashley
8b5bd59420 add itag_hd 2024-07-03 10:20:01 +00:00
ashley
9b7a612bc4 mb lmao 2024-07-03 08:52:10 +00:00
ashley
f6f0614d1f ehe 2024-07-03 08:51:19 +00:00
ashley
a5596afc4c add sugesstion api :3 2024-07-03 08:49:11 +00:00
ashley
57d19f5326 change api url :3 2024-07-03 08:47:01 +00:00
ashley
8f28bc4842 re-add videos :3 2024-07-03 08:06:38 +00:00
ashley
ab3b30d618 fix stuff :3 2024-07-03 08:05:43 +00:00
ashley
c64c800024 fix url :3 2024-07-03 07:00:30 +00:00
ashley
99cc288726 bwa 2024-07-02 22:09:57 +00:00
ashley
6658fdc5a7 add hd video!! 2024-07-02 22:09:19 +00:00
ashley
be0fd4ae31 Update html/poketube.ejs 2024-06-29 15:47:15 +00:00
ashley
7da7d37c00 Update html/poketube.ejs 2024-06-29 15:45:41 +00:00
ashley
a05ab40086 Update html/poketube.ejs 2024-06-26 14:59:19 +00:00
ashley
6fce8fde00 lol 2024-06-26 14:58:25 +00:00
ashley
5adb35ad94 TEST IGNORE 2024-06-26 14:54:34 +00:00
Ashley ////
b6dda9ac2d Merge pull request 'merger: Fix f string bug' (#96) from nin0dev/poke:main into main
Reviewed-on: https://codeberg.org/ashley/poke/pulls/96
2024-06-26 11:04:59 +00:00
nin0dev
75a6333f96 Update videobundler/README.md 2024-06-26 10:21:53 +00:00
nin0dev
ccd1eab93c Merge branch 'main' into main 2024-06-26 10:20:30 +00:00
nin0dev
dd517abe97 Fix f string bug 2024-06-26 06:17:04 -04:00
Ashley ////
f875214bee Merge pull request '(videobundler) implement streaming and fix unknown var issue' (#95) from nin0dev/poke:main into main
Reviewed-on: https://codeberg.org/ashley/poke/pulls/95
2024-06-26 07:59:30 +00:00
Ashley ////
1e0d1f77f9 Merge branch 'main' into main 2024-06-26 07:59:20 +00:00
nin0dev
e21718181e edited cmdline 2024-06-25 14:34:41 -04:00
Korbs
f903092531 Merge pull request 'Update instances.json' (#91) from blade10101/poke:main into main
Reviewed-on: https://codeberg.org/ashley/poke/pulls/91
2024-06-25 03:13:45 +00:00
blade10101
3ddbf9eed0 Merge branch 'main' into main 2024-06-25 01:25:56 +00:00
blade10101
9f42bcc8c6 Update instances.json 2024-06-25 01:24:19 +00:00
nin0dev
174e0564a7 apparently there are things to commit 2024-06-24 03:55:25 -04:00
nin0dev
ac1cf91adc Added separate browser handling
Chromium shits itself if i give it a streamed output from ffmpeg, so i just do old behavior of downloading file if its not a firefox browser
2024-06-24 03:54:37 -04:00
nin0dev
f16ebe8aa3 Merge branch 'main' into main 2024-06-24 07:20:16 +00:00
nin0dev
113fbd91f8 Added video streaming 2024-06-24 00:26:57 -04:00
nin0dev
6dafa572a4 stop writing bundler output to disk 2024-06-23 23:28:08 -04:00
Korbs
4d31a08fef Rename to "Poke", use PokeTube org's package, and note about ARM64/v7 2024-06-24 02:50:05 +00:00
nin0dev
08271eb2b4 nothing more 2024-06-23 16:11:12 -04:00
nin0dev
6826788925 Merge branch 'main' into main 2024-06-23 18:36:11 +00:00
nin0dev
a3bd8ec957 Fixed bug where video would get corrupted over not correctly getting id 2024-06-23 14:34:36 -04:00
Ashley ////
678e30c89a Merge pull request 'improve videobundler' (#93) from nin0dev/poke:main into main
Reviewed-on: https://codeberg.org/ashley/poke/pulls/93
2024-06-23 16:22:42 +00:00
nin0dev
70243b5b76 Updated videobundler to add concurrency and caching 2024-06-23 12:21:01 -04:00
nin0dev
00d21afbc5 Merge branch 'main' into main 2024-06-23 15:37:28 +00:00
Ashley ////
f7c961ae93 Merge pull request 'Add: Suggestions for Search Bar' (#94) from Korbs/poke:main into main
Reviewed-on: https://codeberg.org/ashley/poke/pulls/94
2024-06-23 11:50:08 +00:00
Korbs
0fdbeea5d5
Add suggestions to search bar (Landing) 2024-06-23 04:17:34 -04:00
Korbs
cf7cf98dd5 Merge pull request 'main' (#7) from ashley/poke:main into main
Reviewed-on: https://codeberg.org/Korbs/poke/pulls/7
2024-06-23 07:57:23 +00:00
blade10101
8ae73e932f Update instances.json 2024-06-23 02:00:33 +00:00
nin0dev
bbff542938 Added concurrency 2024-06-22 18:02:30 -04:00
nin0dev
a1e1c3e33f Merge branch 'main' into main 2024-06-22 21:59:42 +00:00
ashley
b7e6d55fcc o7 2024-06-22 21:12:05 +00:00
nin0dev
2adf9231a8 Merge branch 'main' into main 2024-06-22 19:31:40 +00:00
nin0dev
7fbd295c87 Fixed f string error 2024-06-22 15:30:35 -04:00
Ashley ////
bd6337cc5c Merge pull request 'Add videobundler' (#92) from nin0dev/poke:main into main
Reviewed-on: https://codeberg.org/ashley/poke/pulls/92
2024-06-22 19:03:56 +00:00
nin0dev
a5e9c3089b Added videobundler 2024-06-22 14:58:11 -04:00
blade10101
8a0f821eb3 Update instances.json 2024-06-22 02:41:52 +00:00
ashley
b9fe538feb Update README.md 2024-06-21 20:57:15 +00:00
ashley
052547350a Upload files to "css" 2024-06-20 21:57:57 +00:00
ashley
b91601209d bump version 2024-06-19 11:01:01 +00:00
ashley
03cc26622a revert teh thing 2024-06-17 15:39:51 +00:00
ashley
bbd409d28f block videos from tom macdonald in discover 2024-06-17 15:29:31 +00:00
ashley
ab16327c4b add author :3 2024-06-17 15:26:56 +00:00
ashley
771eaeeb8a new discover page :3 2024-06-17 15:17:09 +00:00
ashley
d06ac6a18e :3 2024-06-17 13:53:54 +00:00
ashley
904d6e79b2 add credits for narvi :3 2024-06-16 20:28:22 +00:00
ashley
0f0e142daf change api url :3 2024-06-15 11:52:43 +00:00
ashley
3c5710e192 woo :3 2024-06-15 11:49:13 +00:00
ashley
177d5f7a63 add discord link :3 2024-06-13 22:46:49 +00:00
ashley
daf8e56e8d oops 2024-06-13 22:25:08 +00:00
ashley
bcb14824d7 test new ui 2024-06-13 22:22:13 +00:00
ashley
de9a688f08 o7 2024-06-13 16:32:39 +00:00
Korbs
66ef437a98 Update link to SudoVanilla instance 2024-06-13 01:57:19 +00:00
ashley
ceae76f50e change api url :3 2024-06-11 22:12:49 +00:00
ashley
568a8dc53a mb sorry 2024-06-11 22:09:38 +00:00
ashley
5d4e5024ab test 2024-06-11 22:07:18 +00:00
ashley
269b02f0fc add user score!! to mobile 2024-06-11 22:02:58 +00:00
ashley
7ec38a9933 fix some issues :3 2024-06-10 22:27:48 +00:00
ashley
faa535bd0b mb lmao 2024-06-10 22:26:05 +00:00
ashley
8b1e2bc26b add user score!! 2024-06-10 22:24:47 +00:00
ashley
038b1f610f add dislikePercentage :3 2024-06-10 22:20:12 +00:00
ashley
bccb0a3928 ok am stupid 2024-06-10 21:43:06 +00:00
ashley
e979a3eaa4 rsufjgdujfgfdjghrf 2024-06-10 21:42:36 +00:00
ashley
941ade3d26 bleh 2024-06-10 21:41:53 +00:00
ashley
c5b0107746 mb 2024-06-10 21:34:25 +00:00
ashley
15fadaad41 ehe 2024-06-10 21:33:40 +00:00
ashley
2301392ba4 add likePercentage :3 2024-06-10 21:29:10 +00:00
ashley
a41df78164 new splash texts!! 2024-06-09 19:06:29 +00:00
ashley
9527c91e98 add discord server shortcut :3 2024-06-09 08:40:00 +00:00
ashley
12f43c3acf fix download :3 2024-06-09 08:32:16 +00:00
ashley
e1a38eba0e add search on web button to search :^ 2024-06-09 08:30:35 +00:00
ashley
ea7ca735a0 Update html/search.ejs 2024-06-09 08:26:19 +00:00
ashley
482b9ff833 testing 2024-06-09 08:24:07 +00:00
ashley
cb34181115 add discord link instead 2024-06-07 11:32:23 +00:00
ashley
06cf3cd7d6 mb sorry 2024-06-05 17:34:51 +00:00
ashley
ddad3663cf add girls 2024-06-02 19:50:06 +00:00
ashley
ae741e1c30 use github user content for instance fetch (related: #87) 2024-06-01 22:24:55 +00:00
Korbs
5ef6b6bfcd Convert to Codeberg and use ARM64 2024-06-01 21:18:14 +00:00
ashley
64096ff275 add embedtype :3 2024-06-01 05:44:11 +00:00
ashley
efc2a5d468 add this :3 2024-06-01 05:42:02 +00:00
ashley
0fc44971a1 check if param exists 2024-05-31 11:42:39 +00:00
ashley
ccd7f8e14b im sorry lol 2024-05-31 11:41:01 +00:00
ashley
8203cc8f81 add a 404 to games if it doesnt exist :3 2024-05-31 11:40:18 +00:00
ashley
d46e1dfb80 fix a > issue :3 2024-05-31 11:35:34 +00:00
ashley
f1029b0b93 oops lmao 2024-05-31 11:20:43 +00:00
ashley
024fad830a lets test this :3 2024-05-31 11:16:44 +00:00
ashley
7aa8181f6f add a check for artists 2024-05-31 11:08:45 +00:00
Ashley ////
9edfb9c216 Merge pull request 'Replace "poke.am1.shiggy.cloud" and "poke.am2.shiggy.cloud" with online instances.' (#88) from anotherkai/poke:replace-instances into main
Reviewed-on: https://codeberg.org/ashley/poke/pulls/88
2024-05-30 19:21:03 +00:00
anotherkai
9e79555951 Replace "poke.am1.shiggy.cloud" and "poke.am2.shiggy.cloud" with online instances. 2024-05-29 19:19:04 +00:00
ashley
dcb80d6a51 test ignore 2024-05-26 20:40:10 +00:00
ashley
a9e0acbd0a oops 2024-05-26 20:37:47 +00:00
ashley
9adc242445 fix the contunation bug :p 2024-05-26 20:34:06 +00:00
ashley
93ffc6ea42 bye 2024-05-25 23:08:48 +00:00
ashley
165c6a53ae fix height :3 2024-05-25 23:04:52 +00:00
ashley
8b52cddd22 add x.authorId 2024-05-25 05:46:31 +00:00
ashley
463cad66e6 Update html/playlist.ejs 2024-05-24 14:54:09 +00:00
ashley
2658ca8fdf my sorry 2024-05-24 14:50:18 +00:00
ashley
ca63841dee OOOPS SORRY 2024-05-24 14:49:00 +00:00
ashley
8449a2ea60 lol 2024-05-24 14:46:10 +00:00
ashley
7fcf4647cc add red-tape lol 2024-05-24 14:19:20 +00:00
ashley
ddb1bf189e add red tape lol 2024-05-24 14:11:54 +00:00
Korbs
13477769fb Switch to Codeberg for image host and provide arm64 build
https://codeberg.org/Korbs/-/packages/container/poke/amd64
https://codeberg.org/Korbs/-/packages/container/poke/arm64
2024-05-24 03:30:54 +00:00
Korbs
cf048ce7d8 Merge pull request 'main' (#6) from ashley/poke:main into main
Reviewed-on: https://codeberg.org/Korbs/poke/pulls/6
2024-05-24 03:26:58 +00:00
ashley
4d3e63970d test commit ignore pls 2024-05-23 20:12:24 +00:00
ashley
4515b3ca13 oops lmao 2024-05-23 20:09:45 +00:00
ashley
de4343a001 add warning for dis lol 2024-05-23 20:06:05 +00:00
ashley
6214f1a0a1 test commit ignore pls 2024-05-22 20:07:45 +00:00
ashley
76dae85866 remove vidoes if theres none duh 2024-05-22 20:03:55 +00:00
ashley
358a4d8311 redirect to shorts if theres no videos :3 2024-05-22 19:59:42 +00:00
ashley
43682f2a87 add verified checkmark :3 2024-05-22 13:47:26 +00:00
ashley
ba9820aee4 add new download button :3 2024-05-22 09:10:13 +00:00
ashley
32753be835 new banner :3 2024-05-20 14:47:46 +00:00
ashley
b47f66773b Fix search 2024-05-18 11:13:17 +00:00
ashley
733a99cdc4 add the discover button :3 2024-05-16 18:57:21 +00:00
ashley
99ef160678 add the discover button :3 2024-05-15 23:57:40 +00:00
ashley
ea60a07202 OOOPS SORRY 2024-05-15 23:53:21 +00:00
ashley
b10d91e1c0 add new header 2024-05-15 23:48:34 +00:00
ashley
0b86cb9a1f bump bundle version :3 2024-05-13 16:05:04 +00:00
ashley
06a9e61330 add toggleTheaterMode :3 2024-05-13 16:04:04 +00:00
ashley
54da43668d use theatermodeon 2024-05-13 16:00:39 +00:00
ashley
5ee8a6f847 oops 2024-05-13 15:50:20 +00:00
ashley
77248156a5 bump bundle version :3 2024-05-13 04:35:00 +00:00
Ashley ////
81e74bdfa3 Merge pull request 'Optimize Landing and Improve Theather Mode' (#87) from Korbs/poke:main into main
Reviewed-on: https://codeberg.org/ashley/poke/pulls/87
2024-05-13 04:30:47 +00:00
Korbs
79111259f6 Remove HD videos and keep 480p version on landing background 2024-05-12 18:26:57 -04:00
Korbs
e4263b18f3 Strink height of theather mode for 720p screens 2024-05-12 18:25:55 -04:00
Korbs
f23f9cfd20 Add error template to layouts folder 2024-05-12 18:25:33 -04:00
Korbs
e3b35a4426 Merge pull request 'main' (#5) from ashley/poke:main into main
Reviewed-on: https://codeberg.org/Korbs/poke/pulls/5
2024-05-12 19:27:39 +00:00
ashley
996221f39e oops 2024-05-12 18:47:09 +00:00
ashley
7df79c7da8 lol 2024-05-12 18:46:09 +00:00
ashley
9e2b8e39c4 mb lol 2024-05-11 20:49:15 +00:00
ashley
efc7e8a687 use javascript file 2024-05-11 20:48:52 +00:00
ashley
8ec890dea1 add a file for maps instead 2024-05-11 20:46:47 +00:00
ashley
c540ff27df add clock :3 2024-05-11 19:40:35 +00:00
ashley
a74dea4fb4 oops 2024-05-11 16:02:12 +00:00
ashley
0d128656b7 add funny easter egg :3 2024-05-11 16:00:06 +00:00
ashley
b1f011f907 add a rparamm 2024-05-11 02:54:08 +00:00
ashley
1735f548d6 AM DUMB SORRY 2024-05-11 02:40:23 +00:00
ashley
c49abee128 redirect to the account page :3 2024-05-11 02:37:03 +00:00
ashley
d7ef10b65e fix the XSS in user pages 2024-05-11 02:33:43 +00:00
ashley
75e4e37789 add download button to search :3 2024-05-11 01:20:51 +00:00
ashley
913a43e0cb final commmit hopefull 2024-05-11 00:12:22 +00:00
ashley
284ca61bb9 lets try this 2024-05-11 00:11:16 +00:00
ashley
2349a5f67f test commit ignore pls 2024-05-11 00:09:34 +00:00
ashley
f78344e756 make it 1.5 2024-05-11 00:06:11 +00:00
ashley
667abd4764 OOOPS SORRY 2024-05-11 00:04:47 +00:00
ashley
6134c84047 save every 5 seconds instead 2024-05-11 00:00:36 +00:00
ashley
1d07846314 lol 2024-05-10 23:17:49 +00:00
ashley
d25f416411 added read if cute splah 2024-05-10 23:16:31 +00:00
ashley
0d5494bff2 lol sorry 2024-05-10 21:41:46 +00:00
ashley
87ceaf1a8a fix stuff 2024-05-10 21:36:08 +00:00
ashley
93edc7411c hmm 2024-05-10 21:27:51 +00:00
ashley
026a060c3c mb lmao 2024-05-10 21:24:38 +00:00
ashley
3bdf5a5254 test ignore 2024-05-10 21:22:58 +00:00
ashley
a897c79243 test ignore 2024-05-10 21:09:09 +00:00
ashley
a58a01e1df revert teh thing 2024-05-10 19:59:34 +00:00
Ashley ////
e35d806f51 Merge pull request 'Format Poketube.ejs with Beautify' (#86) from Korbs/poke:main into main
Reviewed-on: https://codeberg.org/ashley/poke/pulls/86
2024-05-10 19:44:00 +00:00
Korbs
1caa2c150d Format with Beautify 2024-05-10 15:37:49 -04:00
ashley
d9ac3f21d0 add regex :3 2024-05-10 10:55:04 +00:00
ashley
78de660963 add regex for vid ids 2024-05-10 10:48:31 +00:00
Korbs
adf09e5dfa Merge pull request 'main' (#4) from ashley/poke:main into main
Reviewed-on: https://codeberg.org/Korbs/poke/pulls/4
2024-05-10 07:09:15 +00:00
ashley
62d7def602 test commit ignore pls 2024-05-09 15:03:50 +00:00
ashley
fd26924324 add commmit field 2024-05-09 14:58:20 +00:00
ashley
3a3cacb6d0 add poke.blahai.gay 2024-05-09 14:37:29 +00:00
ashley
c6cb1334b2 oops 2024-05-09 13:43:29 +00:00
ashley
bcdab77856 more poketube --> poke 2024-05-08 21:19:47 +00:00
ashley
ba5fa3f4e8 bump bundle version :3 2024-05-08 13:57:37 +00:00
ashley
8aaa41de3d remove IE support :p 2024-05-08 13:56:04 +00:00
Korbs
43cf7dc468 Merge pull request 'main' (#3) from ashley/poke:main into main
Reviewed-on: https://codeberg.org/Korbs/poke/pulls/3
2024-05-08 05:56:03 +00:00
ashley
cdcec542d0 bump version!! :3 2024-05-06 15:36:09 +00:00
ashley
00e6597265 lol mb 2024-05-05 18:21:24 +00:00
ashley
0f025be830 ad stuff :.3 2024-05-05 18:13:56 +00:00
ashley
44f6697b02 mb 2024-05-05 15:46:56 +00:00
ashley
8a31d194fa oops 2024-05-05 15:45:57 +00:00
ashley
bc917b4727 bump version!! :3 2024-05-05 15:45:33 +00:00
ashley
ab96d0fa34 fix video search 2024-05-05 12:57:16 +00:00
ashley
ae935825f4 add stuff 2024-05-05 12:19:28 +00:00
ashley
8c3692d94a add ascii txt :3 2024-05-04 16:41:41 +00:00
ashley
19fa78253c add poke logo 2024-05-04 16:33:55 +00:00
ashley
b7907c8459 add info cards api 2024-05-04 10:10:42 +00:00
ashley
bc9643c5e7 lint code :3 2024-05-04 05:31:35 +00:00
ashley
a0cebd409c return 404 if errors 2024-05-04 05:28:55 +00:00
ashley
d4521153b1 lint code :3 2024-05-04 05:28:05 +00:00
ashley
d451a834a2 new 404 string 2024-05-04 05:16:16 +00:00
ashley
27b7a248e9 am dumb 2024-05-02 22:17:19 +00:00
ashley
fa5a1b9a37 change to eu proxy 2024-05-02 22:03:26 +00:00
ashley
35b89add2f add new splash hehe 2024-05-02 03:48:30 +00:00
ashley
b041062c6a add new screenshot :3 2024-05-02 03:33:38 +00:00
ashley
94a1e3d4c0 add new screenshot :3 2024-05-02 03:32:49 +00:00
ashley
e2adc76b7d bump bundle version :3 2024-05-02 02:13:04 +00:00
ashley
98b310342e fix stats for nerds :p 2024-05-02 02:12:44 +00:00
ashley
def705d37a fix january in school 2024-05-02 02:06:22 +00:00
ashley
4ba2b7c466 bump bundle version :3 2024-05-02 02:03:15 +00:00
ashley
10ef769b6c bump bundle version :3 2024-05-02 02:02:02 +00:00
ashley
66ee2e0a8d dont display theather mode without js 2024-05-02 02:00:23 +00:00
Ashley ////
3e5ba0da0b Merge pull request 'Minor UI Improvements' (#82) from Korbs/poke:main into main
Reviewed-on: https://codeberg.org/ashley/poke/pulls/82
2024-05-02 01:53:27 +00:00
Korbs
c46d95c196 Clean up area above searched videos 2024-05-01 17:32:37 -04:00
Korbs
83d06e8584 Clean up area under video and add Theater toggle 2024-05-01 17:32:08 -04:00
Korbs
7db35edfda Merge pull request 'main' (#2) from ashley/poke:main into main
Reviewed-on: https://codeberg.org/Korbs/poke/pulls/2
2024-05-01 21:31:28 +00:00
ashley
1938f35856 fix silly issues 2024-05-01 16:11:56 +00:00
ashley
c8b1c644d1 my sorry 2024-05-01 16:10:31 +00:00
ashley
d56e224f3b fix result 2024-05-01 16:08:14 +00:00
ashley
a8b35495f6 make search work again!! 2024-05-01 16:05:41 +00:00
ashley
6f1af7c36d oops 2024-05-01 13:46:00 +00:00
ashley
7a6a1e99a5 add more splash texts :3 2024-05-01 13:45:11 +00:00
ashley
1077a5ef60 remove old icons :3 2024-05-01 12:02:11 +00:00
ashley
35b9412e7c my bad lmao- 2024-05-01 09:33:43 +00:00
ashley
f5d8863236 add more splash texts :3 2024-05-01 09:32:28 +00:00
ashley
f941957dea remove icons lol 2024-05-01 09:00:01 +00:00
ashley
ff45b70bcd add proxyurl 2024-05-01 06:41:06 +00:00
ashley
b8dad3e52b add fontawsome :3 2024-05-01 06:40:12 +00:00
ashley
477ba74e83 Update html/landing.ejs 2024-05-01 06:31:56 +00:00
ashley
1ed3fe20b1 change parts of readme 2024-05-01 06:27:27 +00:00
Ashley ////
11e0cf2580 Merge pull request 'Update Landing and README' (#81) from Korbs/poke:main into main
Reviewed-on: https://codeberg.org/ashley/poke/pulls/81
2024-05-01 06:25:18 +00:00
ashley
21ae44ca15 add splash text :3 2024-05-01 06:24:48 +00:00
ashley
aa9daac6f4 add text lol :3 2024-05-01 06:22:58 +00:00
Korbs
1f4d9c3282 Add links to bottom cards 2024-04-30 23:12:50 -04:00
Korbs
98c1d4a638 Merge pull request 'main' (#1) from ashley/poke:main into main
Reviewed-on: https://codeberg.org/Korbs/poke/pulls/1
2024-05-01 03:10:00 +00:00
Korbs
b634646d4b Update parts of README 2024-04-30 23:07:06 -04:00
Korbs
d85f362544 Remove old file 2024-04-30 23:06:54 -04:00
Korbs
cc3b9fd5d6 Add header partial 2024-04-30 23:06:19 -04:00
Korbs
82a5cb5e3a Add cart partial 2024-04-30 23:06:09 -04:00
Korbs
f64e1d21df Update landing page 2024-04-30 23:05:57 -04:00
Korbs
c488771d5d Add secure and verify to landing 2024-04-30 23:04:20 -04:00
ashley
24c51c85a3 use value instead lol 2024-04-30 13:47:11 +00:00
ashley
6822e5fc1e add channel search placeholder 2024-04-30 13:40:37 +00:00
ashley
cdd0cbf72b add more redirects :3 2024-04-29 21:11:50 +00:00
Ashley ////
c80793cb2e test this 2024-04-27 18:47:23 +00:00
Ashley ////
312f717713 add escape buttono to close the popup 2024-04-27 18:31:05 +00:00
Ashley ////
2e7a43ad7c oops 2024-04-27 15:12:42 +00:00
Ashley ////
e2dd39b5e3 css fixes 2024-04-27 15:11:23 +00:00
Ashley ////
82e838438d add the div 2024-04-27 15:07:51 +00:00
Ashley ////
f0fd776104 mb agaij 2024-04-27 15:06:01 +00:00
Ashley ////
a6383824b2 oops 2024-04-27 15:04:21 +00:00
Ashley ////
9f3ec29813 add tags for channels 2024-04-27 15:01:21 +00:00
Ashley ////
4b7d4d2f43 add margin 2024-04-27 08:52:02 +00:00
Ashley ////
8a5d09e7d6 test commit ignore pls 2024-04-27 08:50:28 +00:00
Ashley ////
0180cc7ab0 test ignore 2024-04-27 08:48:13 +00:00
Ashley ////
9b10540fb9 test commit ignore pls 2024-04-27 08:45:37 +00:00
Ashley ////
6da10df705 test commit ignore pls 2024-04-27 08:42:20 +00:00
Ashley ////
1640f7e66c test author verified,, 2024-04-27 08:39:10 +00:00
Ashley ////
c611d23b3d use usa proxy :3 2024-04-27 08:01:15 +00:00
Ashley ////
5b4b454f92 set to invidious proxy 2024-04-27 08:00:26 +00:00
Ashley ////
3cbdec493b lol 2024-04-26 18:50:10 +00:00
Ashley ////
7eea8c3cc0 use kuylars proxy for now 2024-04-26 18:40:53 +00:00
Ashley ////
81fcbe2be0 OOOPS SORRY 2024-04-26 18:34:23 +00:00
Ashley ////
1231ddb1b3 Update src/libpoketube/libpoketube-dislikes.js 2024-04-26 18:29:53 +00:00
Ashley ////
ffd18e8629 oops 2024-04-26 18:24:58 +00:00
Ashley ////
952c1f453c fix videoplayback 2024-04-26 18:23:10 +00:00
Ashley ////
49c03bf3c0 mhm 2024-04-26 15:50:27 +00:00
Ashley ////
35e06ef2e8 lets ee 2024-04-26 15:46:52 +00:00
Ashley ////
d445e462c5 fix videos :3 2024-04-26 15:42:06 +00:00
Ashley ////
23e742697a h 2024-04-25 18:37:22 +00:00
Ashley ////
38a624bd48 Update src/libpoketube/libpoketube-core.js 2024-04-25 18:20:06 +00:00
Ashley ////
b5e34674eb Update src/libpoketube/libpoketube-core.js 2024-04-25 18:15:11 +00:00
Ashley ////
1c6673f62e use config.invidapi instead 2024-04-25 18:05:45 +00:00
Ashley ////
fe86bf06ca do 2 2024-04-25 17:58:49 +00:00
Ashley ////
3270d2b5a0 mb 2024-04-25 17:53:54 +00:00
Ashley ////
b9992fc0df try this 2024-04-25 17:48:30 +00:00
Ashley ////
043406f0d5 lets see 2024-04-25 17:37:00 +00:00
Ashley ////
68d54e7ea1 do 2 2024-04-25 17:35:05 +00:00
Ashley ////
a4df679439 test commit ignore pls 2024-04-25 17:33:36 +00:00
Korbs
2b35a27252 Remove Vern's instance 2024-04-24 23:59:57 +00:00
Ashley ////
a68fcde7f9 test ignore 2024-04-24 15:44:33 +00:00
Ashley ////
61fb7ee345 lol 2024-04-24 14:20:08 +00:00
Ashley ////
6a83a1343e fix a issue :3 2024-04-23 19:53:28 +00:00
Ashley ////
32924854b6 mb 2024-04-23 15:36:36 +00:00
Ashley ////
d019ad69ed o7 2024-04-23 15:02:59 +00:00
Ashley ////
1f5a286195 mb lmao 2024-04-23 14:13:32 +00:00
Ashley ////
743229525a use invidious comments 2024-04-23 14:08:44 +00:00
Ashley ////
928a686cf5 bring back invidious comments 2024-04-23 14:06:02 +00:00
Ashley ////
614e38600b oops 2024-04-23 13:48:21 +00:00
Ashley ////
e54eb5c764 bunp undici 2024-04-23 13:44:57 +00:00
Ashley ////
38481ef63d test commit ignore pls 2024-04-23 10:48:11 +00:00
Ashley ////
6f660d5bff use the url instead 2024-04-23 10:27:33 +00:00
222 changed files with 22782 additions and 19807 deletions

View File

@ -1,19 +1,19 @@
kind: pipeline
type: exec
name: Build and Push Docker Image (Quay)
name: Build and Push Docker Image
platform:
os: linux
arch: amd64
arch: arm64
steps:
- name: Build
environment:
QUAY_USERNAME:
from_secret: QUAY_USERNAME
QUAY_PASSWORD:
from_secret: QUAY_PASSWORD
CODEBERG_USERNAME:
from_secret: CODEBERG_USERNAME
CODEBERG_PASSWORD:
from_secret: CODEBERG_PASSWORD
commands:
- echo $QUAY_PASSWORD | docker login quay.io --username $QUAY_USERNAME --password-stdin
- docker build -t quay.io/sudovanilla/poketube .
- docker push quay.io/sudovanilla/poketube
- echo $CODEBERG_PASSWORD | docker login codeberg.org --username $CODEBERG_USERNAME --password-stdin
- docker build -t codeberg.org/korbs/poke:arm64 .
- docker push codeberg.org/korbs/poke:arm64

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ yarn.lock
package-lock.json
.env
json.sqlite
config.json

View File

@ -1,169 +1,9 @@
# Contributor Covenant Code of Conduct / PokeTube code of conduct
### Code of Conduct
## Our Pledge
By accessing, using, or contributing to Poke, you agree that all actions, behaviors, and interactions are subject to and governed by the **Poke Project Code of Conduct**, available at:
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
https://poketube.fun/code-of-conduct
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
iamashley@duck.com (E-mail) https://discord.gg/pfKSQ3pMfW (Discord server) https://matrix.to/#/#poke:vern.cc (matrix space) and https://rvlt.gg/poke (revolt server).
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Additional Terms for Poketube
**TL;DR**: You are encouraged not to edit or remove these terms from Poketube. While you have the freedom to make changes in your Poketube fork, if you choose to modify this document, please refrain from using the title "Poketube Code of Conduct." Everyone can copy and share this document as is, but making changes is allowed with the aforementioned condition. If your chosen alternative code of conduct doesn't include provisions against hate speech, inappropriate behavior, anti-immigrant sentiments, far-right, or authoritarian content, it's not recommended.
1. Definitions
- **"Alternative Code of Conduct"**: This refers to a code of conduct other than the Contributor Covenant Code of Conduct.
- **"Free Software"**: The definition of "free software" is in accordance with the GNU GPL version 3. You can find a complete copy of it in the LICENSE file.
- **Hate Speech**: Hate speech includes any communication, whether written, spoken, or expressed in any form, that promotes discrimination, hostility, or violence against individuals or groups based on attributes such as race, ethnicity, gender, religion, or other protected characteristics.
- **Inappropriate Behavior**: Inappropriate behavior encompasses actions or expressions that create an unwelcome, hostile, or offensive environment for others, such as harassment, intimidation, or bullying.
- **Authoritarianism**: Authoritarianism is characterized by an emphasis on strong central authority, limited individual freedoms, and restrictions on democratic processes. Content or behavior that promotes authoritarian principles, suppresses freedom of speech, individual rights, or democratic values is strongly discouraged.
- **Protected characteristics** include attributes such as race, ethnicity, gender, religion, sexual orientation, disability, and other traits or qualities safeguarded from discrimination by relevant laws and regulations. This defines what is meant by "protected characteristics" in the context of this document.
2. Terms
NOTE: The Contributor Covenant Code of Conduct already includes provisions on some of these issues. Our intention is to provide a more defined and explicit statement regarding these prohibitions to ensure a clear and inclusive community environment.
YOU ARE NOT ENCOURAGED TO EDIT, REMOVE, OR ALTER THE TERMS OF THIS FILE. However, should you choose to make changes, please avoid using the title "Poketube Code of Conduct." Removing this file from your Poketube fork is allowed. Everyone, without exception, is permitted to create unmodified copies of this document and distribute it as is; however, modifications to this document are allowed with the aforementioned condition.
It is of paramount importance to emphasize that the promotion or glorification of anti-immigrant sentiments, the alignment with far-right ideologies, Islamophobia, or any form of religious discrimination is strongly discouraged within the scope of Poketube. We maintain a stance against such content, which includes material that discriminates against immigrants, promotes hatred or hostility towards religious groups, or actively supports extremist beliefs associated with far-right ideologies. This stance is encouraged and non-binding.
We believe in fostering an environment that is inclusive, respectful, and free from discrimination or the promotion of extremist ideologies. As such, any content found in violation of this encouragement will be addressed promptly and appropriately.
THE CLARITY AND FORCE OF THIS STATEMENT ARE INTENDED TO ENCOURAGE CLEAR GUIDELINES: ANTI-IMMIGRANT SENTIMENTS, FAR-RIGHT IDEOLOGIES, ISLAMOPHOBIA, RELIGIOUS DISCRIMINATION, MISOGYNY, AND SEXISM ARE STRONGLY DISCOURAGED AND NOT PREFERRED WITHIN OUR COMMUNITY. OUR HOPE IS TO MAINTAIN A RESPECTFUL AND INCLUSIVE ATMOSPHERE FOR ALL, REGARDLESS OF THEIR BACKGROUND, BELIEFS, OR IDENTITY.
These terms may be subject to change, and any updates will be communicated to the Poketube community. Changes to these terms will be communicated to users.
**3. Application of GNU Kind Communication Policy**
These terms also align with the principles outlined in the [GNU Kind Communication Policy](https://www.gnu.org/philosophy/kind-communication.html), which encourage respectful and inclusive communication within the Poketube community.
It is crucial to note that we respect the diverse opinions and beliefs of our users.
***Additional terms end lol***
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
The provisions, standards, and expectations set forth therein apply in full to your use of this service and its related platforms.
Any breach of these terms may result in limitation or termination of access as outlined in that document. More sections or updates may be introduced in the future without prior notice. Continued use constitutes acceptance of any such changes.

13
LICENSE-APPSTORE.md Normal file
View File

@ -0,0 +1,13 @@
The developers are aware that the terms of service that apply to apps distributed via Apple's App Store services and similar app stores may conflict
with rights granted under the Poke license, the GNU General
Public License, version 3.
The copyright holders of the Poke project do not wish this conflict to prevent the otherwise-compliant distribution of derived apps via the App Store and similar app stores.
Therefore, we have committed not to pursue any license
violation that results solely from the conflict between the GNU GPLv3
and the Apple App Store terms of service or similar app stores. In
other words, as long as you comply with the GPL in all other respects,
including its requirements to provide users with source code and the
text of the license, we will not object to your distribution of the
Poke project through the App Store.

249
README.md
View File

@ -1,178 +1,119 @@
<h1 align="center">
<a href="https://poketube.fun/watch?v=9sJUDx7iEJw&quality=medium&=sjohgteojgytrueugtye4jhtytjrjnyıı">
<img src="https://poketube.fun/css/logo-poke.svg" width="400">
<a href="http://www.defectivebydesign.org/drm-free">
<img src="https://static.fsf.org/dbd/label/DRM-free%20label%20120.en.png"
alt="DefectiveByDesign.org"
width="65" height="65" border="0" align="middle" /></a>
</a>
<p>The Ultimate Privacy App</p>
</h1>
<div align="center">
<span> Be Anonymous watching epic videos, searching thingys on the interwebs and listening to music on poke - the free privacy front end!</span>
<span>"Since you work on poke, Are you in touch with its lead developer "Jose marchasi"? <br>
-RMS
Stallman though poke was GNU poke lmaoooo
</span>
</div>
<a href="https://poketube.fun/watch?v=QZfH7cFp3Ys">
<img src="https://poketube.fun/css/logo-poke.svg" width="400">
</a>
<a href="http://www.defectivebydesign.org/drm-free">
<img src="https://static.fsf.org/dbd/label/DRM-free%20label%20120.en.png"
alt="DRM Free" width="65" height="65" border="0" align="middle" />
</a>
<p>THE PRIVACY APP OF YOUR DREAMS! :3</p>
</h1>
<div align="center">
[Welcome!](#welcome)&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;[Features](#features)&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;[No non-free codec needed](#no-non-free-codec-needed-3)&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;[Hosting Poke~](#hosting-poketube)&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;[Poke community!](#poketube-community)&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;[The Legal Stuff (boring tbh)](#the-legal-stuff-boring-tbh)
<a href="https://status.poketube.fun" target="_blank">
<img
width="170"
src="https://api.netweak.com/status-badges/K2LY9"
alt="Netweak status badge"
/>
</a><br>
<a href="https://tosdr.org/en/service/7114">
<img src="https://shields.tosdr.org/en_7114.svg"/>
</a>
<img src="https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg"> <a href="./LICENSE"><img src="https://img.shields.io/badge/License-GPL--3-FF6666" alt="License - GPL-3"></a>
<a href="https://ci.poketube.fun/ashley/poke">
<img src="https://ci.poketube.fun/api/badges/ashley/poke/status.svg" />
</a>
<p>Be anonymous while watching videos, gaming, and listening to music on Poke - the free privacy front-end!</p>
</div>
<div align="center">
<a href="#welcome">Welcome!</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="#features">Features</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="#no-non-free-codec-needed">No Non-Free Codec</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="#hosting-poke">Hosting</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="#poke-community">Community</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="#legal">Legal</a>
<br><br>
<img src="https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg" alt="Stand with Ukraine">
<img src="https://codeberg.org/ashley/pages/raw/branch/main/images/trans-badge.svg">
<img src="https://codeberg.org/ashley/pages/raw/branch/main/images/free-Palestine.svg"><br>
<a href="https://codeberg.org/ashley/poke/src/branch/main/LICENSE">
<img src="https://img.shields.io/badge/License-GPL--3.0--or--later-FF6666" alt="GPL-3.0-or-later SPDX License">
</a>
<img src="https://img.shields.io/badge/Powered%20by-GNU/Linux-333333?logo=gnu" alt="GNU/Linux">
<img src="https://img.shields.io/badge/Web%20Server-Nginx-009639?logo=nginx&logoColor=white" alt="Nginx">
<br> <img src="https://img.shields.io/badge/Backend-Express.js-000000?logo=express" alt="Express.js">
<img src="https://img.shields.io/badge/Frontend-EJS-F4D03F" alt="EJS">
<img src="https://img.shields.io/badge/Language-JavaScript-F7DF1E?logo=javascript&logoColor=black" alt="JavaScript">
</div>
<br>
<img src="https://codeberg.org/ashley/pages/raw/branch/main/Untitled.webp">
## Welcome!
This is the source code of Poke (formerly PokeTube), the privacy-friendly youtube front-end built with the InnerTube API!
Welcome to Poke (formerly PokeTube), the privacy-friendly YouTube front-end built with the invidious API!
## Features
| <img width="100%" style="border-radius: 24px" src="./css/README_RYD.png"> | <div style="text-align: left"><h3>🔙 Built-In Return YouTube Dislikes</h3>See the dislikes from *returnyoutubedislike* - because sometimes you need to know how bad that video really is :3</div> |
| - | - |
| <div style="text-align: right"><h3>📱 PWA Support</h3>With PWA Support, you can install Poke on your mobile device. Now you can pretend to be productive while watching cat videos on the go, mreoww! :3</div> | <img width="100%" style="border-radius: 24px" src="./css/README_PWA.jpg"> |
| <h3>🎨 Customize</h3>Customize Poke however you want. Make it as unique as your taste in memes. | <h3>📥 Accounts</h3>Suscribe (yes Suscribe hehe sussy baka) to whaever channel you want! </div> |
| <h3>☁️ PokeWeather</h3>Check The weather privately on PokeWeather!!! | <h3>🎶 And...</h3>Ambient mode, HQ audio, and even more! :3 |
## No Non-Free Codec Needed
Poke uses Free Software codecs! No non-free components included :3
## Hosting Poke
### NodeJS
1. **Install Packages**
- Fedora/RHEL GNU/linux: `$ sudo dnf install git make gcc libcurl nodejs python libcurl g++ curl-config`
- Debian/Ubuntu GNU/linux: `$ sudo apt install git make gcc libcurl4-openssl-dev nodejs npm python g++`
- Alpine Linux (non-gnu): `$ apk add git nodejs npm python make gcc g++ libcurl curl-dev`
2. **Clone Repo**
- Codeberg: `$ git clone https://codeberg.org/ashley/poke.git`
- GitHub: `$ git clone https://github.com/ashley0143/poke.git`
reccomended unoffical mirrors:
- git.lgbt: `$ git clone https://git.lgbt/mirror/poke.git` [sync every 10mins]
- nin0git :`$ git clone https://git.nin0.dev/mirrors/poke.git` [sync every 10mins]
not reccomended, unstable
- none!!! yippee
<h1>Features</h1>
<h3>Return Youtube Dislikes Built-in </h3>
<img src="https://autumn.revolt.chat/attachments/0JlGwlnJdMcQwc6qzoo2qpmMAOWbrYMZ9vDX50l80r/resim.png">
<p>See the dislikes from returnyoutubedislike!</p>
3. **Install Dependencies**
- `$ cd poke`
- `$ npm install`
<h3>Customize</h3>
Customize Poketube However you want :3
4. **Start Server**
- `$ node server.js`
<h3>PWA</h3>
Install PokeTube as a pwa on your mobile device :3
Congrats! Poke should now be running on `localhost:6003`! 🎉
## Poke Community
<h3>Accounts </h3>
Suscribe (yes Suscribe hehe sussy baka) to whaever channel you want!
Join us on [Discord](https://discord.poketube.fun/) ! I promise we're cool! <3
<h3>Web Search </h3>
Search the web privatly on poketube :3
<br>
<h3>And... </h3>
<p>
Ambient mode, HQ audio And Even more!!!!!
</p>
## Written by humans - not gpt
poke is made by hard-working hoomans - not gpt :3<br>
<a href="https://notbyai.fyi"><img src="https://cdn.glitch.global/d68d17bb-f2c0-4bc3-993f-50902734f652/Written-By-Human-Not-By-AI-Badge-white.svg?v=1696672202901" alt="Written by Humanss, Not by AI"></a>
## No Non-free codec needed :3
Poke uses openh264 which is free software! poketube does not inculude non free stuff owowowoow!!!!
you can view the source code of the openh264 codec in this repo :3 --> https://github.com/cisco/openh264.git
PLEASE NOTE THAT THIS SOFTWARE MAY INCULUDE CODECS THAT IN CERTAIN COUNTRIES MAY BE COVERED BY PATENTS OR HAVE LEGAL ISSUES. PATENT AND COPYRIGHT LAWS OPERATE DIFFERENTLY DEPENDING ON WHICH COUNTRY YOU ARE IN. PLEASE OBTAIN LEGAL ADVICE IF YOU ARE UNSURE WHETHER A PARTICULAR PATENT OR RESTRICTION APPLIES TO A CODEC YOU WISH TO USE IN YOUR COUNTRY.
## Hosting Poke~
### IMPORTANT
Before you host, if ur server is in usa, set the proxylocation to `USA` (which is the default) - if you use any eu server set it to `EU` instead to make videos load faster
### With NodeJS
- To self host your own Poke instance, you'll need the following:
- [Node.js](https://nodejs.org/en/download/)
- [npm](http://npmjs.com) (Included with Node.js)
Once you have everything, clone our repo:
```
git clone https://codeberg.org/ashley/poke.git
```
You can also clone using our Github mirror if you'd prefer:
```
git clone https://github.com/ashley0143/poke.git
```
Or you can use our forgejo instance:
```
git clone https://git.poketube.fun/ashley/poke.git
```
Now, install the needed dependencies within the Poke folder:
( go to the folder by running cd poke)
```
npm install
```
Once everythings installed, start your server with the following command:
```
node server.js
```
Congrats, Poketube should now be running on `localhost:6003`!
### With Docker
Create a new directory for PokeTube:
```
mkdir poketube && cd poketube
```
Download the docker compose and config file:
```
curl -O https://codeberg.org/Ashley/poke/raw/branch/main/docker-compose.yml
```
Run PokeTube:
```
docker compose up -d
```
PokeTube should be running on `http://localhost:6003`.
The port can be changed with the config file you downloaded, just change the `server_port` option.
## Hosting Image Proxy
see [here](https://codeberg.org/Ashley/poke/src/branch/main/january) :3
just uhh change the url in config.json to ur image proxy
## Poke community!
Join the community on [revolt](https://rvlt.gg/poketube) or [matrix](https://matrix.to/#/#poke:vern.cc) :3
or if u like fedi, we host [PokeSocial](https://social.poketube.fun) as well :3
## The Legal Stuff (boring tbh)
the main parts of the project is Under GPL-3.0-OR-LATER :3
see https://poketube.fun/license
see the each sections LICENSE tho!!
Copyleft 2021-202x Poke Project
see the each sections LICENSE tho!!
Copyleft 2021-202x Poke Project of poke initative, mostly ashley0143
Trans rights!
https://initiative.poketube.fun/
poke proudly does not support the ["source first"](https://sourcefirst.com/) or ["open source"](https://opensource.org) movement :3
we support the free software movement (fsf.org)
please dont use the term "open source", see gnu.org/not-open-source for more information on why its a wrong term to use!
[Code Of conduct](https://codeberg.org/Ashley/poke/src/branch/main/CODE_OF_CONDUCT.md)
<hr>
[Privacy Policy](https://poketube.fun/privacy)
TL;DR: we dont collect or share your personal info, that's it lol.
We additionally use the GNU Coding Standard, see [this link.](https://www.gnu.org/prep/standards)
<div>
<h3>some parts of poketube.fun is proudly hosted on glitch.com since <i>2020</i> </h3>
<a href="https://glitch.com/"><img src="https://cdn.glitch.global/d68d17bb-f2c0-4bc3-993f-50902734f652/glitch-fastly-lock-up.svg?v=1696671148266"></a><br><hr>
<a href="https://gnu.org/not-open-source"><img width="200" src="https://autumn.revolt.chat/attachments/eNpfwV2C1_wudONe43YCvWr-4vbvLpG78HbuXgOYfO"></a>
</div>
TL;DR: we dont collect or share your personal info that's it lol
<hr>
<p align="center"> <a href="https://www.amd.com/en/products/processors/server/epyc/7003-series/amd-epyc-7543.html"> <img width="65" height="65" src="https://codeberg.org/ashley/pages/raw/branch/main/amd.jpeg" alt="AMD EPYC"> </a> <a href="https://ubuntu.com/server"> <img width="65" height="65" src="https://res.cloudinary.com/canonical/image/fetch/f_auto,q_auto,fl_sanitize,w_317/https%3A%2F%2Fassets.ubuntu.com%2Fv1%2Ff76dd871-ubuntu-certified.png" alt="Ubuntu Certified"> </a> </p> <p align="center"> <small>Poke is proudly powered by AMD + Ubuntu servers!!!!!!!!!! (via Skrime Hosting). Parts of Poke ran on Glitch.com from <i>20212023</i>.</small> </p> <p align="center"> <a href="https://glitch.com/"> <img src="https://cdn.glitch.global/d68d17bb-f2c0-4bc3-993f-50902734f652/glitch-fastly-lock-up.svg" alt="Glitch logo"> </a> </p>

25
ascii_txt.txt Normal file
View File

@ -0,0 +1,25 @@
_____ _______ _____ _____
/\ \ /::\ \ /\ \ /\ \
/::\ \ /::::\ \ /::\____\ /::\ \
/::::\ \ /::::::\ \ /:::/ / /::::\ \
/::::::\ \ /::::::::\ \ /:::/ / /::::::\ \
/:::/\:::\ \ /:::/~~\:::\ \ /:::/ / /:::/\:::\ \
/:::/__\:::\ \ /:::/ \:::\ \ /:::/____/ /:::/__\:::\ \
/::::\ \:::\ \ /:::/ / \:::\ \ /::::\ \ /::::\ \:::\ \
/::::::\ \:::\ \ /:::/____/ \:::\____\ /::::::\____\________ /::::::\ \:::\ \
/:::/\:::\ \:::\____\ |:::| | |:::| | /:::/\:::::::::::\ \ /:::/\:::\ \:::\ \
/:::/ \:::\ \:::| ||:::|____| |:::| |/:::/ |:::::::::::\____\/:::/__\:::\ \:::\____\
\::/ \:::\ /:::|____| \:::\ \ /:::/ / \::/ |::|~~~|~~~~~ \:::\ \:::\ \::/ /
\/_____/\:::\/:::/ / \:::\ \ /:::/ / \/____|::| | \:::\ \:::\ \/____/
\::::::/ / \:::\ /:::/ / |::| | \:::\ \:::\ \
\::::/ / \:::\__/:::/ / |::| | \:::\ \:::\____\
\::/____/ \::::::::/ / |::| | \:::\ \::/ /
~~ \::::::/ / |::| | \:::\ \/____/
\::::/ / |::| | \:::\ \
\::/____/ \::| | \:::\____\
~~ \:| | \::/ /
\|___| \/____/

View File

@ -0,0 +1 @@
haii!! these files are made for the poke's server - if u wanna use them on ur server u might have to change the directories :p

View File

@ -0,0 +1,40 @@
#!/bin/bash
#
# Copyright (C) 2024-20xx Poke! (https://codeberg.org/ashley/poke)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
scriptDir=$(dirname "$(readlink -f "$0")")
output=$(docker run quay.io/invidious/youtube-trusted-session-generator)
visitor_data=$(echo "$output" | grep -oP '(?<=visitor_data: )[^ ]+')
po_token=$(echo "$output" | grep -oP '(?<=po_token: )[^ ]+')
if [ -z "$visitor_data" ] || [ -z "$po_token" ]; then
echo "Error: Could not generate visitor_data or po_token."
exit 1
fi
sed -i "s/visitor_data: .*/visitor_data: $visitor_data/g" $scriptDir/../services/invidious/docker-compose.yml
sed -i "s/po_token: .*/po_token: $po_token/g" $scriptDir/../services/invidious/docker-compose.yml
cd $scriptDir/../services/invidious
docker compose up -d
echo "Successfully updated visitor_data and po_token on Invidious."

View File

@ -0,0 +1,164 @@
#!/bin/bash
#
# Copyright (C) 2024-20xx Poke! (https://codeberg.org/ashley/poke)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Function to generate a random Chrome version
generate_random_chrome_version() {
major=$((RANDOM % 100 + 1)) # Major version 1-99
minor=$((RANDOM % 100)) # Minor version 0-99
build=$((RANDOM % 10000)) # Build version 0-9999
patch=$((RANDOM % 100)) # Patch version 0-99
echo "$major.$minor.$build.$patch"
}
restart_services() {
echo "Restarting services..."
# Navigate to the script directory
scriptDir=$(dirname "$(readlink -f "$0")")
cd "$scriptDir/../services/invidious" || { echo "Error: Failed to navigate to $scriptDir/../services/invidious"; exit 1; }
docker compose down
echo "Services stopped. Restarting..."
docker compose up -d
echo "Services restarted successfully."
/home/qt/globe/scripts/inv-update-token.sh
}
fetch_playlist() {
local playlist_id="$1"
response=$(curl -s -w "%{http_code}" -o /tmp/playlist_data.json "https://invid-api.poketube.fun/api/v1/playlists/${playlist_id}")
if [ "$response" -eq 502 ] || [ "$response" -eq 500 ] || [ "$response" -eq 403 ]; then
echo "Error: Failed to fetch playlist data. HTTP Status: $response"
restart_services
return 1
elif [ "$response" -ne 200 ]; then
echo "Error: Failed to fetch playlist data. HTTP Status: $response"
return 1
fi
}
extract_video_ids() {
local json_data="$1"
video_ids=$(jq -r '.videos[].videoId' "$json_data")
if [ -z "$video_ids" ]; then
echo "Error: Failed to extract video IDs from the playlist data."
return 1
fi
echo "$video_ids"
}
# Playlist IDs to fetch
playlist_ids=("PLMws9SCqJ1JCeVMVPsdamuUM0HK0MbA6g")
# Base URL for the API
base_url="http://localhost:54301/latest_version?id="
# Pick a random playlist (without using invalid options in shuf)
random_playlist_id="PLMC9KNkIncKvYin_USF1qoJQnIyMAfRxl"
echo "Randomly selected playlist: $random_playlist_id"
# Fetch playlist JSON data
fetch_playlist "$random_playlist_id"
if [ $? -ne 0 ]; then
echo "Error: Playlist fetch failed. Restarting services..."
restart_services # Restart services before exiting
exit 1
fi
# Extract video IDs from the playlist
video_ids=($(extract_video_ids "/tmp/playlist_data.json"))
if [ $? -ne 0 ]; then
echo "Error: Failed to extract video IDs. Exiting..."
exit 1
fi
# Shuffle video IDs and pick 4 random videos
shuffled_video_ids=($(shuf -e "${video_ids[@]}" | head -n 4))
error_count=0
all_errors=(500 502 403)
for video_id in "${shuffled_video_ids[@]}"; do
# Add a cache buster query (unique random number)
unique_param=$RANDOM
url="${base_url}${video_id}&itag=18&local=true&_=${unique_param}"
# Generate a random Chrome version
chrome_version=$(generate_random_chrome_version)
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/$chrome_version Safari/537.36"
response_headers=$(curl -s -D - -H "Cache-Control: no-cache, no-store, must-revalidate" \
-H "Pragma: no-cache" -H "Expires: 0" -A "$user_agent" "$url" -o /dev/null)
# Extract ETag and last modified info (if available)
etag=$(echo "$response_headers" | grep -i ETag | awk '{print $2}' | tr -d '"')
last_modified=$(echo "$response_headers" | grep -i Last-Modified | cut -d' ' -f2-)
# Use conditional request if ETag is present
if [ -n "$etag" ]; then
status_code=$(curl -s -o /dev/null -w "%{http_code}" -H "If-None-Match: $etag" \
-H "Cache-Control: no-cache, no-store, must-revalidate" -A "$user_agent" "$url")
else
status_code=$(curl -s -o /dev/null -w "%{http_code}" -A "$user_agent" "$url")
fi
# Echo the status code for visibility
echo "Checking URL: $url"
echo "User Agent: $user_agent"
echo "HTTP Status Code for ID $video_id: $status_code"
if [[ " ${all_errors[@]} " =~ " ${status_code} " ]]; then
echo "Error: Received $status_code for ID $video_id."
error_count=$((error_count + 1))
# Run the token refresh script
echo "Running inv-update-token.sh for ID $video_id..."
/home/qt/globe/scripts/inv-update-token.sh
/home/qt/globe/scripts/inv-update-token.sh
echo "inv-update-token.sh script executed successfully."
# Fetch the video again after token refresh
status_code=$(curl -s -o /dev/null -w "%{http_code}" -A "$user_agent" "$url")
echo "Post-token-refresh Status Code for ID $video_id: $status_code"
# Check if it still results in 500/502/403 after refresh
if [[ " ${all_errors[@]} " =~ " ${status_code} " ]]; then
echo "Error: Received $status_code for ID $video_id after token refresh."
else
echo "Token refresh succeeded for ID $video_id."
fi
elif [ "$status_code" -eq 304 ]; then
echo "Content is still fresh for ID $video_id. No action required."
else
echo "we are so barack (Status code for ID $video_id is neither 502, 500, nor 403.)"
fi
echo "----------------****************----------------" # Separator for readability
done
# If all videos still resulted in 500/502/403 errors even after running inv-update-token.sh, try restaring
if [ "$error_count" -eq "${#shuffled_video_ids[@]}" ]; then
echo "All videos failed to load after running inv-update-token.sh. Restarting services..."
restart_services
fi

View File

@ -0,0 +1,37 @@
#!/bin/bash
#CHANGE BOTH OF THESE TO THE CORRECT VALUES
domain="server_ddns_ip_here"
wireguard_file="/path/to/wg0.conf"
new_ip=$(dig +short "$domain" | tail -n1)
current_ip=$(grep -Eo 'Endpoint = [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' "$wireguard_file" | awk '{print $3}')
while true; do
read -p "Are you sure you want to run this? Have you checked the gluetun logs first? [Yy/Nn]" yn
case $yn in Y|y|Yes|yes* )
if [[ "$new_ip" == "$current_ip" ]]; then
echo "IP is already up to date: $current_ip"
exit 0
else
sed -i -r "s/^(Endpoint = +)([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(:[0-9]+)/\1$new_ip\3/" "$wireguard_file"
echo "IP updated in $wireguard_file, new IP is: $(grep '^Endpoint' "$wireguard_file")"
echo "Restarting gluetun..."
docker restart gluetun >/dev/null 2>&1
echo "Restarting companion..."
docker restart invidious-companion-1 >/dev/null 2>&1
exit 0
fi
;;
N|n|No|no* ) exit;;
* ) echo "Please answer yes or no.";;
esac
done

View File

@ -0,0 +1,700 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: GPL-3.0-or-later
#
# poke-nginx-analytics — IPv4/IPv6 traffic analyzer for Nginx access logs.
# Author: Ashley Iris — https://ashley0143.xyz
# Copyright (C) 2025 Poke Initiative
#
# This is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
set -euo pipefail
# --------------------------- Metadata ---------------------------
VERSION="1.3.0"
SCRIPT_NAME="poke-nginx-analytics.sh"
# DISCLAIMER:
# This tool reads your existing local Nginx access logs and prints
# aggregate counts to your terminal. It makes no network requests,
# stores nothing, and sends nothing anywhere. Optional flags let you
# ignore bot traffic and anonymize IPs in summaries. In other words:
# this is not “data collection” — its a local, read-only report.
# --------------------------- Defaults ---------------------------
LOG_GLOB="/var/log/nginx/access.log*"
DATEPAT="$(date +"%d/%b/%Y")" # e.g. "02/Oct/2025"
SINCE="" # "HH:MM" within DATEPAT (inclusive)
UNTIL="" # "HH:MM" within DATEPAT (inclusive)
WATCH_INTERVAL="" # seconds; if set, loop output
SUCCESS_CODES_DEFAULT="200,301,302,304"
SUCCESS_REGEX_DEFAULT=""
IGNORE_BOTS_DEFAULT="0"
BOT_REGEX_DEFAULT='(?i)(bot|spider|crawler|bingpreview|httpclient|curl|wget|headless|phantom|scrapy|uptimerobot|validator|pingdom|ahrefs|semrush|mj12|yandex|baiduspider|facebookexternalhit|discordbot)'
TOP_LIMIT_DEFAULT=10
ANONIP_DEFAULT="0" # mask IPs in uniques/top lists (v4 /24; v6 /64)
SHOW_LOADING_DEFAULT="1" # show “Loading …” spinner for longer actions
# --------------------------- License / Privacy ------------------
print_license() {
cat <<'LIC'
poke-nginx-analytics — IPv4/IPv6 traffic analyzer for Nginx access logs.
Author: Ashley Iris — https://ashley0143.xyz
Copyright (C) 2025 Poke Initiative
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU 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 General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program. If not, see: https://www.gnu.org/licenses/gpl-3.0.html
LIC
}
print_privacy() {
cat <<'PV'
The Software operates locally on the controllers host and processes only the HTTP access log files specified by the controller (by default, /var/log/nginx/access.log*). Processing is limited to parsing log lines to compute ephemeral aggregate statistics such as counts by IP version, status code, and hour; no identification, profiling, cross-service correlation, or tracking is performed. The Software initiates no outbound network connections and transmits no telemetry or data to third parties. Output is directed solely to the invoking terminal session and is not persisted unless the controller elects to do so (for example, by redirecting standard output). The controller remains solely responsible for the lawfulness of logging and retention; the Software does not modify logging configuration or retention policies and does not create additional stores. Optional controls permit the controller to pseudonymize displayed addresses (masking IPv4 to /24 and IPv6 to /64) and to exclude typical automated User-Agents; these controls affect only presentation and do not alter source logs. Use of the Software constitutes processing under the controllers legitimate administrative interests in operating, securing, and troubleshooting services. The Software performs deterministic, documented parsing of provided files and is publicly licensed under GPL-3.0-or-later, enabling independent audit. No special privileges are required beyond read access to the designated log files.
PV
}
# --------------------------- Usage ------------------------------
usage() {
cat <<USAGE
$SCRIPT_NAME v$VERSION — IPv4/IPv6 traffic analyzer for Nginx access logs
Author: Ashley Iris — https://ashley0143.xyz
Usage:
$SCRIPT_NAME <subcommand> [options]
Subcommands:
today Show today's IPv4 and IPv6 counts and percentages (optionally time-windowed).
today-success Same as above but success-only.
today-fail Same as above but fail-only (non-success).
success-rate Show success rate today for v4/v6, plus totals and top 5 fail codes.
v4-today IPv4 count today.
v6-today IPv6 count today.
v4-today-success IPv4 success-only today.
v6-today-success IPv6 success-only today.
breakdown-today v4|v6 Status-code breakdown today for v4 or v6.
hourly Hourly breakdown today for v4/v6 (+totals).
uniques Unique IP counts today for v4 and v6 (respects --anonip).
top-ips v4|v6 Top client IPs today for the chosen family (respects --anonip).
top-fails Top 5 failure reasons today (codes + short explanations and common fixes).
all IPv4 vs IPv6 totals from all logs (no date/time filter).
Options:
--file PATH|GLOB Single log path or glob (default: /var/log/nginx/access.log*)
--date DD/Mon/YYYY Override date filter (default: today)
--since HH:MM Start time within --date (inclusive)
--until HH:MM End time within --date (inclusive)
--success-codes "list" Comma-separated list (default: 200,301,302,304)
--success-regex REGEX Regex for success, e.g. '^(2..|3..)$' (overrides codes list)
--ignore-bots Exclude bots by UA regex (see --bot-regex)
--bot-regex REGEX Override bot UA regex (default is broad and case-insensitive)
--limit N Limit for top-ips (default: 10)
--anonip Mask IPs in uniques/top-ips (v4 /24; v6 /64)
--watch SECONDS Refresh output every N seconds
--no-loading Disable the spinner/“Loading …” message
--license | -license Print GPL-3.0-or-later license notice
--privacy Print a privacy statement
--version Print version and exit
-h, --help Show this help
Notes:
- Handles rotated + gz logs automatically (access.log, access.log.1, access.log.2.gz, ...).
- Time filters apply inside the chosen --date only.
- If both --success-regex and --success-codes are set, regex wins.
USAGE
}
# --------------------------- Helpers ---------------------------
LOG_FILES=()
SUCCESS_CODES="$SUCCESS_CODES_DEFAULT"
SUCCESS_REGEX="$SUCCESS_REGEX_DEFAULT"
TOP_LIMIT="$TOP_LIMIT_DEFAULT"
IGNORE_BOTS="$IGNORE_BOTS_DEFAULT"
BOT_REGEX="$BOT_REGEX_DEFAULT"
ANONIP="$ANONIP_DEFAULT"
SHOW_LOADING="$SHOW_LOADING_DEFAULT"
SUB=""
FAMILY="" # v4 or v6
expand_logs() {
local glob="${1:-$LOG_GLOB}"
# shellcheck disable=SC2206
local arr=($glob)
LOG_FILES=()
for f in "${arr[@]}"; do
if ([[ -f "$f" ]] || [[ "$f" =~ \* ]]); then LOG_FILES+=("$f"); fi
done
if [[ ${#LOG_FILES[@]} -eq 1 && "${LOG_FILES[0]}" == *"*"* ]]; then
# shellcheck disable=SC2206
local rexpanded=(${LOG_FILES[0]})
LOG_FILES=()
for f in "${rexpanded[@]}"; do
if [[ -f "$f" ]]; then LOG_FILES+=("$f"); fi
done
fi
if [[ ${#LOG_FILES[@]} -eq 0 ]]; then
echo "No log files found for: $glob" >&2
exit 1
fi
}
read_logs() {
for f in "${LOG_FILES[@]}"; do
case "$f" in
*.gz) zcat -f -- "$f" ;;
*) cat -- "$f" ;;
esac
done
}
percent() {
local part="$1" total="$2"
if [[ "$total" -eq 0 ]]; then printf "0.0"; return; fi
awk -v p="$part" -v t="$total" 'BEGIN{ printf("%.1f", (p*100.0)/t) }'
}
awk_date_guard() {
if [[ -z "$SINCE$UNTIL" ]]; then
echo 'substr($4,2,11)==datepat'
else
local cond='substr($4,2,11)==datepat'
if [[ -n "$SINCE" ]]; then
cond="$cond && substr(\$4,14,5)>=since"
fi
if [[ -n "$UNTIL" ]]; then
cond="$cond && substr(\$4,14,5)<=until"
fi
echo "$cond"
fi
}
awk_common_header() {
cat <<'AWKHEAD'
function is_ipv4(ip) { return ip ~ /^([0-9]{1,3}\.){3}[0-9]{1,3}$/ }
function is_ipv6(ip) { return ip ~ /:/ && !(ip ~ /^([0-9]{1,3}\.){3}[0-9]{1,3}$/) }
# Get the last quoted field as UA (combined log format)
function extract_ua(line, ua) {
if (match(line, /"[^"]*"$/)) {
ua = substr(line, RSTART+1, RLENGTH-2);
return ua;
}
return "";
}
# Mask IPv4 to /24, IPv6 to /64 (display only)
function anon_ip(ip, a, i, out, n) {
if (anonip==0) return ip;
if (is_ipv4(ip)) {
n = split(ip, a, ".");
if (n==4) { a[4]=0; return a[1]"."a[2]"."a[3]".0" }
return ip;
} else if (is_ipv6(ip)) {
n = split(ip, a, ":");
for (i=5; i<=n; i++) a[i]="0000";
out=a[1];
for (i=2;i<=n;i++) out=out ":" a[i];
return out;
}
return ip;
}
function ok_status(code) { # success by regex or by list
if (has_regex) return (code ~ success_regex);
return (code in okcodes);
}
AWKHEAD
}
awk_begin_block() {
local bot_awk_regex
bot_awk_regex=$(printf '%s' "$BOT_REGEX" | sed 's/[&/\]/\\&/g')
cat <<EOF
BEGIN {
has_regex = ("$SUCCESS_REGEX" != "");
success_regex = "$SUCCESS_REGEX";
if (!has_regex) {
split("$SUCCESS_CODES", arr, ",");
for (i in arr) okcodes[arr[i]]=1;
}
ignore_bots = $IGNORE_BOTS;
bot_regex = "$bot_awk_regex";
anonip = $ANONIP;
}
EOF
}
count_family_today() {
local fam="$1" mode="$2"
local date_guard; date_guard="$(awk_date_guard)"
local pred_ip
if [[ "$fam" == "v4" ]]; then pred_ip='is_ipv4($1)'
else pred_ip='is_ipv6($1)'
fi
awk -v datepat="$DATEPAT" -v since="$SINCE" -v until="$UNTIL" '
'"$(awk_common_header)"'
'"$(awk_begin_block)"'
{
if (!('"$date_guard"')) next;
if (ignore_bots) {
ua = extract_ua($0);
if (ua ~ bot_regex) next;
}
if (!('"$pred_ip"')) next;
status = $9;
success = ok_status(status);
if ("'"$mode"'"=="any") c++;
else if ("'"$mode"'"=="success" && success) c++;
else if ("'"$mode"'"=="fail" && !success) c++;
}
END { print c+0 }
' < <(read_logs)
}
status_breakdown_today() {
local fam="$1"
local date_guard; date_guard="$(awk_date_guard)"
local pred_ip
if [[ "$fam" == "v4" ]]; then pred_ip='is_ipv4($1)'
else pred_ip='is_ipv6($1)'
fi
awk -v datepat="$DATEPAT" -v since="$SINCE" -v until="$UNTIL" '
'"$(awk_common_header)"'
'"$(awk_begin_block)"'
{
if (!('"$date_guard"')) next;
if (ignore_bots) {
ua = extract_ua($0);
if (ua ~ bot_regex) next;
}
if (!('"$pred_ip"')) next;
code=$9; count[code]++;
}
END {
for (c in count) printf "%10d %s\n", count[c], c | "sort -nr";
close("sort -nr");
}
' < <(read_logs)
}
hourly_breakdown_today() {
local date_guard; date_guard="$(awk_date_guard)"
awk -v datepat="$DATEPAT" -v since="$SINCE" -v until="$UNTIL" '
'"$(awk_common_header)"'
'"$(awk_begin_block)"'
{
if (!('"$date_guard"')) next;
if (ignore_bots) {
ua = extract_ua($0);
if (ua ~ bot_regex) next;
}
hh=substr($4,14,2);
if (is_ipv4($1)) v4[hh]++; else if (is_ipv6($1)) v6[hh]++;
}
END {
printf "Hour | v4 v6 total\n";
printf "------+------------------------\n";
for (i=0; i<24; i++) {
h = sprintf("%02d", i);
a=v4[h]+0; b=v6[h]+0; t=a+b;
printf "%s | %6d %6d %8d\n", h, a, b, t;
}
}
' < <(read_logs)
}
unique_ips_today() {
local date_guard; date_guard="$(awk_date_guard)"
awk -v datepat="$DATEPAT" -v since="$SINCE" -v until="$UNTIL" '
'"$(awk_common_header)"'
'"$(awk_begin_block)"'
{
if (!('"$date_guard"')) next;
if (ignore_bots) {
ua = extract_ua($0);
if (ua ~ bot_regex) next;
}
ip=$1;
aip=anon_ip(ip);
if (is_ipv4(ip)) v4[aip]=1;
else if (is_ipv6(ip)) v6[aip]=1;
}
END {
print (length(v4)+0) " " (length(v6)+0);
}
' < <(read_logs)
}
top_ips_today() {
local fam="$1" limit="$2"
local date_guard; date_guard="$(awk_date_guard)"
local pred_ip
if [[ "$fam" == "v4" ]]; then pred_ip='is_ipv4($1)'
else pred_ip='is_ipv6($1)'
fi
awk -v datepat="$DATEPAT" -v since="$SINCE" -v until="$UNTIL" -v lim="$limit" '
'"$(awk_common_header)"'
'"$(awk_begin_block)"'
{
if (!('"$date_guard"')) next;
if (ignore_bots) {
ua = extract_ua($0);
if (ua ~ bot_regex) next;
}
if (!('"$pred_ip"')) next;
ip=$1;
aip=anon_ip(ip);
c[aip]++;
}
END {
cmd = "sort -nr | head -n " lim;
for (k in c) printf "%10d %s\n", c[k], k | cmd;
close(cmd);
}
' < <(read_logs)
}
# collect top failure codes (v4+v6 combined)
_top_fail_codes_today_raw() {
local limit="${1:-5}"
local date_guard; date_guard="$(awk_date_guard)"
awk -v datepat="$DATEPAT" -v since="$SINCE" -v until="$UNTIL" -v lim="$limit" '
'"$(awk_common_header)"'
'"$(awk_begin_block)"'
{
if (!('"$date_guard"')) next;
if (ignore_bots) {
ua = extract_ua($0);
if (ua ~ bot_regex) next;
}
code=$9;
if (!ok_status(code)) fails[code]++;
}
END {
cmd = "sort -nr | head -n " lim;
for (c in fails) printf "%10d %s\n", fails[c], c | cmd;
close(cmd);
}
' < <(read_logs)
}
# map codes to short reason + common fixes
_code_reason_and_fix() {
local code="$1"
case "$code" in
404) echo "Not Found | Check routes/file paths; verify upstream/location blocks; add/refresh indexes." ;;
403) echo "Forbidden | Fix permissions/SELinux; review 'deny' rules; ensure correct root/user; auth config." ;;
400) echo "Bad Request | Validate request size/headers; client encoding; large header buffers." ;;
401) echo "Unauthorized | Confirm auth headers/keys; verify Basic/Bearer configs; clock skew for signed URLs." ;;
408) echo "Client Timeout | Client slow; increase client_header/body_timeout; check network; throttle long uploads." ;;
413) echo "Payload Too Large | Raise client_max_body_size; review upload size from client/app." ;;
414) echo "URI Too Long | Reduce query size; switch to POST; adjust large_client_header_buffers." ;;
429) echo "Too Many Requests | Tuning rate limiting/burst; whitelist health checks/bots if intended." ;;
499) echo "Client Closed Request | Client aborted/timeout; investigate latency; optimize upstream; keepalive." ;;
500) echo "Internal Server Error | Check app errors; upstream logs; fastcgi/proxy params; temp file perms." ;;
502) echo "Bad Gateway | Upstream down/misconfigured; check upstream server, sockets, DNS, healthchecks." ;;
503) echo "Service Unavailable | Upstream overloaded/maintenance; tune workers; queue/backoff; autoscale." ;;
504) echo "Gateway Timeout | Upstream slow; raise proxy_read_timeout; profile app/DB; connection pool." ;;
530|531|532) echo "Upstream/Custom Error | Vendor or custom code; check error_page mapping and upstream." ;;
301|302|304) echo "Redirect/Not Modified | Typically not a failure; ensure correct cache/redirect rules." ;;
*) # generic 4xx/5xx buckets
case "$code" in
4??) echo "Client Error | Validate client requests, auth, size limits, and security rules." ;;
5??) echo "Server Error | Inspect upstream/app, timeouts, resource limits, and Nginx proxy config." ;;
*) echo "Other | Review Nginx error logs and upstream/application logs." ;;
esac ;;
esac
}
# pretty top fails with reasons and fixes
top_fail_reasons_today() {
local limit="${1:-5}"
_top_fail_codes_today_raw "$limit" | while read -r cnt code; do
local info; info=$(_code_reason_and_fix "$code")
local reason=${info%|*}
local fix=${info#*|}
printf "%10d %s — %s\n fix: %s\n" "$cnt" "$code" "$(echo "$reason" | sed 's/^ *//')" "$(echo "$fix" | sed 's/^ *//')"
done
}
count_all_family() {
local fam="$1"
local pred_ip
if [[ "$fam" == "v4" ]]; then pred_ip='is_ipv4($1)'
else pred_ip='is_ipv6($1)'
fi
awk '
'"$(awk_common_header)"'
BEGIN { ignore_bots=0; has_regex=0; anonip=0; }
{
if (!('"$pred_ip"')) next;
c++;
}
END { print c+0 }
' < <(read_logs)
}
# ----------------------- Spinner / Loading ---------------------
_spinner_start() {
[[ "$SHOW_LOADING" != "1" || -n "${WATCH_INTERVAL}" ]] && return 0
local msg="${1:-Loading}"
printf "%s " "$msg"
(
i=0
while :; do
case $((i%4)) in
0) c='|';;
1) c='/';;
2) c='-';;
3) c='\\';;
esac
printf "\r%s %s" "$msg" "$c"
i=$((i+1))
sleep 0.15
done
) &
SPINNER_PID=$!
disown "$SPINNER_PID" 2>/dev/null || true
}
_spinner_stop() {
[[ "${SPINNER_PID:-}" =~ ^[0-9]+$ ]] || return 0
kill "$SPINNER_PID" 2>/dev/null || true
wait "$SPINNER_PID" 2>/dev/null || true
unset SPINNER_PID
printf "\r\033[K"
}
run_with_spinner() {
local msg="$1"; shift
local tmp; tmp="$(mktemp)"
_spinner_start "$msg"
(
"$@"
) >"$tmp" 2>&1 || true
_spinner_stop
cat "$tmp"
rm -f "$tmp"
}
# -------------------------- CLI Parse --------------------------
while [[ $# -gt 0 ]]; do
case "$1" in
--license|-license) print_license; exit 0 ;;
--privacy) print_privacy; exit 0 ;;
--version) echo "$SCRIPT_NAME v$VERSION"; exit 0 ;;
today|today-success|today-fail|success-rate|v4-today|v6-today|v4-today-success|v6-today-success|breakdown-today|hourly|uniques|top-ips|top-fails|all)
SUB="$1"; shift ;;
v4|v6) FAMILY="$1"; shift ;;
--file) LOG_GLOB="$2"; shift 2 ;;
--date) DATEPAT="$2"; shift 2 ;;
--since) SINCE="$2"; shift 2 ;;
--until) UNTIL="$2"; shift 2 ;;
--success-codes) SUCCESS_CODES="$2"; shift 2 ;;
--success-regex) SUCCESS_REGEX="$2"; shift 2 ;;
--ignore-bots) IGNORE_BOTS="1"; shift ;;
--bot-regex) BOT_REGEX="$2"; shift 2 ;;
--limit) TOP_LIMIT="$2"; shift 2 ;;
--anonip) ANONIP="1"; shift ;;
--watch) WATCH_INTERVAL="$2"; shift 2 ;;
--no-loading) SHOW_LOADING="0"; shift ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown argument: $1" >&2; usage; exit 1 ;;
esac
done
expand_logs "$LOG_GLOB"
# ---------------------------- Runner ---------------------------
_do_command() {
case "$SUB" in
today)
v4=$(count_family_today v4 any)
v6=$(count_family_today v6 any)
total=$((v4+v6))
pv4=$(percent "$v4" "$total"); pv6=$(percent "$v6" "$total")
echo "Date: $DATEPAT${SINCE:+ from $SINCE}${UNTIL:+ to $UNTIL}"
[[ "$IGNORE_BOTS" == "1" ]] && echo "Ignoring bots by UA regex"
echo "IPv4: $v4 (${pv4}%)"
echo "IPv6: $v6 (${pv6}%)"
echo "Total requests: $total"
;;
today-success)
v4=$(count_family_today v4 success)
v6=$(count_family_today v6 success)
total=$((v4+v6))
pv4=$(percent "$v4" "$total"); pv6=$(percent "$v6" "$total")
echo "Date: $DATEPAT${SINCE:+ from $SINCE}${UNTIL:+ to $UNTIL}"
[[ -n "$SUCCESS_REGEX" ]] && echo "Success regex: $SUCCESS_REGEX" || echo "Success codes: $SUCCESS_CODES"
[[ "$IGNORE_BOTS" == "1" ]] && echo "Ignoring bots by UA regex"
echo "IPv4 (success-only): $v4 (${pv4}%)"
echo "IPv6 (success-only): $v6 (${pv6}%)"
echo "Total successful requests: $total"
;;
today-fail)
v4=$(count_family_today v4 fail)
v6=$(count_family_today v6 fail)
total=$((v4+v6))
pv4=$(percent "$v4" "$total"); pv6=$(percent "$v6" "$total")
echo "Date: $DATEPAT${SINCE:+ from $SINCE}${UNTIL:+ to $UNTIL}"
[[ -n "$SUCCESS_REGEX" ]] && echo "Success complement of regex: $SUCCESS_REGEX" || echo "Fail = not in: $SUCCESS_CODES"
[[ "$IGNORE_BOTS" == "1" ]] && echo "Ignoring bots by UA regex"
echo "IPv4 (fail-only): $v4 (${pv4}%)"
echo "IPv6 (fail-only): $v6 (${pv6}%)"
echo "Total failed requests: $total"
echo "Top 5 failure reasons:"
top_fail_reasons_today 5
;;
success-rate)
s4=$(count_family_today v4 success)
a4=$(count_family_today v4 any)
s6=$(count_family_today v6 success)
a6=$(count_family_today v6 any)
r4=$(percent "$s4" "$a4")
r6=$(percent "$s6" "$a6")
stotal=$((s4+s6))
atotal=$((a4+a6))
ftotal=$((atotal-stotal))
echo "Date: $DATEPAT${SINCE:+ from $SINCE}${UNTIL:+ to $UNTIL}"
[[ -n "$SUCCESS_REGEX" ]] && echo "Success regex: $SUCCESS_REGEX" || echo "Success codes: $SUCCESS_CODES"
[[ "$IGNORE_BOTS" == "1" ]] && echo "Ignoring bots by UA regex"
echo "IPv4 success-rate: $s4 / $a4 (${r4}%)"
echo "IPv6 success-rate: $s6 / $a6 (${r6}%)"
echo "—"
echo "TOTAL requests: $atotal"
echo "TOTAL successful: $stotal"
echo "TOTAL failed: $ftotal"
echo "Top 5 failure reasons (combined v4+v6):"
top_fail_reasons_today 5
;;
v4-today)
v4=$(count_family_today v4 any)
echo "Date: $DATEPAT${SINCE:+ from $SINCE}${UNTIL:+ to $UNTIL}"
[[ "$IGNORE_BOTS" == "1" ]] && echo "Ignoring bots by UA regex"
echo "IPv4: $v4"
;;
v6-today)
v6=$(count_family_today v6 any)
echo "Date: $DATEPAT${SINCE:+ from $SINCE}${UNTIL:+ to $UNTIL}"
[[ "$IGNORE_BOTS" == "1" ]] && echo "Ignoring bots by UA regex"
echo "IPv6: $v6"
;;
v4-today-success)
v4=$(count_family_today v4 success)
echo "Date: $DATEPAT${SINCE:+ from $SINCE}${UNTIL:+ to $UNTIL}"
[[ -n "$SUCCESS_REGEX" ]] && echo "Success regex: $SUCCESS_REGEX" || echo "Success codes: $SUCCESS_CODES"
[[ "$IGNORE_BOTS" == "1" ]] && echo "Ignoring bots by UA regex"
echo "IPv4 (success-only): $v4"
;;
v6-today-success)
v6=$(count_family_today v6 success)
echo "Date: $DATEPAT${SINCE:+ from $SINCE}${UNTIL:+ to $UNTIL}"
[[ -n "$SUCCESS_REGEX" ]] && echo "Success regex: $SUCCESS_REGEX" || echo "Success codes: $SUCCESS_CODES"
[[ "$IGNORE_BOTS" == "1" ]] && echo "Ignoring bots by UA regex"
echo "IPv6 (success-only): $v6"
;;
breakdown-today)
if [[ -z "$FAMILY" ]]; then echo "Specify family: v4 or v6" >&2; exit 1; fi
echo "Date: $DATEPAT${SINCE:+ from $SINCE}${UNTIL:+ to $UNTIL}"
[[ "$IGNORE_BOTS" == "1" ]] && echo "Ignoring bots by UA regex"
echo "Status breakdown ($FAMILY):"
status_breakdown_today "$FAMILY"
;;
hourly)
echo "Date: $DATEPAT${SINCE:+ from $SINCE}${UNTIL:+ to $UNTIL}"
[[ "$IGNORE_BOTS" == "1" ]] && echo "Ignoring bots by UA regex"
hourly_breakdown_today
;;
uniques)
read -r u4 u6 < <(unique_ips_today)
echo "Date: $DATEPAT${SINCE:+ from $SINCE}${UNTIL:+ to $UNTIL}"
[[ "$IGNORE_BOTS" == "1" ]] && echo "Ignoring bots by UA regex"
[[ "$ANONIP" == "1" ]] && echo "IP anonymization: enabled (v4 /24, v6 /64)"
echo "Unique IPv4 IPs: $u4"
echo "Unique IPv6 IPs: $u6"
;;
top-ips)
if [[ -z "$FAMILY" ]] ; then echo "Specify family: v4 or v6" >&2; exit 1; fi
echo "Date: $DATEPAT${SINCE:+ from $SINCE}${UNTIL:+ to $UNTIL}"
[[ "$IGNORE_BOTS" == "1" ]] && echo "Ignoring bots by UA regex"
[[ "$ANONIP" == "1" ]] && echo "IP anonymization: enabled (v4 /24, v6 /64)"
echo "Top IPs ($FAMILY), limit $TOP_LIMIT:"
top_ips_today "$FAMILY" "$TOP_LIMIT"
;;
top-fails)
echo "Date: $DATEPAT${SINCE:+ from $SINCE}${UNTIL:+ to $UNTIL}"
[[ "$IGNORE_BOTS" == "1" ]] && echo "Ignoring bots by UA regex"
echo "Top 5 failure reasons (combined v4+v6):"
top_fail_reasons_today 5
;;
all)
v4=$(count_all_family v4)
v6=$(count_all_family v6)
total=$((v4+v6))
pv4=$(percent "$v4" "$total"); pv6=$(percent "$v6" "$total")
echo "All logs (no date/time filter)"
echo "IPv4: $v4 (${pv4}%)"
echo "IPv6: $v6 (${pv6}%)"
echo "Total requests: $total"
;;
*)
usage; exit 1 ;;
esac
}
run_once() { _do_command; }
if [[ -n "$WATCH_INTERVAL" ]]; then
while :; do
clear || true
run_with_spinner "Loading…" run_once
sleep "$WATCH_INTERVAL"
done
else
run_with_spinner "Loading…" run_once
fi

View File

@ -0,0 +1,18 @@
#!/bin/bash
#CHANGE ME
compose_file="/path/to/compose/file"
# Threshold in MB (8 GB = 8192 MB)
threshold=8980
# Get total memory usage in MB
used=$(free -m | awk '/^Mem:/ {print $3}')
if [ "$USED" -ge "$THRESHOLD" ]; then
echo "Memory usage is ${used}MB (≥ ${threshold}MB). Restarting Invidious..."
docker compose -f "$compose_file" down >/dev/null 2>&1
docker compose -f "$compose_file" up -d >/dev/null 2>&1
else
echo "Memory usage is ${used}MB (< ${threshold}MB). No restart performed."
fi

View File

@ -0,0 +1,26 @@
#!/bin/bash
path=/your/path/here/
opts=("Switch to router VPN" "Switch to Cloudflare VPN" "Exit")
PS3="Choose an option:"
select o in "${opts[@]}"
do
case "$REPLY" in
1) echo "Switching to router VPN..."
rm $path/wg0.conf
ln -s $path/wg0.conf.router $path/wg0.conf
docker compose -f $path/docker-compose.yml down >/dev/null 2>&1
docker compose -f $path/docker-compose.yml up -d >/dev/null 2>&1
exit 0
;;
2) echo "Switching to Cloudflare VPN..."
rm $path/wg0.conf
ln -s $path/wg0.conf.cf $path/wg0.conf
docker compose -f $path/docker-compose.yml down >/dev/null 2>&1
docker compose -f $path/docker-compose.yml up -d >/dev/null 2>&1
exit 0
;;
3) break;
;;
esac
done

View File

@ -0,0 +1,10 @@
[Unit]
Description=YouTube anti-block
[Service]
Type=simple
ExecStart=/home/qt/globe/scripts/inv-restart-docker.sh
Restart=on-failure
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,10 @@
[Unit]
Description=Make yt anti-block Run every Minute
[Timer]
OnUnitActiveSec=1min
Unit=yt-block-protect.service
[Install]
WantedBy=timers.target

View File

@ -1,17 +1,20 @@
{
"tubeApi": "https://inner-api.poketube.fun/api/",
"invapi": "https://invid-api.poketube.fun/api/v1",
"invapi": "https://invid-api.poketube.fun/bHj665PpYhUdPWuKPfZuQGoX/api/v1",
"dislikes": "https://returnyoutubedislikeapi.com/votes?videoId=",
"invchannel": "https://invid-api.poketube.fun/api/v1",
"p_url":"https://p.poketube.fun",
"useragent":"PokeTube/2.0.0 (GNU/Linux; Android 14; Trisquel 11; poketube-vidious; like FreeTube)",
"media_proxy": "https://image-proxy.poketube.fun",
"videourl":"https://eu-proxy.poketube.fun",
"email_main_url":"https://email-server.poketube.fun",
"mastodon_client_url":"https://fediverse.poketube.fun",
"mastodon_client_url":"https://social.poketube.fun",
"mastodon_client_server_name":"PokeSocial",
"libreoffice_online_url":"https://office.poketube.fun",
"cacher_max_age": "864000",
"cacher_max_age": "86400",
"enablealwayshttps": false,
"proxylocation":"USA",
"banner":"welcome to poke!",
"t_url": "https://t.poketube.fun/",
"server_port": "6003"
}

View File

@ -1,16 +0,0 @@
using System;
namespace InnerTube
{
public class CacheItem<T>
{
public T Item;
public DateTimeOffset ExpireTime;
public CacheItem(T item, TimeSpan expiresIn)
{
Item = item;
ExpireTime = DateTimeOffset.Now.Add(expiresIn);
}
}
}

View File

@ -1,12 +0,0 @@
namespace InnerTube
{
public enum ChannelTabs
{
Home,
Videos,
Playlists,
Community,
Channels,
About
}
}

View File

@ -1,11 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
</Project>

View File

@ -1,380 +0,0 @@
using System;
using System.Xml;
using System.Xml.Linq;
namespace InnerTube.Models
{
public class DynamicItem
{
public string Id;
public string Title;
public Thumbnail[] Thumbnails;
public virtual XmlElement GetXmlElement(XmlDocument doc)
{
XmlElement item = doc.CreateElement("DynamicItem");
item.SetAttribute("id", Id);
XmlElement title = doc.CreateElement("Title");
title.InnerText = Title;
item.AppendChild(title);
foreach (Thumbnail t in Thumbnails ?? Array.Empty<Thumbnail>())
{
XmlElement thumbnail = doc.CreateElement("Thumbnail");
thumbnail.SetAttribute("width", t.Width.ToString());
thumbnail.SetAttribute("height", t.Height.ToString());
thumbnail.InnerText = t.Url;
item.AppendChild(thumbnail);
}
return item;
}
}
public class VideoItem : DynamicItem
{
public string UploadedAt;
public long Views;
public Channel Channel;
public string Duration;
public string Description;
public override XmlElement GetXmlElement(XmlDocument doc)
{
XmlElement item = doc.CreateElement("Video");
item.SetAttribute("id", Id);
item.SetAttribute("duration", Duration);
item.SetAttribute("views", Views.ToString());
item.SetAttribute("uploadedAt", UploadedAt);
XmlElement title = doc.CreateElement("Title");
title.InnerText = Title;
item.AppendChild(title);
if (Channel is not null)
item.AppendChild(Channel.GetXmlElement(doc));
foreach (Thumbnail t in Thumbnails ?? Array.Empty<Thumbnail>())
{
XmlElement thumbnail = doc.CreateElement("Thumbnail");
thumbnail.SetAttribute("width", t.Width.ToString());
thumbnail.SetAttribute("height", t.Height.ToString());
thumbnail.InnerText = t.Url;
item.AppendChild(thumbnail);
}
if (!string.IsNullOrWhiteSpace(Description))
{
XmlElement description = doc.CreateElement("Description");
description.InnerText = Description;
item.AppendChild(description);
}
return item;
}
}
public class PlaylistItem : DynamicItem
{
public int VideoCount;
public string FirstVideoId;
public Channel Channel;
public override XmlElement GetXmlElement(XmlDocument doc)
{
XmlElement item = doc.CreateElement("Playlist");
item.SetAttribute("id", Id);
item.SetAttribute("videoCount", VideoCount.ToString());
item.SetAttribute("firstVideoId", FirstVideoId);
XmlElement title = doc.CreateElement("Title");
title.InnerText = Title;
item.AppendChild(title);
item.AppendChild(Channel.GetXmlElement(doc));
foreach (Thumbnail t in Thumbnails ?? Array.Empty<Thumbnail>())
{
XmlElement thumbnail = doc.CreateElement("Thumbnail");
thumbnail.SetAttribute("width", t.Width.ToString());
thumbnail.SetAttribute("height", t.Height.ToString());
thumbnail.InnerText = t.Url;
item.AppendChild(thumbnail);
}
return item;
}
}
public class RadioItem : DynamicItem
{
public string FirstVideoId;
public Channel Channel;
public override XmlElement GetXmlElement(XmlDocument doc)
{
XmlElement item = doc.CreateElement("Radio");
item.SetAttribute("id", Id);
item.SetAttribute("firstVideoId", FirstVideoId);
XmlElement title = doc.CreateElement("Title");
title.InnerText = Title;
item.AppendChild(title);
item.AppendChild(Channel.GetXmlElement(doc));
foreach (Thumbnail t in Thumbnails ?? Array.Empty<Thumbnail>())
{
XmlElement thumbnail = doc.CreateElement("Thumbnail");
thumbnail.SetAttribute("width", t.Width.ToString());
thumbnail.SetAttribute("height", t.Height.ToString());
thumbnail.InnerText = t.Url;
item.AppendChild(thumbnail);
}
return item;
}
}
public class ChannelItem : DynamicItem
{
public string Url;
public string Description;
public long VideoCount;
public string Subscribers;
public override XmlElement GetXmlElement(XmlDocument doc)
{
XmlElement item = doc.CreateElement("Channel");
item.SetAttribute("id", Id);
item.SetAttribute("videoCount", VideoCount.ToString());
item.SetAttribute("subscribers", Subscribers);
if (!string.IsNullOrWhiteSpace(Url))
item.SetAttribute("customUrl", Url);
XmlElement title = doc.CreateElement("Name");
title.InnerText = Title;
item.AppendChild(title);
XmlElement description = doc.CreateElement("Description");
description.InnerText = Description;
item.AppendChild(description);
foreach (Thumbnail t in Thumbnails ?? Array.Empty<Thumbnail>())
{
XmlElement thumbnail = doc.CreateElement("Avatar");
thumbnail.SetAttribute("width", t.Width.ToString());
thumbnail.SetAttribute("height", t.Height.ToString());
thumbnail.InnerText = t.Url;
item.AppendChild(thumbnail);
}
return item;
}
}
public class ContinuationItem : DynamicItem
{
public override XmlElement GetXmlElement(XmlDocument doc)
{
XmlElement item = doc.CreateElement("Continuation");
item.SetAttribute("key", Id);
return item;
}
}
public class ShelfItem : DynamicItem
{
public DynamicItem[] Items;
public int CollapsedItemCount;
public BadgeItem[] Badges;
public override XmlElement GetXmlElement(XmlDocument doc)
{
XmlElement item = doc.CreateElement("Shelf");
item.SetAttribute("title", Title);
item.SetAttribute("collapsedItemCount", CollapsedItemCount.ToString());
foreach (Thumbnail t in Thumbnails ?? Array.Empty<Thumbnail>())
{
XmlElement thumbnail = doc.CreateElement("Thumbnail");
thumbnail.SetAttribute("width", t.Width.ToString());
thumbnail.SetAttribute("height", t.Height.ToString());
thumbnail.InnerText = t.Url;
item.AppendChild(thumbnail);
}
if (Badges.Length > 0)
{
XmlElement badges = doc.CreateElement("Badges");
foreach (BadgeItem badge in Badges) badges.AppendChild(badge.GetXmlElement(doc));
item.AppendChild(badges);
}
XmlElement items = doc.CreateElement("Items");
foreach (DynamicItem dynamicItem in Items) items.AppendChild(dynamicItem.GetXmlElement(doc));
item.AppendChild(items);
return item;
}
}
public class HorizontalCardListItem : DynamicItem
{
public DynamicItem[] Items;
public override XmlElement GetXmlElement(XmlDocument doc)
{
XmlElement item = doc.CreateElement("CardList");
item.SetAttribute("title", Title);
foreach (DynamicItem dynamicItem in Items) item.AppendChild(dynamicItem.GetXmlElement(doc));
return item;
}
}
public class CardItem : DynamicItem
{
public override XmlElement GetXmlElement(XmlDocument doc)
{
XmlElement item = doc.CreateElement("Card");
item.SetAttribute("title", Title);
foreach (Thumbnail t in Thumbnails ?? Array.Empty<Thumbnail>())
{
XmlElement thumbnail = doc.CreateElement("Thumbnail");
thumbnail.SetAttribute("width", t.Width.ToString());
thumbnail.SetAttribute("height", t.Height.ToString());
thumbnail.InnerText = t.Url;
item.AppendChild(thumbnail);
}
return item;
}
}
public class PlaylistVideoItem : DynamicItem
{
public long Index;
public Channel Channel;
public string Duration;
public override XmlElement GetXmlElement(XmlDocument doc)
{
XmlElement item = doc.CreateElement("Video");
item.SetAttribute("id", Id);
item.SetAttribute("index", Index.ToString());
item.SetAttribute("duration", Duration);
XmlElement title = doc.CreateElement("Title");
title.InnerText = Title;
item.AppendChild(title);
item.AppendChild(Channel.GetXmlElement(doc));
foreach (Thumbnail t in Thumbnails ?? Array.Empty<Thumbnail>())
{
XmlElement thumbnail = doc.CreateElement("Thumbnail");
thumbnail.SetAttribute("width", t.Width.ToString());
thumbnail.SetAttribute("height", t.Height.ToString());
thumbnail.InnerText = t.Url;
item.AppendChild(thumbnail);
}
return item;
}
}
public class ItemSectionItem : DynamicItem
{
public DynamicItem[] Contents;
public override XmlElement GetXmlElement(XmlDocument doc)
{
XmlElement section = doc.CreateElement("ItemSection");
foreach (DynamicItem item in Contents) section.AppendChild(item.GetXmlElement(doc));
return section;
}
}
public class MessageItem : DynamicItem
{
public override XmlElement GetXmlElement(XmlDocument doc)
{
XmlElement message = doc.CreateElement("Message");
message.InnerText = Title;
return message;
}
}
public class ChannelAboutItem : DynamicItem
{
public string Description;
public string Country;
public string Joined;
public string ViewCount;
public override XmlElement GetXmlElement(XmlDocument doc)
{
XmlElement about = doc.CreateElement("About");
XmlElement description = doc.CreateElement("Description");
description.InnerText = Description;
about.AppendChild(description);
XmlElement country = doc.CreateElement("Location");
country.InnerText = Country;
about.AppendChild(country);
XmlElement joined = doc.CreateElement("Joined");
joined.InnerText = Joined;
about.AppendChild(joined);
XmlElement viewCount = doc.CreateElement("ViewCount");
viewCount.InnerText = ViewCount;
about.AppendChild(viewCount);
return about;
}
}
public class BadgeItem : DynamicItem
{
public string Style;
public override XmlElement GetXmlElement(XmlDocument doc)
{
XmlElement badge = doc.CreateElement("Badge");
badge.SetAttribute("style", Style);
badge.InnerText = Title;
return badge;
}
}
public class StationItem : DynamicItem
{
public int VideoCount;
public string FirstVideoId;
public string Description;
public override XmlElement GetXmlElement(XmlDocument doc)
{
XmlElement item = doc.CreateElement("Station");
item.SetAttribute("id", Id);
item.SetAttribute("videoCount", VideoCount.ToString());
item.SetAttribute("firstVideoId", FirstVideoId);
XmlElement title = doc.CreateElement("Title");
title.InnerText = Title;
item.AppendChild(title);
XmlElement description = doc.CreateElement("Description");
description.InnerText = Description;
item.AppendChild(description);
foreach (Thumbnail t in Thumbnails ?? Array.Empty<Thumbnail>())
{
XmlElement thumbnail = doc.CreateElement("Thumbnail");
thumbnail.SetAttribute("width", t.Width.ToString());
thumbnail.SetAttribute("height", t.Height.ToString());
thumbnail.InnerText = t.Url;
item.AppendChild(thumbnail);
}
return item;
}
}
}

View File

@ -1,67 +0,0 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace InnerTube.Models
{
public class RequestContext
{
[JsonProperty("context")] public Context Context;
public static string BuildRequestContextJson(Dictionary<string, object> additionalFields, string language = "en",
string region = "US", string clientName = "WEB", string clientVersion = "2.20220224.07.00")
{
RequestContext ctx = new()
{
Context = new Context(
new RequestClient(language, region, clientName, clientVersion),
new RequestUser(false))
};
string json1 = JsonConvert.SerializeObject(ctx);
Dictionary<string, object> json2 = JsonConvert.DeserializeObject<Dictionary<string, object>>(json1);
foreach (KeyValuePair<string,object> pair in additionalFields) json2.Add(pair.Key, pair.Value);
return JsonConvert.SerializeObject(json2);
}
}
public class Context
{
[JsonProperty("client")] public RequestClient RequestClient { get; set; }
[JsonProperty("user")] public RequestUser RequestUser { get; set; }
public Context(RequestClient requestClient, RequestUser requestUser)
{
RequestClient = requestClient;
RequestUser = requestUser;
}
}
public class RequestClient
{
[JsonProperty("hl")] public string Language { get; set; }
[JsonProperty("gl")] public string Region { get; set; }
[JsonProperty("clientName")] public string ClientName { get; set; }
[JsonProperty("clientVersion")] public string ClientVersion { get; set; }
[JsonProperty("deviceModel")] public string DeviceModel { get; set; }
public RequestClient(string language, string region, string clientName, string clientVersion)
{
Language = language;
Region = region;
ClientName = clientName;
ClientVersion = clientVersion;
if (clientName == "IOS") DeviceModel = "iPhone14,3";
}
}
public class RequestUser
{
[JsonProperty("lockedSafetyMode")] public bool LockedSafetyMode { get; set; }
public RequestUser(bool lockedSafetyMode)
{
LockedSafetyMode = lockedSafetyMode;
}
}
}

View File

@ -1,71 +0,0 @@
using System.Xml;
namespace InnerTube.Models
{
public class YoutubeChannel
{
public string Id;
public string Name;
public string Url;
public Thumbnail[] Avatars;
public Thumbnail[] Banners;
public string Description;
public DynamicItem[] Videos;
public string Subscribers;
public string GetHtmlDescription()
{
return Utils.GetHtmlDescription(Description);
}
public XmlDocument GetXmlDocument()
{
XmlDocument doc = new();
XmlElement channel = doc.CreateElement("Channel");
channel.SetAttribute("id", Id);
if (Id != Url)
channel.SetAttribute("customUrl", Url);
XmlElement metadata = doc.CreateElement("Metadata");
XmlElement name = doc.CreateElement("Name");
name.InnerText = Name;
metadata.AppendChild(name);
XmlElement avatars = doc.CreateElement("Avatars");
foreach (Thumbnail t in Avatars)
{
XmlElement thumbnail = doc.CreateElement("Thumbnail");
thumbnail.SetAttribute("width", t.Width.ToString());
thumbnail.SetAttribute("height", t.Height.ToString());
thumbnail.InnerText = t.Url;
avatars.AppendChild(thumbnail);
}
metadata.AppendChild(avatars);
XmlElement banners = doc.CreateElement("Banners");
foreach (Thumbnail t in Banners)
{
XmlElement thumbnail = doc.CreateElement("Thumbnail");
thumbnail.SetAttribute("width", t.Width.ToString());
thumbnail.SetAttribute("height", t.Height.ToString());
thumbnail.InnerText = t.Url;
banners.AppendChild(thumbnail);
}
metadata.AppendChild(banners);
XmlElement subscriberCount = doc.CreateElement("Subscribers");
subscriberCount.InnerText = Subscribers;
metadata.AppendChild(subscriberCount);
channel.AppendChild(metadata);
XmlElement contents = doc.CreateElement("Contents");
foreach (DynamicItem item in Videos) contents.AppendChild(item.GetXmlElement(doc));
channel.AppendChild(contents);
doc.AppendChild(channel);
return doc;
}
}
}

View File

@ -1,40 +0,0 @@
using System.Collections.Generic;
using System.Xml;
namespace InnerTube.Models
{
public class YoutubeLocals
{
public Dictionary<string, string> Languages { get; set; }
public Dictionary<string, string> Regions { get; set; }
public XmlDocument GetXmlDocument()
{
XmlDocument doc = new();
XmlElement locals = doc.CreateElement("Locals");
XmlElement languages = doc.CreateElement("Languages");
foreach (KeyValuePair<string, string> l in Languages)
{
XmlElement language = doc.CreateElement("Language");
language.SetAttribute("hl", l.Key);
language.InnerText = l.Value;
languages.AppendChild(language);
}
locals.AppendChild(languages);
XmlElement regions = doc.CreateElement("Regions");
foreach (KeyValuePair<string, string> r in Regions)
{
XmlElement region = doc.CreateElement("Region");
region.SetAttribute("gl", r.Key);
region.InnerText = r.Value;
regions.AppendChild(region);
}
locals.AppendChild(regions);
doc.AppendChild(locals);
return doc;
}
}
}

View File

@ -1,230 +0,0 @@
using System;
using System.Xml;
using Newtonsoft.Json;
namespace InnerTube.Models
{
public class YoutubePlayer
{
public string Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string[] Tags { get; set; }
public Channel Channel { get; set; }
public long? Duration { get; set; }
public bool IsLive { get; set; }
public Chapter[] Chapters { get; set; }
public Thumbnail[] Thumbnails { get; set; }
public Format[] Formats { get; set; }
public Format[] AdaptiveFormats { get; set; }
public string HlsManifestUrl { get; set; }
public Subtitle[] Subtitles { get; set; }
public string[] Storyboards { get; set; }
public string ExpiresInSeconds { get; set; }
public string ErrorMessage { get; set; }
public string GetHtmlDescription()
{
return Utils.GetHtmlDescription(Description);
}
public XmlDocument GetXmlDocument()
{
XmlDocument doc = new();
if (!string.IsNullOrWhiteSpace(ErrorMessage))
{
XmlElement error = doc.CreateElement("Error");
error.InnerText = ErrorMessage;
doc.AppendChild(error);
}
else
{
XmlElement player = doc.CreateElement("Player");
player.SetAttribute("id", Id);
player.SetAttribute("duration", Duration.ToString());
player.SetAttribute("isLive", IsLive.ToString());
player.SetAttribute("expiresInSeconds", ExpiresInSeconds);
XmlElement title = doc.CreateElement("Title");
title.InnerText = Title;
player.AppendChild(title);
XmlElement description = doc.CreateElement("Description");
description.InnerText = Description;
player.AppendChild(description);
XmlElement tags = doc.CreateElement("Tags");
foreach (string tag in Tags ?? Array.Empty<string>())
{
XmlElement tagElement = doc.CreateElement("Tag");
tagElement.InnerText = tag;
tags.AppendChild(tagElement);
}
player.AppendChild(tags);
player.AppendChild(Channel.GetXmlElement(doc));
XmlElement thumbnails = doc.CreateElement("Thumbnails");
foreach (Thumbnail t in Thumbnails)
{
XmlElement thumbnail = doc.CreateElement("Thumbnail");
thumbnail.SetAttribute("width", t.Width.ToString());
thumbnail.SetAttribute("height", t.Height.ToString());
thumbnail.InnerText = t.Url;
thumbnails.AppendChild(thumbnail);
}
player.AppendChild(thumbnails);
XmlElement formats = doc.CreateElement("Formats");
foreach (Format f in Formats ?? Array.Empty<Format>()) formats.AppendChild(f.GetXmlElement(doc));
player.AppendChild(formats);
XmlElement adaptiveFormats = doc.CreateElement("AdaptiveFormats");
foreach (Format f in AdaptiveFormats ?? Array.Empty<Format>()) adaptiveFormats.AppendChild(f.GetXmlElement(doc));
player.AppendChild(adaptiveFormats);
XmlElement storyboards = doc.CreateElement("Storyboards");
foreach (string s in Storyboards)
{
XmlElement storyboard = doc.CreateElement("Storyboard");
storyboard.InnerText = s;
storyboards.AppendChild(storyboard);
}
player.AppendChild(storyboards);
XmlElement subtitles = doc.CreateElement("Subtitles");
foreach (Subtitle s in Subtitles ?? Array.Empty<Subtitle>()) subtitles.AppendChild(s.GetXmlElement(doc));
player.AppendChild(subtitles);
doc.AppendChild(player);
}
return doc;
}
}
public class Chapter
{
[JsonProperty("title")] public string Title { get; set; }
[JsonProperty("start_time")] public long StartTime { get; set; }
[JsonProperty("end_time")] public long EndTime { get; set; }
}
public class Format
{
[JsonProperty("format")] public string FormatName { get; set; }
[JsonProperty("format_id")] public string FormatId { get; set; }
[JsonProperty("format_note")] public string FormatNote { get; set; }
[JsonProperty("filesize")] public long? Filesize { get; set; }
[JsonProperty("quality")] public long Quality { get; set; }
[JsonProperty("bitrate")] public double Bitrate { get; set; }
[JsonProperty("audio_codec")] public string AudioCodec { get; set; }
[JsonProperty("video_codec")] public string VideoCodec { get; set; }
[JsonProperty("audio_sample_rate")] public long? AudioSampleRate { get; set; }
[JsonProperty("resolution")] public string Resolution { get; set; }
[JsonProperty("url")] public string Url { get; set; }
[JsonProperty("init_range")] public Range InitRange { get; set; }
[JsonProperty("index_range")] public Range IndexRange { get; set; }
public XmlElement GetXmlElement(XmlDocument doc)
{
XmlElement format = doc.CreateElement("Format");
format.SetAttribute("id", FormatId);
format.SetAttribute("label", FormatName);
format.SetAttribute("filesize", Filesize.ToString());
format.SetAttribute("quality", Bitrate.ToString());
format.SetAttribute("audioCodec", AudioCodec);
format.SetAttribute("videoCodec", VideoCodec);
if (AudioSampleRate != null)
format.SetAttribute("audioSampleRate", AudioSampleRate.ToString());
else
format.SetAttribute("resolution", Resolution);
XmlElement url = doc.CreateElement("URL");
url.InnerText = Url;
format.AppendChild(url);
if (InitRange != null && IndexRange != null)
{
XmlElement initRange = doc.CreateElement("InitRange");
initRange.SetAttribute("start", InitRange.Start);
initRange.SetAttribute("end", InitRange.End);
format.AppendChild(initRange);
XmlElement indexRange = doc.CreateElement("IndexRange");
indexRange.SetAttribute("start", IndexRange.Start);
indexRange.SetAttribute("end", IndexRange.End);
format.AppendChild(indexRange);
}
return format;
}
}
public class Range
{
[JsonProperty("start")] public string Start { get; set; }
[JsonProperty("end")] public string End { get; set; }
public Range(string start, string end)
{
Start = start;
End = end;
}
}
public class Channel
{
[JsonProperty("name")] public string Name { get; set; }
[JsonProperty("id")] public string Id { get; set; }
[JsonProperty("subscriberCount")] public string SubscriberCount { get; set; }
[JsonProperty("avatars")] public Thumbnail[] Avatars { get; set; }
public XmlElement GetXmlElement(XmlDocument doc)
{
XmlElement channel = doc.CreateElement("Channel");
channel.SetAttribute("id", Id);
if (!string.IsNullOrWhiteSpace(SubscriberCount))
channel.SetAttribute("subscriberCount", SubscriberCount);
XmlElement name = doc.CreateElement("Name");
name.InnerText = Name;
channel.AppendChild(name);
foreach (Thumbnail avatarThumb in Avatars ?? Array.Empty<Thumbnail>())
{
XmlElement avatar = doc.CreateElement("Avatar");
avatar.SetAttribute("width", avatarThumb.Width.ToString());
avatar.SetAttribute("height", avatarThumb.Height.ToString());
avatar.InnerText = avatarThumb.Url;
channel.AppendChild(avatar);
}
return channel;
}
}
public class Subtitle
{
[JsonProperty("ext")] public string Ext { get; set; }
[JsonProperty("name")] public string Language { get; set; }
[JsonProperty("url")] public string Url { get; set; }
public XmlElement GetXmlElement(XmlDocument doc)
{
XmlElement subtitle = doc.CreateElement("Subtitle");
subtitle.SetAttribute("ext", Ext);
subtitle.SetAttribute("language", Language);
subtitle.InnerText = Url;
return subtitle;
}
}
public class Thumbnail
{
[JsonProperty("height")] public long Height { get; set; }
[JsonProperty("url")] public string Url { get; set; }
[JsonProperty("width")] public long Width { get; set; }
}
}

View File

@ -1,68 +0,0 @@
using System.Xml;
namespace InnerTube.Models
{
public class YoutubePlaylist
{
public string Id;
public string Title;
public string Description;
public string VideoCount;
public string ViewCount;
public string LastUpdated;
public Thumbnail[] Thumbnail;
public Channel Channel;
public DynamicItem[] Videos;
public string ContinuationKey;
public string GetHtmlDescription() => Utils.GetHtmlDescription(Description);
public XmlDocument GetXmlDocument()
{
XmlDocument doc = new();
XmlElement playlist = doc.CreateElement("Playlist");
playlist.SetAttribute("id", Id);
playlist.SetAttribute("continuation", ContinuationKey);
XmlElement metadata = doc.CreateElement("Metadata");
XmlElement title = doc.CreateElement("Title");
title.InnerText = Title;
metadata.AppendChild(title);
metadata.AppendChild(Channel.GetXmlElement(doc));
XmlElement thumbnails = doc.CreateElement("Thumbnails");
foreach (Thumbnail t in Thumbnail)
{
XmlElement thumbnail = doc.CreateElement("Thumbnail");
thumbnail.SetAttribute("width", t.Width.ToString());
thumbnail.SetAttribute("height", t.Height.ToString());
thumbnail.InnerText = t.Url;
thumbnails.AppendChild(thumbnail);
}
metadata.AppendChild(thumbnails);
XmlElement videoCount = doc.CreateElement("VideoCount");
XmlElement viewCount = doc.CreateElement("ViewCount");
XmlElement lastUpdated = doc.CreateElement("LastUpdated");
videoCount.InnerText = VideoCount;
viewCount.InnerText = ViewCount;
lastUpdated.InnerText = LastUpdated;
metadata.AppendChild(videoCount);
metadata.AppendChild(viewCount);
metadata.AppendChild(lastUpdated);
playlist.AppendChild(metadata);
XmlElement results = doc.CreateElement("Videos");
foreach (DynamicItem result in Videos) results.AppendChild(result.GetXmlElement(doc));
playlist.AppendChild(results);
doc.AppendChild(playlist);
return doc;
}
}
}

View File

@ -1,39 +0,0 @@
using System.Xml;
namespace InnerTube.Models
{
public class YoutubeSearchResults
{
public string[] Refinements;
public long EstimatedResults;
public DynamicItem[] Results;
public string ContinuationKey;
public XmlDocument GetXmlDocument()
{
XmlDocument doc = new();
XmlElement search = doc.CreateElement("Search");
search.SetAttribute("estimatedResults", EstimatedResults.ToString());
search.SetAttribute("continuation", ContinuationKey);
if (Refinements.Length > 0)
{
XmlElement refinements = doc.CreateElement("Refinements");
foreach (string refinementText in Refinements)
{
XmlElement refinement = doc.CreateElement("Refinement");
refinement.InnerText = refinementText;
refinements.AppendChild(refinement);
}
search.AppendChild(refinements);
}
XmlElement results = doc.CreateElement("Results");
foreach (DynamicItem result in Results) results.AppendChild(result.GetXmlElement(doc));
search.AppendChild(results);
doc.AppendChild(search);
return doc;
}
}
}

View File

@ -1,38 +0,0 @@
using System;
using System.Collections.Generic;
namespace InnerTube.Models
{
public class YoutubeStoryboardSpec
{
public Dictionary<string, string> Urls = new();
public YoutubeStoryboardSpec(string specStr, long duration)
{
if (specStr is null) return;
List<string> spec = new(specStr.Split("|"));
string baseUrl = spec[0];
spec.RemoveAt(0);
spec.Reverse();
int L = spec.Count - 1;
for (int i = 0; i < spec.Count; i++)
{
string[] args = spec[i].Split("#");
int width = int.Parse(args[0]);
int height = int.Parse(args[1]);
int frameCount = int.Parse(args[2]);
int cols = int.Parse(args[3]);
int rows = int.Parse(args[4]);
string N = args[6];
string sigh = args[7];
string url = baseUrl
.Replace("$L", (spec.Count - 1 - i).ToString())
.Replace("$N", N) + "&sigh=" + sigh;
float fragmentCount = frameCount / (cols * rows);
float fragmentDuration = duration / fragmentCount;
for (int j = 0; j < Math.Ceiling(fragmentCount); j++)
Urls.TryAdd($"L{spec.Count - 1 - i}", url.Replace("$M", j.ToString()));
}
}
}
}

View File

@ -1,70 +0,0 @@
using System;
using System.Xml;
namespace InnerTube.Models
{
public class YoutubeTrends
{
public TrendCategory[] Categories;
public DynamicItem[] Videos;
public XmlDocument GetXmlDocument()
{
XmlDocument doc = new();
XmlElement explore = doc.CreateElement("Explore");
XmlElement categories = doc.CreateElement("Categories");
foreach (TrendCategory category in Categories ?? Array.Empty<TrendCategory>()) categories.AppendChild(category.GetXmlElement(doc));
explore.AppendChild(categories);
XmlElement contents = doc.CreateElement("Videos");
foreach (DynamicItem item in Videos ?? Array.Empty<DynamicItem>()) contents.AppendChild(item.GetXmlElement(doc));
explore.AppendChild(contents);
doc.AppendChild(explore);
return doc;
}
}
public class TrendCategory
{
public string Label;
public Thumbnail[] BackgroundImage;
public Thumbnail[] Icon;
public string Id;
public XmlElement GetXmlElement(XmlDocument doc)
{
XmlElement category = doc.CreateElement("Category");
category.SetAttribute("id", Id);
XmlElement title = doc.CreateElement("Name");
title.InnerText = Label;
category.AppendChild(title);
XmlElement backgroundImages = doc.CreateElement("BackgroundImage");
foreach (Thumbnail t in BackgroundImage ?? Array.Empty<Thumbnail>())
{
XmlElement thumbnail = doc.CreateElement("Thumbnail");
thumbnail.SetAttribute("width", t.Width.ToString());
thumbnail.SetAttribute("height", t.Height.ToString());
thumbnail.InnerText = t.Url;
backgroundImages.AppendChild(thumbnail);
}
category.AppendChild(backgroundImages);
XmlElement icons = doc.CreateElement("Icon");
foreach (Thumbnail t in Icon ?? Array.Empty<Thumbnail>())
{
XmlElement thumbnail = doc.CreateElement("Thumbnail");
thumbnail.SetAttribute("width", t.Width.ToString());
thumbnail.SetAttribute("height", t.Height.ToString());
thumbnail.InnerText = t.Url;
icons.AppendChild(thumbnail);
}
category.AppendChild(icons);
return category;
}
}
}

View File

@ -1,45 +0,0 @@
using System;
using System.Xml;
namespace InnerTube.Models
{
public class YoutubeVideo
{
public string Id;
public string Title;
public string Description;
public Channel Channel;
public string UploadDate;
public DynamicItem[] Recommended;
public string Views;
public string GetHtmlDescription() => InnerTube.Utils.GetHtmlDescription(Description);
public XmlDocument GetXmlDocument()
{
XmlDocument doc = new();
XmlElement item = doc.CreateElement("Video");
item.SetAttribute("id", Id);
item.SetAttribute("views", Views);
item.SetAttribute("uploadDate", UploadDate);
XmlElement title = doc.CreateElement("Title");
title.InnerText = Title;
item.AppendChild(title);
XmlElement description = doc.CreateElement("Description");
description.InnerText = Description;
item.AppendChild(description);
item.AppendChild(Channel.GetXmlElement(doc));
XmlElement recommendations = doc.CreateElement("Recommendations");
foreach (DynamicItem f in Recommended ?? Array.Empty<DynamicItem>()) recommendations.AppendChild(f.GetXmlElement(doc));
item.AppendChild(recommendations);
doc.AppendChild(item);
return doc;
}
}
}

View File

@ -1,44 +0,0 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace InnerTube
{
public static class ReturnYouTubeDislike
{
private static HttpClient _client = new();
private static Dictionary<string, YoutubeDislikes> DislikesCache = new();
// TODO: better cache
public static async Task<YoutubeDislikes> GetDislikes(string videoId)
{
if (DislikesCache.ContainsKey(videoId))
return DislikesCache[videoId];
HttpResponseMessage response = await _client.GetAsync("https://returnyoutubedislikeapi.com/votes?videoId=" + videoId);
string json = await response.Content.ReadAsStringAsync();
YoutubeDislikes dislikes = JsonConvert.DeserializeObject<YoutubeDislikes>(json);
if (dislikes is not null)
DislikesCache.Add(videoId, dislikes);
return dislikes ?? new YoutubeDislikes();
}
}
public class YoutubeDislikes
{
[JsonProperty("id")] public string Id { get; set; }
[JsonProperty("dateCreated")] public string DateCreated { get; set; }
[JsonProperty("likes")] public long Likes { get; set; }
[JsonProperty("dislikes")] public long Dislikes { get; set; }
[JsonProperty("rating")] public double Rating { get; set; }
[JsonProperty("viewCount")] public long Views { get; set; }
[JsonProperty("deleted")] public bool Deleted { get; set; }
public float GetLikePercentage()
{
return Likes / (float)(Likes + Dislikes) * 100;
}
}
}

View File

@ -1,457 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using System.Xml;
using InnerTube.Models;
using Newtonsoft.Json.Linq;
namespace InnerTube
{
public static class Utils
{
private static string Sapisid;
private static string Psid;
private static bool UseAuthorization;
public static string GetHtmlDescription(string description) => description?.Replace("\n", "<br>") ?? "";
public static string GetMpdManifest(this YoutubePlayer player, string proxyUrl, string videoCodec = null, string audioCodec = null)
{
XmlDocument doc = new();
XmlDeclaration xmlDeclaration = doc.CreateXmlDeclaration("1.0", "UTF-8", null);
XmlElement root = doc.DocumentElement;
doc.InsertBefore(xmlDeclaration, root);
XmlElement mpdRoot = doc.CreateElement(string.Empty, "MPD", string.Empty);
mpdRoot.SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
mpdRoot.SetAttribute("xmlns", "urn:mpeg:dash:schema:mpd:2011");
mpdRoot.SetAttribute("xsi:schemaLocation", "urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd");
//mpdRoot.SetAttribute("profiles", "urn:mpeg:dash:profile:isoff-on-demand:2011");
mpdRoot.SetAttribute("profiles", "urn:mpeg:dash:profile:isoff-main:2011");
mpdRoot.SetAttribute("type", "static");
mpdRoot.SetAttribute("minBufferTime", "PT1.500S");
TimeSpan durationTs = TimeSpan.FromMilliseconds(double.Parse(HttpUtility
.ParseQueryString(player.Formats.First().Url.Split("?")[1])
.Get("dur")?.Replace(".", "") ?? "0"));
StringBuilder duration = new("PT");
if (durationTs.TotalHours > 0)
duration.Append($"{durationTs.Hours}H");
if (durationTs.Minutes > 0)
duration.Append($"{durationTs.Minutes}M");
if (durationTs.Seconds > 0)
duration.Append(durationTs.Seconds);
mpdRoot.SetAttribute("mediaPresentationDuration", $"{duration}.{durationTs.Milliseconds}S");
doc.AppendChild(mpdRoot);
XmlElement period = doc.CreateElement("Period");
period.AppendChild(doc.CreateComment("Audio Adaptation Set"));
XmlElement audioAdaptationSet = doc.CreateElement("AdaptationSet");
List<Format> audios;
if (audioCodec != "all")
audios = player.AdaptiveFormats
.Where(x => x.AudioSampleRate.HasValue && x.FormatId != "17" &&
(audioCodec == null || x.AudioCodec.ToLower().Contains(audioCodec.ToLower())))
.GroupBy(x => x.FormatNote)
.Select(x => x.Last())
.ToList();
else
audios = player.AdaptiveFormats
.Where(x => x.AudioSampleRate.HasValue && x.FormatId != "17")
.ToList();
audioAdaptationSet.SetAttribute("mimeType",
HttpUtility.ParseQueryString(audios.First().Url.Split("?")[1]).Get("mime"));
audioAdaptationSet.SetAttribute("subsegmentAlignment", "true");
audioAdaptationSet.SetAttribute("contentType", "audio");
foreach (Format format in audios)
{
XmlElement representation = doc.CreateElement("Representation");
representation.SetAttribute("id", format.FormatId);
representation.SetAttribute("codecs", format.AudioCodec);
representation.SetAttribute("startWithSAP", "1");
representation.SetAttribute("bandwidth",
Math.Floor((format.Filesize ?? 1) / (double)player.Duration).ToString());
XmlElement audioChannelConfiguration = doc.CreateElement("AudioChannelConfiguration");
audioChannelConfiguration.SetAttribute("schemeIdUri",
"urn:mpeg:dash:23003:3:audio_channel_configuration:2011");
audioChannelConfiguration.SetAttribute("value", "2");
representation.AppendChild(audioChannelConfiguration);
XmlElement baseUrl = doc.CreateElement("BaseURL");
baseUrl.InnerText = string.IsNullOrWhiteSpace(proxyUrl) ? format.Url : $"{proxyUrl}media/{player.Id}/{format.FormatId}";
representation.AppendChild(baseUrl);
if (format.IndexRange != null && format.InitRange != null)
{
XmlElement segmentBase = doc.CreateElement("SegmentBase");
segmentBase.SetAttribute("indexRange", $"{format.IndexRange.Start}-{format.IndexRange.End}");
segmentBase.SetAttribute("indexRangeExact", "true");
XmlElement initialization = doc.CreateElement("Initialization");
initialization.SetAttribute("range", $"{format.InitRange.Start}-{format.InitRange.End}");
segmentBase.AppendChild(initialization);
representation.AppendChild(segmentBase);
}
audioAdaptationSet.AppendChild(representation);
}
period.AppendChild(audioAdaptationSet);
period.AppendChild(doc.CreateComment("Video Adaptation Set"));
List<Format> videos;
if (videoCodec != "all")
videos = player.AdaptiveFormats.Where(x => !x.AudioSampleRate.HasValue && x.FormatId != "17" &&
(videoCodec == null || x.VideoCodec.ToLower()
.Contains(videoCodec.ToLower())))
.GroupBy(x => x.FormatNote)
.Select(x => x.Last())
.ToList();
else
videos = player.AdaptiveFormats.Where(x => x.Resolution != "audio only" && x.FormatId != "17").ToList();
XmlElement videoAdaptationSet = doc.CreateElement("AdaptationSet");
videoAdaptationSet.SetAttribute("mimeType",
HttpUtility.ParseQueryString(videos.FirstOrDefault()?.Url?.Split("?")[1] ?? "mime=video/mp4")
.Get("mime"));
videoAdaptationSet.SetAttribute("subsegmentAlignment", "true");
videoAdaptationSet.SetAttribute("contentType", "video");
foreach (Format format in videos)
{
XmlElement representation = doc.CreateElement("Representation");
representation.SetAttribute("id", format.FormatId);
representation.SetAttribute("codecs", format.VideoCodec);
representation.SetAttribute("startWithSAP", "1");
string[] widthAndHeight = format.Resolution.Split("x");
representation.SetAttribute("width", widthAndHeight[0]);
representation.SetAttribute("height", widthAndHeight[1]);
representation.SetAttribute("bandwidth",
Math.Floor((format.Filesize ?? 1) / (double)player.Duration).ToString());
XmlElement baseUrl = doc.CreateElement("BaseURL");
baseUrl.InnerText = string.IsNullOrWhiteSpace(proxyUrl) ? format.Url : $"{proxyUrl}media/{player.Id}/{format.FormatId}";
representation.AppendChild(baseUrl);
if (format.IndexRange != null && format.InitRange != null)
{
XmlElement segmentBase = doc.CreateElement("SegmentBase");
segmentBase.SetAttribute("indexRange", $"{format.IndexRange.Start}-{format.IndexRange.End}");
segmentBase.SetAttribute("indexRangeExact", "true");
XmlElement initialization = doc.CreateElement("Initialization");
initialization.SetAttribute("range", $"{format.InitRange.Start}-{format.InitRange.End}");
segmentBase.AppendChild(initialization);
representation.AppendChild(segmentBase);
}
videoAdaptationSet.AppendChild(representation);
}
period.AppendChild(videoAdaptationSet);
period.AppendChild(doc.CreateComment("Subtitle Adaptation Sets"));
foreach (Subtitle subtitle in player.Subtitles ?? Array.Empty<Subtitle>())
{
period.AppendChild(doc.CreateComment(subtitle.Language));
XmlElement adaptationSet = doc.CreateElement("AdaptationSet");
adaptationSet.SetAttribute("mimeType", "text/vtt");
adaptationSet.SetAttribute("lang", subtitle.Language);
XmlElement representation = doc.CreateElement("Representation");
representation.SetAttribute("id", $"caption_{subtitle.Language.ToLower()}");
representation.SetAttribute("bandwidth", "256"); // ...why do we need this for a plaintext file
XmlElement baseUrl = doc.CreateElement("BaseURL");
string url = subtitle.Url;
url = url.Replace("fmt=srv3", "fmt=vtt");
baseUrl.InnerText = string.IsNullOrWhiteSpace(proxyUrl) ? url : $"{proxyUrl}caption/{player.Id}/{subtitle.Language}";
representation.AppendChild(baseUrl);
adaptationSet.AppendChild(representation);
period.AppendChild(adaptationSet);
}
mpdRoot.AppendChild(period);
return doc.OuterXml.Replace(" schemaLocation=\"", " xsi:schemaLocation=\"");
}
public static async Task<string> GetHlsManifest(this YoutubePlayer player, string proxyUrl)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("#EXTM3U");
sb.AppendLine("##Generated by LightTube");
sb.AppendLine("##Video ID: " + player.Id);
sb.AppendLine("#EXT-X-VERSION:7");
sb.AppendLine("#EXT-X-INDEPENDENT-SEGMENTS");
string hls = await new HttpClient().GetStringAsync(player.HlsManifestUrl);
string[] hlsLines = hls.Split("\n");
foreach (string line in hlsLines)
{
if (line.StartsWith("#EXT-X-STREAM-INF:"))
sb.AppendLine(line);
if (line.StartsWith("http"))
{
Uri u = new(line);
sb.AppendLine($"{proxyUrl}/ytmanifest?path={HttpUtility.UrlEncode(u.PathAndQuery)}");
}
}
return sb.ToString();
}
public static string ReadRuns(JArray runs)
{
string str = "";
foreach (JToken runToken in runs ?? new JArray())
{
JObject run = runToken as JObject;
if (run is null) continue;
if (run.ContainsKey("bold"))
{
str += "<b>" + run["text"] + "</b>";
}
else if (run.ContainsKey("navigationEndpoint"))
{
if (run?["navigationEndpoint"]?["urlEndpoint"] is not null)
{
string url = run["navigationEndpoint"]?["urlEndpoint"]?["url"]?.ToString() ?? "";
if (url.StartsWith("https://www.youtube.com/redirect"))
{
NameValueCollection qsl = HttpUtility.ParseQueryString(url.Split("?")[1]);
url = qsl["url"] ?? qsl["q"];
}
str += $"<a href=\"{url}\">{run["text"]}</a>";
}
else if (run?["navigationEndpoint"]?["commandMetadata"] is not null)
{
string url = run["navigationEndpoint"]?["commandMetadata"]?["webCommandMetadata"]?["url"]
?.ToString() ?? "";
if (url.StartsWith("/"))
url = "https://youtube.com" + url;
str += $"<a href=\"{url}\">{run["text"]}</a>";
}
}
else
{
str += run["text"];
}
}
return str;
}
public static Thumbnail ParseThumbnails(JToken arg) => new()
{
Height = arg["height"]?.ToObject<long>() ?? -1,
Url = arg["url"]?.ToString() ?? string.Empty,
Width = arg["width"]?.ToObject<long>() ?? -1
};
public static async Task<JObject> GetAuthorizedPlayer(string id, HttpClient client)
{
HttpRequestMessage hrm = new(HttpMethod.Post,
"https://www.youtube.com/youtubei/v1/player?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8");
byte[] buffer = Encoding.UTF8.GetBytes(
RequestContext.BuildRequestContextJson(new Dictionary<string, object>
{
["videoId"] = id
}));
ByteArrayContent byteContent = new(buffer);
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
hrm.Content = byteContent;
if (UseAuthorization)
{
hrm.Headers.Add("Cookie", GenerateAuthCookie());
hrm.Headers.Add("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:96.0) Gecko/20100101 Firefox/96.0");
hrm.Headers.Add("Authorization", GenerateAuthHeader());
hrm.Headers.Add("X-Origin", "https://www.youtube.com");
hrm.Headers.Add("X-Youtube-Client-Name", "1");
hrm.Headers.Add("X-Youtube-Client-Version", "2.20210721.00.00");
hrm.Headers.Add("Accept-Language", "en-US;q=0.8,en;q=0.7");
hrm.Headers.Add("Origin", "https://www.youtube.com");
hrm.Headers.Add("Referer", "https://www.youtube.com/watch?v=" + id);
}
HttpResponseMessage ytPlayerRequest = await client.SendAsync(hrm);
return JObject.Parse(await ytPlayerRequest.Content.ReadAsStringAsync());
}
internal static string GenerateAuthHeader()
{
if (!UseAuthorization) return "None none";
long timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
string hashInput = timestamp + " " + Sapisid + " https://www.youtube.com";
string hashDigest = GenerateSha1Hash(hashInput);
return $"SAPISIDHASH {timestamp}_{hashDigest}";
}
internal static string GenerateAuthCookie() => UseAuthorization ? $"SAPISID={Sapisid}; __Secure-3PAPISID={Sapisid}; __Secure-3PSID={Psid};" : ";";
private static string GenerateSha1Hash(string input)
{
using SHA1Managed sha1 = new();
byte[] hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(input));
StringBuilder sb = new(hash.Length * 2);
foreach (byte b in hash) sb.Append(b.ToString("X2"));
return sb.ToString();
}
public static string GetExtension(this Format format)
{
if (format.VideoCodec != "none") return "mp4";
else
switch (format.FormatId)
{
case "139":
case "140":
case "141":
case "256":
case "258":
case "327":
return "mp3";
case "249":
case "250":
case "251":
case "338":
return "opus";
}
return "mp4";
}
public static void SetAuthorization(bool canUseAuthorizedEndpoints, string sapisid, string psid)
{
UseAuthorization = canUseAuthorizedEndpoints;
Sapisid = sapisid;
Psid = psid;
}
internal static string GetCodec(string mimetypeString, bool audioCodec)
{
string acodec = "";
string vcodec = "";
Match match = Regex.Match(mimetypeString, "codecs=\"([\\s\\S]+?)\"");
string[] g = match.Groups[1].ToString().Split(",");
foreach (string codec in g)
{
switch (codec.Split(".")[0].Trim())
{
case "avc1":
case "av01":
case "vp9":
case "mp4v":
vcodec = codec;
break;
case "mp4a":
case "opus":
acodec = codec;
break;
default:
Console.WriteLine("Unknown codec type: " + codec.Split(".")[0].Trim());
break;
}
}
return (audioCodec ? acodec : vcodec).Trim();
}
public static string GetFormatName(JToken formatToken)
{
string format = formatToken["itag"]?.ToString() switch
{
"160" => "144p",
"278" => "144p",
"330" => "144p",
"394" => "144p",
"694" => "144p",
"133" => "240p",
"242" => "240p",
"331" => "240p",
"395" => "240p",
"695" => "240p",
"134" => "360p",
"243" => "360p",
"332" => "360p",
"396" => "360p",
"696" => "360p",
"135" => "480p",
"244" => "480p",
"333" => "480p",
"397" => "480p",
"697" => "480p",
"136" => "720p",
"247" => "720p",
"298" => "720p",
"302" => "720p",
"334" => "720p",
"398" => "720p",
"698" => "720p",
"137" => "1080p",
"299" => "1080p",
"248" => "1080p",
"303" => "1080p",
"335" => "1080p",
"399" => "1080p",
"699" => "1080p",
"264" => "1440p",
"271" => "1440p",
"304" => "1440p",
"308" => "1440p",
"336" => "1440p",
"400" => "1440p",
"700" => "1440p",
"266" => "2160p",
"305" => "2160p",
"313" => "2160p",
"315" => "2160p",
"337" => "2160p",
"401" => "2160p",
"701" => "2160p",
"138" => "4320p",
"272" => "4320p",
"402" => "4320p",
"571" => "4320p",
var _ => $"{formatToken["height"]}p",
};
return format == "p"
? formatToken["audioQuality"]?.ToString().ToLowerInvariant()
: (formatToken["fps"]?.ToObject<int>() ?? 0) > 30
? $"{format}{formatToken["fps"]}"
: format;
}
}
}

View File

@ -1,790 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using InnerTube.Models;
using Newtonsoft.Json.Linq;
namespace InnerTube
{
public class Youtube
{
internal readonly HttpClient Client = new();
public readonly Dictionary<string, CacheItem<YoutubePlayer>> PlayerCache = new();
private readonly Dictionary<ChannelTabs, string> ChannelTabParams = new()
{
[ChannelTabs.Home] = @"EghmZWF0dXJlZA%3D%3D",
[ChannelTabs.Videos] = @"EgZ2aWRlb3M%3D",
[ChannelTabs.Playlists] = @"EglwbGF5bGlzdHM%3D",
[ChannelTabs.Community] = @"Egljb21tdW5pdHk%3D",
[ChannelTabs.Channels] = @"EghjaGFubmVscw%3D%3D",
[ChannelTabs.About] = @"EgVhYm91dA%3D%3D"
};
private async Task<JObject> MakeRequest(string endpoint, Dictionary<string, object> postData, string language,
string region, string clientName = "WEB", string clientId = "1", string clientVersion = "2.20220405", bool authorized = false)
{
HttpRequestMessage hrm = new(HttpMethod.Post,
@$"https://www.youtube.com/youtubei/v1/{endpoint}?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8");
byte[] buffer = Encoding.UTF8.GetBytes(RequestContext.BuildRequestContextJson(postData, language, region, clientName, clientVersion));
ByteArrayContent byteContent = new(buffer);
if (authorized)
{
hrm.Headers.Add("Cookie", Utils.GenerateAuthCookie());
hrm.Headers.Add("Authorization", Utils.GenerateAuthHeader());
hrm.Headers.Add("X-Youtube-Client-Name", clientId);
hrm.Headers.Add("X-Youtube-Client-Version", clientVersion);
hrm.Headers.Add("Origin", "https://www.youtube.com");
}
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
hrm.Content = byteContent;
HttpResponseMessage ytPlayerRequest = await Client.SendAsync(hrm);
return JObject.Parse(await ytPlayerRequest.Content.ReadAsStringAsync());
}
public async Task<YoutubePlayer> GetPlayerAsync(string videoId, string language = "en", string region = "US", bool iOS = false)
{
if (PlayerCache.Any(x => x.Key == videoId && x.Value.ExpireTime > DateTimeOffset.Now))
{
CacheItem<YoutubePlayer> item = PlayerCache[videoId];
item.Item.ExpiresInSeconds = ((int)(item.ExpireTime - DateTimeOffset.Now).TotalSeconds).ToString();
return item.Item;
}
JObject player = await MakeRequest("player", new Dictionary<string, object>
{
["videoId"] = videoId,
["contentCheckOk"] = true,
["racyCheckOk"] = true
}, language, region, iOS ? "IOS" : "ANDROID", iOS ? "5" : "3", "17.13.3", true);
switch (player["playabilityStatus"]?["status"]?.ToString())
{
case "OK":
YoutubeStoryboardSpec storyboardSpec =
new(player["storyboards"]?["playerStoryboardSpecRenderer"]?["spec"]?.ToString(), player["videoDetails"]?["lengthSeconds"]?.ToObject<long>() ?? 0);
YoutubePlayer video = new()
{
Id = player["videoDetails"]?["videoId"]?.ToString(),
Title = player["videoDetails"]?["title"]?.ToString(),
Description = player["videoDetails"]?["shortDescription"]?.ToString(),
Tags = player["videoDetails"]?["keywords"]?.ToObject<string[]>(),
Channel = new Channel
{
Name = player["videoDetails"]?["author"]?.ToString(),
Id = player["videoDetails"]?["channelId"]?.ToString(),
Avatars = Array.Empty<Thumbnail>()
},
Duration = player["videoDetails"]?["lengthSeconds"]?.ToObject<long>(),
IsLive = player["videoDetails"]?["isLiveContent"]?.ToObject<bool>() ?? false,
Chapters = Array.Empty<Chapter>(),
Thumbnails = player["videoDetails"]?["thumbnail"]?["thumbnails"]?.Select(x => new Thumbnail
{
Height = x["height"]?.ToObject<int>() ?? -1,
Url = x["url"]?.ToString(),
Width = x["width"]?.ToObject<int>() ?? -1
}).ToArray(),
Formats = player["streamingData"]?["formats"]?.Select(x => new Format
{
FormatName = Utils.GetFormatName(x),
FormatId = x["itag"]?.ToString(),
FormatNote = x["quality"]?.ToString(),
Filesize = x["contentLength"]?.ToObject<long>(),
Bitrate = x["bitrate"]?.ToObject<long>() ?? 0,
AudioCodec = Utils.GetCodec(x["mimeType"]?.ToString(), true),
VideoCodec = Utils.GetCodec(x["mimeType"]?.ToString(), false),
AudioSampleRate = x["audioSampleRate"]?.ToObject<long>(),
Resolution = $"{x["width"] ?? "0"}x{x["height"] ?? "0"}",
Url = x["url"]?.ToString()
}).ToArray() ?? Array.Empty<Format>(),
AdaptiveFormats = player["streamingData"]?["adaptiveFormats"]?.Select(x => new Format
{
FormatName = Utils.GetFormatName(x),
FormatId = x["itag"]?.ToString(),
FormatNote = x["quality"]?.ToString(),
Filesize = x["contentLength"]?.ToObject<long>(),
Bitrate = x["bitrate"]?.ToObject<long>() ?? 0,
AudioCodec = Utils.GetCodec(x["mimeType"].ToString(), true),
VideoCodec = Utils.GetCodec(x["mimeType"].ToString(), false),
AudioSampleRate = x["audioSampleRate"]?.ToObject<long>(),
Resolution = $"{x["width"] ?? "0"}x{x["height"] ?? "0"}",
Url = x["url"]?.ToString(),
InitRange = x["initRange"]?.ToObject<Models.Range>(),
IndexRange = x["indexRange"]?.ToObject<Models.Range>()
}).ToArray() ?? Array.Empty<Format>(),
HlsManifestUrl = player["streamingData"]?["hlsManifestUrl"]?.ToString(),
Subtitles = player["captions"]?["playerCaptionsTracklistRenderer"]?["captionTracks"]?.Select(
x => new Subtitle
{
Ext = HttpUtility.ParseQueryString(x["baseUrl"].ToString()).Get("fmt"),
Language = Utils.ReadRuns(x["name"]?["runs"]?.ToObject<JArray>()),
Url = x["baseUrl"].ToString()
}).ToArray(),
Storyboards = storyboardSpec.Urls.TryGetValue("L0", out string sb) ? new[] { sb } : Array.Empty<string>(),
ExpiresInSeconds = player["streamingData"]?["expiresInSeconds"]?.ToString(),
ErrorMessage = null
};
PlayerCache.Remove(videoId);
PlayerCache.Add(videoId,
new CacheItem<YoutubePlayer>(video,
TimeSpan.FromSeconds(int.Parse(video.ExpiresInSeconds ?? "21600"))
.Subtract(TimeSpan.FromHours(1))));
return video;
case "LOGIN_REQUIRED":
return new YoutubePlayer
{
Id = "",
Title = "",
Description = "",
Tags = Array.Empty<string>(),
Channel = new Channel
{
Name = "",
Id = "",
SubscriberCount = "",
Avatars = Array.Empty<Thumbnail>()
},
Duration = 0,
IsLive = false,
Chapters = Array.Empty<Chapter>(),
Thumbnails = Array.Empty<Thumbnail>(),
Formats = Array.Empty<Format>(),
AdaptiveFormats = Array.Empty<Format>(),
Subtitles = Array.Empty<Subtitle>(),
Storyboards = Array.Empty<string>(),
ExpiresInSeconds = "0",
ErrorMessage =
"This video is age-restricted. Please contact this instances authors to update their configuration"
};
default:
return new YoutubePlayer
{
Id = "",
Title = "",
Description = "",
Tags = Array.Empty<string>(),
Channel = new Channel
{
Name = "",
Id = "",
SubscriberCount = "",
Avatars = Array.Empty<Thumbnail>()
},
Duration = 0,
IsLive = false,
Chapters = Array.Empty<Chapter>(),
Thumbnails = Array.Empty<Thumbnail>(),
Formats = Array.Empty<Format>(),
AdaptiveFormats = Array.Empty<Format>(),
Subtitles = Array.Empty<Subtitle>(),
Storyboards = Array.Empty<string>(),
ExpiresInSeconds = "0",
ErrorMessage = player["playabilityStatus"]?["reason"]?.ToString() ?? "Something has gone *really* wrong"
};
}
}
public async Task<YoutubeVideo> GetVideoAsync(string videoId, string language = "en", string region = "US")
{
JObject player = await MakeRequest("next", new Dictionary<string, object>
{
["videoId"] = videoId
}, language, region);
JToken[] contents =
(player?["contents"]?["twoColumnWatchNextResults"]?["results"]?["results"]?["contents"]
?.ToObject<JArray>() ?? new JArray())
.SkipWhile(x => !x.First.Path.EndsWith("videoPrimaryInfoRenderer")).ToArray();
YoutubeVideo video = new();
video.Id = player["currentVideoEndpoint"]?["watchEndpoint"]?["videoId"]?.ToString();
try
{
video.Title = Utils.ReadRuns(
contents[0]
["videoPrimaryInfoRenderer"]?["title"]?["runs"]?.ToObject<JArray>());
video.Description = Utils.ReadRuns(
contents[1]
["videoSecondaryInfoRenderer"]?["description"]?["runs"]?.ToObject<JArray>());
video.Views = contents[0]
["videoPrimaryInfoRenderer"]?["viewCount"]?["videoViewCountRenderer"]?["viewCount"]?["simpleText"]?.ToString();
video.Channel = new Channel
{
Name =
contents[1]
["videoSecondaryInfoRenderer"]?["owner"]?["videoOwnerRenderer"]?["title"]?["runs"]?[0]?[
"text"]?.ToString(),
Id = contents[1]
["videoSecondaryInfoRenderer"]?["owner"]?["videoOwnerRenderer"]?["title"]?["runs"]?[0]?
["navigationEndpoint"]?["browseEndpoint"]?["browseId"]?.ToString(),
SubscriberCount =
contents[1]
["videoSecondaryInfoRenderer"]?["owner"]?["videoOwnerRenderer"]?["subscriberCountText"]?[
"simpleText"]?.ToString(),
Avatars =
(contents[1][
"videoSecondaryInfoRenderer"]?["owner"]?["videoOwnerRenderer"]?["thumbnail"]?[
"thumbnails"]
?.ToObject<JArray>() ?? new JArray()).Select(Utils.ParseThumbnails).ToArray()
};
video.UploadDate = contents[0][
"videoPrimaryInfoRenderer"]?["dateText"]?["simpleText"]?.ToString();
}
catch
{
video.Title ??= "";
video.Description ??= "";
video.Channel ??= new Channel
{
Name = "",
Id = "",
SubscriberCount = "",
Avatars = Array.Empty<Thumbnail>()
};
video.UploadDate ??= "";
}
video.Recommended = ParseRenderers(
player?["contents"]?["twoColumnWatchNextResults"]?["secondaryResults"]?["secondaryResults"]?
["results"]?.ToObject<JArray>() ?? new JArray());
return video;
}
public async Task<YoutubeSearchResults> SearchAsync(string query, string continuation = null,
string language = "en", string region = "US")
{
Dictionary<string, object> data = new();
if (string.IsNullOrWhiteSpace(continuation))
data.Add("query", query);
else
data.Add("continuation", continuation);
JObject search = await MakeRequest("search", data, language, region);
return new YoutubeSearchResults
{
Refinements = search?["refinements"]?.ToObject<string[]>() ?? Array.Empty<string>(),
EstimatedResults = search?["estimatedResults"]?.ToObject<long>() ?? 0,
Results = ParseRenderers(
search?["contents"]?["twoColumnSearchResultsRenderer"]?["primaryContents"]?["sectionListRenderer"]?
["contents"]?[0]?["itemSectionRenderer"]?["contents"]?.ToObject<JArray>() ??
search?["onResponseReceivedCommands"]?[0]?["appendContinuationItemsAction"]?["continuationItems"]?
[0]?["itemSectionRenderer"]?["contents"]?.ToObject<JArray>() ?? new JArray()),
ContinuationKey =
search?["contents"]?["twoColumnSearchResultsRenderer"]?["primaryContents"]?["sectionListRenderer"]?
["contents"]?[1]?["continuationItemRenderer"]?["continuationEndpoint"]?["continuationCommand"]?
["token"]?.ToString() ??
search?["onResponseReceivedCommands"]?[0]?["appendContinuationItemsAction"]?["continuationItems"]?
[1]?["continuationItemRenderer"]?["continuationEndpoint"]?["continuationCommand"]?["token"]
?.ToString() ?? ""
};
}
public async Task<YoutubePlaylist> GetPlaylistAsync(string id, string continuation = null,
string language = "en", string region = "US")
{
Dictionary<string, object> data = new();
if (string.IsNullOrWhiteSpace(continuation))
data.Add("browseId", "VL" + id);
else
data.Add("continuation", continuation);
JObject playlist = await MakeRequest("browse", data, language, region);
DynamicItem[] renderers = ParseRenderers(
playlist?["contents"]?["twoColumnBrowseResultsRenderer"]?["tabs"]?[0]?["tabRenderer"]?["content"]?
["sectionListRenderer"]?["contents"]?[0]?["itemSectionRenderer"]?["contents"]?[0]?
["playlistVideoListRenderer"]?["contents"]?.ToObject<JArray>() ??
playlist?["onResponseReceivedActions"]?[0]?["appendContinuationItemsAction"]?["continuationItems"]
?.ToObject<JArray>() ?? new JArray());
return new YoutubePlaylist
{
Id = id,
Title = playlist?["metadata"]?["playlistMetadataRenderer"]?["title"]?.ToString(),
Description = playlist?["metadata"]?["playlistMetadataRenderer"]?["description"]?.ToString(),
VideoCount = playlist?["sidebar"]?["playlistSidebarRenderer"]?["items"]?[0]?[
"playlistSidebarPrimaryInfoRenderer"]?["stats"]?[0]?["runs"]?[0]?["text"]?.ToString(),
ViewCount = playlist?["sidebar"]?["playlistSidebarRenderer"]?["items"]?[0]?[
"playlistSidebarPrimaryInfoRenderer"]?["stats"]?[1]?["simpleText"]?.ToString(),
LastUpdated = Utils.ReadRuns(playlist?["sidebar"]?["playlistSidebarRenderer"]?["items"]?[0]?[
"playlistSidebarPrimaryInfoRenderer"]?["stats"]?[2]?["runs"]?.ToObject<JArray>() ?? new JArray()),
Thumbnail = (playlist?["microformat"]?["microformatDataRenderer"]?["thumbnail"]?["thumbnails"] ??
new JArray()).Select(Utils.ParseThumbnails).ToArray(),
Channel = new Channel
{
Name =
playlist?["sidebar"]?["playlistSidebarRenderer"]?["items"]?[1]?
["playlistSidebarSecondaryInfoRenderer"]?["videoOwner"]?["videoOwnerRenderer"]?["title"]?
["runs"]?[0]?["text"]?.ToString(),
Id = playlist?["sidebar"]?["playlistSidebarRenderer"]?["items"]?[1]?
["playlistSidebarSecondaryInfoRenderer"]?["videoOwner"]?["videoOwnerRenderer"]?
["navigationEndpoint"]?["browseEndpoint"]?["browseId"]?.ToString(),
SubscriberCount = "",
Avatars =
(playlist?["sidebar"]?["playlistSidebarRenderer"]?["items"]?[1]?
["playlistSidebarSecondaryInfoRenderer"]?["videoOwner"]?["videoOwnerRenderer"]?["thumbnail"]
?["thumbnails"] ?? new JArray()).Select(Utils.ParseThumbnails).ToArray()
},
Videos = renderers.Where(x => x is not ContinuationItem).ToArray(),
ContinuationKey = renderers.FirstOrDefault(x => x is ContinuationItem)?.Id
};
}
public async Task<YoutubeChannel> GetChannelAsync(string id, ChannelTabs tab = ChannelTabs.Home,
string continuation = null, string language = "en", string region = "US")
{
Dictionary<string, object> data = new();
if (string.IsNullOrWhiteSpace(continuation))
{
data.Add("browseId", id);
if (string.IsNullOrWhiteSpace(continuation))
data.Add("params", ChannelTabParams[tab]);
}
else
{
data.Add("continuation", continuation);
}
JObject channel = await MakeRequest("browse", data, language, region);
JArray mainArray =
(channel?["contents"]?["twoColumnBrowseResultsRenderer"]?["tabs"]?.ToObject<JArray>() ?? new JArray())
.FirstOrDefault(x => x?["tabRenderer"]?["selected"]?.ToObject<bool>() ?? false)?["tabRenderer"]?[
"content"]?
["sectionListRenderer"]?["contents"]?.ToObject<JArray>();
return new YoutubeChannel
{
Id = channel?["metadata"]?["channelMetadataRenderer"]?["externalId"]?.ToString(),
Name = channel?["metadata"]?["channelMetadataRenderer"]?["title"]?.ToString(),
Url = channel?["metadata"]?["channelMetadataRenderer"]?["externalId"]?.ToString(),
Avatars = (channel?["metadata"]?["channelMetadataRenderer"]?["avatar"]?["thumbnails"] ?? new JArray())
.Select(Utils.ParseThumbnails).ToArray(),
Banners = (channel?["header"]?["c4TabbedHeaderRenderer"]?["banner"]?["thumbnails"] ?? new JArray())
.Select(Utils.ParseThumbnails).ToArray(),
Description = channel?["metadata"]?["channelMetadataRenderer"]?["description"]?.ToString(),
Videos = ParseRenderers(mainArray ??
channel?["onResponseReceivedActions"]?[0]?["appendContinuationItemsAction"]?
["continuationItems"]?.ToObject<JArray>() ?? new JArray()),
Subscribers = channel?["header"]?["c4TabbedHeaderRenderer"]?["subscriberCountText"]?["simpleText"]
?.ToString()
};
}
public async Task<YoutubeTrends> GetExploreAsync(string browseId = null, string continuation = null, string language = "en", string region = "US")
{
Dictionary<string, object> data = new();
if (string.IsNullOrWhiteSpace(continuation))
{
data.Add("browseId", browseId ?? "FEexplore");
}
else
{
data.Add("continuation", continuation);
}
JObject explore = await MakeRequest("browse", data, language, region);
JToken[] token =
(explore?["contents"]?["twoColumnBrowseResultsRenderer"]?["tabs"]?[0]?["tabRenderer"]?["content"]?
["sectionListRenderer"]?["contents"]?.ToObject<JArray>() ?? new JArray()).Skip(1).ToArray();
JArray mainArray = new(token.Select(x => x is JObject obj ? obj : null).Where(x => x is not null));
return new YoutubeTrends
{
Categories = explore?["contents"]?["twoColumnBrowseResultsRenderer"]?["tabs"]?[0]?["tabRenderer"]?["content"]?["sectionListRenderer"]?["contents"]?[0]?["itemSectionRenderer"]?["contents"]?[0]?["destinationShelfRenderer"]?["destinationButtons"]?.Select(
x =>
{
JToken rendererObject = x?["destinationButtonRenderer"];
TrendCategory category = new()
{
Label = rendererObject?["label"]?["simpleText"]?.ToString(),
BackgroundImage = (rendererObject?["backgroundImage"]?["thumbnails"]?.ToObject<JArray>() ??
new JArray()).Select(Utils.ParseThumbnails).ToArray(),
Icon = (rendererObject?["iconImage"]?["thumbnails"]?.ToObject<JArray>() ??
new JArray()).Select(Utils.ParseThumbnails).ToArray(),
Id = $"{rendererObject?["onTap"]?["browseEndpoint"]?["browseId"]}"
};
return category;
}).ToArray(),
Videos = ParseRenderers(mainArray)
};
}
public async Task<YoutubeLocals> GetLocalsAsync(string language = "en", string region = "US")
{
JObject locals = await MakeRequest("account/account_menu", new Dictionary<string, object>(), language,
region);
return new YoutubeLocals
{
Languages =
locals["actions"]?[0]?["openPopupAction"]?["popup"]?["multiPageMenuRenderer"]?["sections"]?[0]?
["multiPageMenuSectionRenderer"]?["items"]?[1]?["compactLinkRenderer"]?["serviceEndpoint"]?
["signalServiceEndpoint"]?["actions"]?[0]?["getMultiPageMenuAction"]?["menu"]?
["multiPageMenuRenderer"]?["sections"]?[0]?["multiPageMenuSectionRenderer"]?["items"]?
.ToObject<JArray>()?.ToDictionary(
x => x?["compactLinkRenderer"]?["serviceEndpoint"]?["signalServiceEndpoint"]?
["actions"]?[0]?["selectLanguageCommand"]?["hl"]?.ToString(),
x => x?["compactLinkRenderer"]?["title"]?["simpleText"]?.ToString()),
Regions =
locals["actions"]?[0]?["openPopupAction"]?["popup"]?["multiPageMenuRenderer"]?["sections"]?[0]?
["multiPageMenuSectionRenderer"]?["items"]?[2]?["compactLinkRenderer"]?["serviceEndpoint"]?
["signalServiceEndpoint"]?["actions"]?[0]?["getMultiPageMenuAction"]?["menu"]?
["multiPageMenuRenderer"]?["sections"]?[0]?["multiPageMenuSectionRenderer"]?["items"]?
.ToObject<JArray>()?.ToDictionary(
x => x?["compactLinkRenderer"]?["serviceEndpoint"]?["signalServiceEndpoint"]?
["actions"]?[0]?["selectCountryCommand"]?["gl"]?.ToString(),
x => x?["compactLinkRenderer"]?["title"]?["simpleText"]?.ToString())
};
}
private DynamicItem[] ParseRenderers(JArray renderersArray)
{
List<DynamicItem> items = new();
foreach (JToken jToken in renderersArray)
{
JObject recommendationContainer = jToken as JObject;
string rendererName = recommendationContainer?.First?.Path.Split(".").Last() ?? "";
JObject rendererItem = recommendationContainer?[rendererName]?.ToObject<JObject>();
switch (rendererName)
{
case "videoRenderer":
items.Add(new VideoItem
{
Id = rendererItem?["videoId"]?.ToString(),
Title = Utils.ReadRuns(rendererItem?["title"]?["runs"]?.ToObject<JArray>() ??
new JArray()),
Thumbnails =
(rendererItem?["thumbnail"]?["thumbnails"]?.ToObject<JArray>() ??
new JArray()).Select(Utils.ParseThumbnails).ToArray(),
UploadedAt = rendererItem?["publishedTimeText"]?["simpleText"]?.ToString(),
Views = long.TryParse(
rendererItem?["viewCountText"]?["simpleText"]?.ToString().Split(" ")[0]
.Replace(",", "").Replace(".", "") ?? "0", out long vV) ? vV : 0,
Channel = new Channel
{
Name = rendererItem?["longBylineText"]?["runs"]?[0]?["text"]?.ToString(),
Id = rendererItem?["longBylineText"]?["runs"]?[0]?["navigationEndpoint"]?[
"browseEndpoint"]?["browseId"]?.ToString(),
SubscriberCount = null,
Avatars =
(rendererItem?["channelThumbnailSupportedRenderers"]?[
"channelThumbnailWithLinkRenderer"]?["thumbnail"]?["thumbnails"]
?.ToObject<JArray>() ?? new JArray()).Select(Utils.ParseThumbnails)
.ToArray()
},
Duration = rendererItem?["thumbnailOverlays"]?[0]?[
"thumbnailOverlayTimeStatusRenderer"]?["text"]?["simpleText"]?.ToString(),
Description = Utils.ReadRuns(rendererItem?["detailedMetadataSnippets"]?[0]?[
"snippetText"]?["runs"]?.ToObject<JArray>() ?? new JArray())
});
break;
case "gridVideoRenderer":
items.Add(new VideoItem
{
Id = rendererItem?["videoId"]?.ToString(),
Title = rendererItem?["title"]?["simpleText"]?.ToString() ?? Utils.ReadRuns(
rendererItem?["title"]?["runs"]?.ToObject<JArray>() ?? new JArray()),
Thumbnails =
(rendererItem?["thumbnail"]?["thumbnails"]?.ToObject<JArray>() ??
new JArray()).Select(Utils.ParseThumbnails).ToArray(),
UploadedAt = rendererItem?["publishedTimeText"]?["simpleText"]?.ToString(),
Views = long.TryParse(
rendererItem?["viewCountText"]?["simpleText"]?.ToString().Split(" ")[0]
.Replace(",", "").Replace(".", "") ?? "0", out long gVV) ? gVV : 0,
Channel = null,
Duration = rendererItem?["thumbnailOverlays"]?[0]?[
"thumbnailOverlayTimeStatusRenderer"]?["text"]?["simpleText"]?.ToString()
});
break;
case "playlistRenderer":
items.Add(new PlaylistItem
{
Id = rendererItem?["playlistId"]
?.ToString(),
Title = rendererItem?["title"]?["simpleText"]
?.ToString(),
Thumbnails =
(rendererItem?["thumbnails"]?[0]?["thumbnails"]?.ToObject<JArray>() ??
new JArray()).Select(Utils.ParseThumbnails).ToArray(),
VideoCount = int.TryParse(
rendererItem?["videoCountText"]?["runs"]?[0]?["text"]?.ToString().Replace(",", "")
.Replace(".", "") ?? "0", out int pVC) ? pVC : 0,
FirstVideoId = rendererItem?["navigationEndpoint"]?["watchEndpoint"]?["videoId"]
?.ToString(),
Channel = new Channel
{
Name = rendererItem?["longBylineText"]?["runs"]?[0]?["text"]
?.ToString(),
Id = rendererItem?["longBylineText"]?["runs"]?[0]?["navigationEndpoint"]?[
"browseEndpoint"]?["browseId"]
?.ToString(),
SubscriberCount = null,
Avatars = null
}
});
break;
case "channelRenderer":
items.Add(new ChannelItem
{
Id = rendererItem?["channelId"]?.ToString(),
Title = rendererItem?["title"]?["simpleText"]?.ToString(),
Thumbnails =
(rendererItem?["thumbnail"]?["thumbnails"]
?.ToObject<JArray>() ??
new JArray()).Select(Utils.ParseThumbnails)
.ToArray(), //
Url = rendererItem?["navigationEndpoint"]?["commandMetadata"]?["webCommandMetadata"]?["url"]
?.ToString(),
Description =
Utils.ReadRuns(rendererItem?["descriptionSnippet"]?["runs"]?.ToObject<JArray>() ??
new JArray()),
VideoCount = long.TryParse(
rendererItem?["videoCountText"]?["runs"]?[0]?["text"]
?.ToString()
.Replace(",",
"")
.Replace(".",
"") ??
"0", out long cVC) ? cVC : 0,
Subscribers = rendererItem?["subscriberCountText"]?["simpleText"]?.ToString()
});
break;
case "radioRenderer":
items.Add(new RadioItem
{
Id = rendererItem?["playlistId"]
?.ToString(),
Title = rendererItem?["title"]?["simpleText"]
?.ToString(),
Thumbnails =
(rendererItem?["thumbnail"]?["thumbnails"]?.ToObject<JArray>() ??
new JArray()).Select(Utils.ParseThumbnails).ToArray(),
FirstVideoId = rendererItem?["navigationEndpoint"]?["watchEndpoint"]?["videoId"]
?.ToString(),
Channel = new Channel
{
Name = rendererItem?["longBylineText"]?["simpleText"]?.ToString(),
Id = "",
SubscriberCount = null,
Avatars = null
}
});
break;
case "shelfRenderer":
items.Add(new ShelfItem
{
Title = rendererItem?["title"]?["simpleText"]
?.ToString() ??
rendererItem?["title"]?["runs"]?[0]?["text"]
?.ToString(),
Thumbnails = (rendererItem?["thumbnail"]?["thumbnails"]?.ToObject<JArray>() ??
new JArray()).Select(Utils.ParseThumbnails).ToArray(),
Items = ParseRenderers(
rendererItem?["content"]?["verticalListRenderer"]?["items"]
?.ToObject<JArray>() ??
rendererItem?["content"]?["horizontalListRenderer"]?["items"]
?.ToObject<JArray>() ??
rendererItem?["content"]?["expandedShelfContentsRenderer"]?["items"]
?.ToObject<JArray>() ??
new JArray()),
CollapsedItemCount =
rendererItem?["content"]?["verticalListRenderer"]?["collapsedItemCount"]
?.ToObject<int>() ?? 0,
Badges = ParseRenderers(rendererItem?["badges"]?.ToObject<JArray>() ?? new JArray())
.Where(x => x is BadgeItem).Cast<BadgeItem>().ToArray(),
});
break;
case "horizontalCardListRenderer":
items.Add(new HorizontalCardListItem
{
Title = rendererItem?["header"]?["richListHeaderRenderer"]?["title"]?["simpleText"]
?.ToString(),
Items = ParseRenderers(rendererItem?["cards"]?.ToObject<JArray>() ?? new JArray())
});
break;
case "searchRefinementCardRenderer":
items.Add(new CardItem
{
Title = Utils.ReadRuns(rendererItem?["query"]?["runs"]?.ToObject<JArray>() ??
new JArray()),
Thumbnails = (rendererItem?["thumbnail"]?["thumbnails"]?.ToObject<JArray>() ??
new JArray()).Select(Utils.ParseThumbnails).ToArray()
});
break;
case "compactVideoRenderer":
items.Add(new VideoItem
{
Id = rendererItem?["videoId"]?.ToString(),
Title = rendererItem?["title"]?["simpleText"]?.ToString(),
Thumbnails =
(rendererItem?["thumbnail"]?["thumbnails"]?.ToObject<JArray>() ??
new JArray()).Select(Utils.ParseThumbnails).ToArray(),
UploadedAt = rendererItem?["publishedTimeText"]?["simpleText"]?.ToString(),
Views = long.TryParse(
rendererItem?["viewCountText"]?["simpleText"]?.ToString().Split(" ")[0]
.Replace(",", "").Replace(".", "") ?? "0", out long cVV) ? cVV : 0,
Channel = new Channel
{
Name = rendererItem?["longBylineText"]?["runs"]?[0]?["text"]?.ToString(),
Id = rendererItem?["longBylineText"]?["runs"]?[0]?["navigationEndpoint"]?[
"browseEndpoint"]?["browseId"]?.ToString(),
SubscriberCount = null,
Avatars = null
},
Duration = rendererItem?["thumbnailOverlays"]?[0]?[
"thumbnailOverlayTimeStatusRenderer"]?["text"]?["simpleText"]?.ToString()
});
break;
case "compactPlaylistRenderer":
items.Add(new PlaylistItem
{
Id = rendererItem?["playlistId"]
?.ToString(),
Title = rendererItem?["title"]?["simpleText"]
?.ToString(),
Thumbnails =
(rendererItem?["thumbnail"]?["thumbnails"]
?.ToObject<JArray>() ?? new JArray()).Select(Utils.ParseThumbnails)
.ToArray(),
VideoCount = int.TryParse(
rendererItem?["videoCountText"]?["runs"]?[0]?["text"]?.ToString().Replace(",", "")
.Replace(".", "") ?? "0", out int cPVC) ? cPVC : 0,
FirstVideoId = rendererItem?["navigationEndpoint"]?["watchEndpoint"]?["videoId"]
?.ToString(),
Channel = new Channel
{
Name = rendererItem?["longBylineText"]?["runs"]?[0]?["text"]
?.ToString(),
Id = rendererItem?["longBylineText"]?["runs"]?[0]?["navigationEndpoint"]?[
"browseEndpoint"]?["browseId"]
?.ToString(),
SubscriberCount = null,
Avatars = null
}
});
break;
case "compactRadioRenderer":
items.Add(new RadioItem
{
Id = rendererItem?["playlistId"]
?.ToString(),
Title = rendererItem?["title"]?["simpleText"]
?.ToString(),
Thumbnails =
(rendererItem?["thumbnail"]?["thumbnails"]
?.ToObject<JArray>() ?? new JArray()).Select(Utils.ParseThumbnails)
.ToArray(),
FirstVideoId = rendererItem?["navigationEndpoint"]?["watchEndpoint"]?["videoId"]
?.ToString(),
Channel = new Channel
{
Name = rendererItem?["longBylineText"]?["simpleText"]?.ToString(),
Id = "",
SubscriberCount = null,
Avatars = null
}
});
break;
case "continuationItemRenderer":
items.Add(new ContinuationItem
{
Id = rendererItem?["continuationEndpoint"]?["continuationCommand"]?["token"]?.ToString()
});
break;
case "playlistVideoRenderer":
items.Add(new PlaylistVideoItem
{
Id = rendererItem?["videoId"]?.ToString(),
Index = rendererItem?["index"]?["simpleText"]?.ToObject<long>() ?? 0,
Title = Utils.ReadRuns(rendererItem?["title"]?["runs"]?.ToObject<JArray>() ??
new JArray()),
Thumbnails =
(rendererItem?["thumbnail"]?["thumbnails"]?.ToObject<JArray>() ??
new JArray()).Select(Utils.ParseThumbnails).ToArray(),
Channel = new Channel
{
Name = rendererItem?["shortBylineText"]?["runs"]?[0]?["text"]?.ToString(),
Id = rendererItem?["shortBylineText"]?["runs"]?[0]?["navigationEndpoint"]?[
"browseEndpoint"]?["browseId"]?.ToString(),
SubscriberCount = null,
Avatars = null
},
Duration = rendererItem?["lengthText"]?["simpleText"]?.ToString()
});
break;
case "itemSectionRenderer":
items.Add(new ItemSectionItem
{
Contents = ParseRenderers(rendererItem?["contents"]?.ToObject<JArray>() ?? new JArray())
});
break;
case "gridRenderer":
items.Add(new ItemSectionItem
{
Contents = ParseRenderers(rendererItem?["items"]?.ToObject<JArray>() ?? new JArray())
});
break;
case "messageRenderer":
items.Add(new MessageItem
{
Title = rendererItem?["text"]?["simpleText"]?.ToString()
});
break;
case "channelAboutFullMetadataRenderer":
items.Add(new ChannelAboutItem
{
Description = rendererItem?["description"]?["simpleText"]?.ToString(),
Country = rendererItem?["country"]?["simpleText"]?.ToString(),
Joined = Utils.ReadRuns(rendererItem?["joinedDateText"]?["runs"]?.ToObject<JArray>() ??
new JArray()),
ViewCount = rendererItem?["viewCountText"]?["simpleText"]?.ToString()
});
break;
case "compactStationRenderer":
items.Add(new StationItem
{
Id = rendererItem?["navigationEndpoint"]?["watchEndpoint"]?["playlistId"]?.ToString(),
Title = rendererItem?["title"]?["simpleText"]?.ToString(),
Thumbnails =
(rendererItem?["thumbnail"]?["thumbnails"]?.ToObject<JArray>() ??
new JArray()).Select(Utils.ParseThumbnails).ToArray(),
VideoCount = rendererItem?["videoCountText"]?["runs"]?[0]?["text"].ToObject<int>() ?? 0,
FirstVideoId = rendererItem?["navigationEndpoint"]?["watchEndpoint"]?["videoId"]?.ToString(),
Description = rendererItem?["description"]?["simpleText"]?.ToString()
});
break;
case "metadataBadgeRenderer":
items.Add(new BadgeItem
{
Title = rendererItem?["label"]?.ToString(),
Style = rendererItem?["style"]?.ToString()
});
break;
case "promotedSparklesWebRenderer":
// this is an ad
// no one likes ads
break;
default:
items.Add(new DynamicItem
{
Id = rendererName,
Title = rendererItem?.ToString()
});
break;
}
}
return items.ToArray();
}
}
}

View File

@ -1,7 +0,0 @@
namespace LightTube.Contexts
{
public class BaseContext
{
public bool MobileLayout;
}
}

View File

@ -1,7 +0,0 @@
namespace LightTube.Contexts
{
public class ErrorContext : BaseContext
{
public string Path;
}
}

View File

@ -1,11 +0,0 @@
using LightTube.Database;
namespace LightTube.Contexts
{
public class FeedContext : BaseContext
{
public LTChannel[] Channels;
public FeedVideo[] Videos;
public string RssToken;
}
}

View File

@ -1,13 +0,0 @@
using System.Collections.Generic;
using InnerTube.Models;
namespace LightTube.Contexts
{
public class LocalsContext : BaseContext
{
public Dictionary<string, string> Languages;
public Dictionary<string, string> Regions;
public string CurrentLanguage;
public string CurrentRegion;
}
}

View File

@ -1,11 +0,0 @@
using System.Collections.Generic;
using InnerTube.Models;
using LightTube.Database;
namespace LightTube.Contexts
{
public class PlaylistsContext : BaseContext
{
public IEnumerable<LTPlaylist> Playlists;
}
}

View File

@ -1,351 +0,0 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;
using InnerTube;
using InnerTube.Models;
using LightTube.Contexts;
using LightTube.Database;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace LightTube.Controllers
{
public class AccountController : Controller
{
private readonly Youtube _youtube;
public AccountController(Youtube youtube)
{
_youtube = youtube;
}
[Route("/Account")]
public IActionResult Account()
{
return View(new BaseContext
{
MobileLayout = Utils.IsClientMobile(Request)
});
}
[HttpGet]
public IActionResult Login(string err = null)
{
if (HttpContext.TryGetUser(out LTUser _, "web"))
return Redirect("/");
return View(new MessageContext
{
Message = err,
MobileLayout = Utils.IsClientMobile(Request)
});
}
[HttpPost]
public async Task<IActionResult> Login(string userid, string password)
{
if (HttpContext.TryGetUser(out LTUser _, "web"))
return Redirect("/");
try
{
LTLogin login = await DatabaseManager.Logins.CreateToken(userid, password, Request.Headers["user-agent"], new []{"web"});
Response.Cookies.Append("token", login.Token, new CookieOptions
{
Expires = DateTimeOffset.MaxValue
});
return Redirect("/");
}
catch (KeyNotFoundException e)
{
return Redirect("/Account/Login?err=" + HttpUtility.UrlEncode(e.Message));
}
catch (UnauthorizedAccessException e)
{
return Redirect("/Account/Login?err=" + HttpUtility.UrlEncode(e.Message));
}
}
public async Task<IActionResult> Logout()
{
if (HttpContext.Request.Cookies.TryGetValue("token", out string token))
{
await DatabaseManager.Logins.RemoveToken(token);
}
HttpContext.Response.Cookies.Delete("token");
HttpContext.Response.Cookies.Delete("account_data");
return Redirect("/");
}
[HttpGet]
public IActionResult Register(string err = null)
{
if (HttpContext.TryGetUser(out LTUser _, "web"))
return Redirect("/");
return View(new MessageContext
{
Message = err,
MobileLayout = Utils.IsClientMobile(Request)
});
}
[HttpPost]
public async Task<IActionResult> Register(string userid, string password)
{
if (HttpContext.TryGetUser(out LTUser _, "web"))
return Redirect("/");
try
{
await DatabaseManager.Logins.CreateUser(userid, password);
LTLogin login = await DatabaseManager.Logins.CreateToken(userid, password, Request.Headers["user-agent"], new []{"web"});
Response.Cookies.Append("token", login.Token, new CookieOptions
{
Expires = DateTimeOffset.MaxValue
});
return Redirect("/");
}
catch (DuplicateNameException e)
{
return Redirect("/Account/Register?err=" + HttpUtility.UrlEncode(e.Message));
}
}
public IActionResult RegisterLocal()
{
if (!HttpContext.TryGetUser(out LTUser _, "web"))
HttpContext.CreateLocalAccount();
return Redirect("/");
}
[HttpGet]
public IActionResult Delete(string err = null)
{
if (!HttpContext.TryGetUser(out LTUser _, "web"))
return Redirect("/");
return View(new MessageContext
{
Message = err,
MobileLayout = Utils.IsClientMobile(Request)
});
}
[HttpPost]
public async Task<IActionResult> Delete(string userid, string password)
{
try
{
if (userid == "Local Account" && password == "local_account")
Response.Cookies.Delete("account_data");
else
await DatabaseManager.Logins.DeleteUser(userid, password);
return Redirect("/Account/Register?err=Account+deleted");
}
catch (KeyNotFoundException e)
{
return Redirect("/Account/Delete?err=" + HttpUtility.UrlEncode(e.Message));
}
catch (UnauthorizedAccessException e)
{
return Redirect("/Account/Delete?err=" + HttpUtility.UrlEncode(e.Message));
}
}
public async Task<IActionResult> Logins()
{
if (!HttpContext.TryGetUser(out LTUser _, "web") || !HttpContext.Request.Cookies.TryGetValue("token", out string token))
return Redirect("/Account/Login");
return View(new LoginsContext
{
CurrentLogin = await DatabaseManager.Logins.GetCurrentLoginId(token),
Logins = await DatabaseManager.Logins.GetAllUserTokens(token),
MobileLayout = Utils.IsClientMobile(Request)
});
}
public async Task<IActionResult> DisableLogin(string id)
{
if (!HttpContext.Request.Cookies.TryGetValue("token", out string token))
return Redirect("/Account/Login");
try
{
await DatabaseManager.Logins.RemoveTokenFromId(token, id);
} catch { }
return Redirect("/Account/Logins");
}
public async Task<IActionResult> Subscribe(string channel)
{
if (!HttpContext.TryGetUser(out LTUser user, "web"))
return Unauthorized();
try
{
YoutubeChannel youtubeChannel = await _youtube.GetChannelAsync(channel, ChannelTabs.About);
(LTChannel channel, bool subscribed) result;
result.channel = await DatabaseManager.Channels.UpdateChannel(youtubeChannel.Id, youtubeChannel.Name, youtubeChannel.Subscribers,
youtubeChannel.Avatars.First().Url);
if (user.PasswordHash == "local_account")
{
LTChannel ltChannel = await DatabaseManager.Channels.UpdateChannel(youtubeChannel.Id, youtubeChannel.Name, youtubeChannel.Subscribers,
youtubeChannel.Avatars.First().Url);
if (user.SubscribedChannels.Contains(ltChannel.ChannelId))
user.SubscribedChannels.Remove(ltChannel.ChannelId);
else
user.SubscribedChannels.Add(ltChannel.ChannelId);
HttpContext.Response.Cookies.Append("account_data", JsonConvert.SerializeObject(user),
new CookieOptions
{
Expires = DateTimeOffset.MaxValue
});
result.subscribed = user.SubscribedChannels.Contains(ltChannel.ChannelId);
}
else
{
result =
await DatabaseManager.Logins.SubscribeToChannel(user, youtubeChannel);
}
return Ok(result.subscribed ? "true" : "false");
}
catch
{
return Unauthorized();
}
}
public IActionResult SubscriptionsJson()
{
if (!HttpContext.TryGetUser(out LTUser user, "web"))
return Json(Array.Empty<string>());
try
{
return Json(user.SubscribedChannels);
}
catch
{
return Json(Array.Empty<string>());
}
}
public async Task<IActionResult> Settings()
{
if (!HttpContext.TryGetUser(out LTUser user, "web"))
Redirect("/Account/Login");
if (Request.Method == "POST")
{
CookieOptions opts = new()
{
Expires = DateTimeOffset.MaxValue
};
foreach ((string key, StringValues value) in Request.Form)
{
switch (key)
{
case "theme":
Response.Cookies.Append("theme", value, opts);
break;
case "hl":
Response.Cookies.Append("hl", value, opts);
break;
case "gl":
Response.Cookies.Append("gl", value, opts);
break;
case "compatibility":
Response.Cookies.Append("compatibility", value, opts);
break;
case "api-access":
await DatabaseManager.Logins.SetApiAccess(user, bool.Parse(value));
break;
}
}
return Redirect("/Account");
}
YoutubeLocals locals = await _youtube.GetLocalsAsync();
Request.Cookies.TryGetValue("theme", out string theme);
bool compatibility = false;
if (Request.Cookies.TryGetValue("compatibility", out string compatibilityString))
bool.TryParse(compatibilityString, out compatibility);
return View(new SettingsContext
{
Languages = locals.Languages,
Regions = locals.Regions,
CurrentLanguage = HttpContext.GetLanguage(),
CurrentRegion = HttpContext.GetRegion(),
MobileLayout = Utils.IsClientMobile(Request),
Theme = theme ?? "light",
CompatibilityMode = compatibility,
ApiAccess = user.ApiAccess
});
}
public async Task<IActionResult> AddVideoToPlaylist(string v)
{
if (!HttpContext.TryGetUser(out LTUser user, "web"))
Redirect("/Account/Login");
JObject ytPlayer = await InnerTube.Utils.GetAuthorizedPlayer(v, new HttpClient());
return View(new AddToPlaylistContext
{
Id = v,
Video = await _youtube.GetVideoAsync(v, HttpContext.GetLanguage(), HttpContext.GetRegion()),
Playlists = await DatabaseManager.Playlists.GetUserPlaylists(user.UserID),
Thumbnail = ytPlayer?["videoDetails"]?["thumbnail"]?["thumbnails"]?[0]?["url"]?.ToString() ?? $"https://i.ytimg.com/vi_webp/{v}/maxresdefault.webp",
MobileLayout = Utils.IsClientMobile(Request),
});
}
[HttpGet]
public IActionResult CreatePlaylist(string returnUrl = null)
{
if (!HttpContext.TryGetUser(out LTUser user, "web"))
Redirect("/Account/Login");
return View(new BaseContext
{
MobileLayout = Utils.IsClientMobile(Request),
});
}
[HttpPost]
public async Task<IActionResult> CreatePlaylist()
{
if (!HttpContext.TryGetUser(out LTUser user, "web"))
Redirect("/Account/Login");
if (!Request.Form.ContainsKey("name") || string.IsNullOrWhiteSpace(Request.Form["name"])) return BadRequest();
LTPlaylist pl = await DatabaseManager.Playlists.CreatePlaylist(
user,
Request.Form["name"],
string.IsNullOrWhiteSpace(Request.Form["description"]) ? "" : Request.Form["description"],
Enum.Parse<PlaylistVisibility>(string.IsNullOrWhiteSpace(Request.Form["visibility"]) ? "UNLISTED" : Request.Form["visibility"]));
return Redirect($"/playlist?list={pl.Id}");
}
}
}

View File

@ -1,187 +0,0 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
using InnerTube;
using InnerTube.Models;
using Microsoft.AspNetCore.Mvc;
namespace LightTube.Controllers
{
[Route("/api")]
public class ApiController : Controller
{
private const string VideoIdRegex = @"[a-zA-Z0-9_-]{11}";
private const string ChannelIdRegex = @"[a-zA-Z0-9_-]{24}";
private const string PlaylistIdRegex = @"[a-zA-Z0-9_-]{34}";
private readonly Youtube _youtube;
public ApiController(Youtube youtube)
{
_youtube = youtube;
}
private IActionResult Xml(XmlNode xmlDocument)
{
MemoryStream ms = new();
ms.Write(Encoding.UTF8.GetBytes(xmlDocument.OuterXml));
ms.Position = 0;
HttpContext.Response.Headers.Add("Access-Control-Allow-Origin", "*");
return File(ms, "application/xml");
}
[Route("player")]
public async Task<IActionResult> GetPlayerInfo(string v)
{
if (v is null)
return GetErrorVideoPlayer("", "Missing YouTube ID (query parameter `v`)");
Regex regex = new(VideoIdRegex);
if (!regex.IsMatch(v) || v.Length != 11)
return GetErrorVideoPlayer(v, "Invalid YouTube ID " + v);
try
{
YoutubePlayer player =
await _youtube.GetPlayerAsync(v, HttpContext.GetLanguage(), HttpContext.GetRegion());
XmlDocument xml = player.GetXmlDocument();
return Xml(xml);
}
catch (Exception e)
{
return GetErrorVideoPlayer(v, e.Message);
}
}
private IActionResult GetErrorVideoPlayer(string videoId, string message)
{
YoutubePlayer player = new()
{
Id = videoId,
Title = "",
Description = "",
Tags = Array.Empty<string>(),
Channel = new Channel
{
Name = "",
Id = "",
Avatars = Array.Empty<Thumbnail>()
},
Duration = 0,
Chapters = Array.Empty<Chapter>(),
Thumbnails = Array.Empty<Thumbnail>(),
Formats = Array.Empty<Format>(),
AdaptiveFormats = Array.Empty<Format>(),
Subtitles = Array.Empty<Subtitle>(),
Storyboards = Array.Empty<string>(),
ExpiresInSeconds = "0",
ErrorMessage = message
};
return Xml(player.GetXmlDocument());
}
[Route("video")]
public async Task<IActionResult> GetVideoInfo(string v)
{
if (v is null)
return GetErrorVideoPlayer("", "Missing YouTube ID (query parameter `v`)");
Regex regex = new(VideoIdRegex);
if (!regex.IsMatch(v) || v.Length != 11)
{
XmlDocument doc = new();
XmlElement item = doc.CreateElement("Error");
item.InnerText = "Invalid YouTube ID " + v;
doc.AppendChild(item);
return Xml(doc);
}
YoutubeVideo player = await _youtube.GetVideoAsync(v, HttpContext.GetLanguage(), HttpContext.GetRegion());
XmlDocument xml = player.GetXmlDocument();
return Xml(xml);
}
[Route("search")]
public async Task<IActionResult> Search(string query, string continuation = null)
{
if (string.IsNullOrWhiteSpace(query) && string.IsNullOrWhiteSpace(continuation))
{
XmlDocument doc = new();
XmlElement item = doc.CreateElement("Error");
item.InnerText = "Invalid query " + query;
doc.AppendChild(item);
return Xml(doc);
}
YoutubeSearchResults player = await _youtube.SearchAsync(query, continuation, HttpContext.GetLanguage(),
HttpContext.GetRegion());
XmlDocument xml = player.GetXmlDocument();
return Xml(xml);
}
[Route("playlist")]
public async Task<IActionResult> Playlist(string id, string continuation = null)
{
Regex regex = new(PlaylistIdRegex);
if (!regex.IsMatch(id) || id.Length != 34) return GetErrorVideoPlayer(id, "Invalid playlist ID " + id);
if (string.IsNullOrWhiteSpace(id) && string.IsNullOrWhiteSpace(continuation))
{
XmlDocument doc = new();
XmlElement item = doc.CreateElement("Error");
item.InnerText = "Invalid ID " + id;
doc.AppendChild(item);
return Xml(doc);
}
YoutubePlaylist player = await _youtube.GetPlaylistAsync(id, continuation, HttpContext.GetLanguage(),
HttpContext.GetRegion());
XmlDocument xml = player.GetXmlDocument();
return Xml(xml);
}
[Route("channel")]
public async Task<IActionResult> Channel(string id, ChannelTabs tab = ChannelTabs.Home,
string continuation = null)
{
Regex regex = new(ChannelIdRegex);
if (!regex.IsMatch(id) || id.Length != 24) return GetErrorVideoPlayer(id, "Invalid channel ID " + id);
if (string.IsNullOrWhiteSpace(id) && string.IsNullOrWhiteSpace(continuation))
{
XmlDocument doc = new();
XmlElement item = doc.CreateElement("Error");
item.InnerText = "Invalid ID " + id;
doc.AppendChild(item);
return Xml(doc);
}
YoutubeChannel player = await _youtube.GetChannelAsync(id, tab, continuation, HttpContext.GetLanguage(),
HttpContext.GetRegion());
XmlDocument xml = player.GetXmlDocument();
return Xml(xml);
}
[Route("trending")]
public async Task<IActionResult> Trending(string id, string continuation = null)
{
YoutubeTrends player = await _youtube.GetExploreAsync(id, continuation,
HttpContext.GetLanguage(),
HttpContext.GetRegion());
XmlDocument xml = player.GetXmlDocument();
return Xml(xml);
}
}
}

View File

@ -1,189 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
using InnerTube;
using InnerTube.Models;
using LightTube.Database;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
namespace LightTube.Controllers
{
[Route("/api/auth")]
public class AuthorizedApiController : Controller
{
private readonly Youtube _youtube;
private IReadOnlyList<string> _scopes = new[]
{
"api.subscriptions.read",
"api.subscriptions.write"
};
public AuthorizedApiController(Youtube youtube)
{
_youtube = youtube;
}
private IActionResult Xml(XmlNode xmlDocument, HttpStatusCode statusCode)
{
MemoryStream ms = new();
ms.Write(Encoding.UTF8.GetBytes(xmlDocument.OuterXml));
ms.Position = 0;
HttpContext.Response.Headers.Add("Access-Control-Allow-Origin", "*");
Response.StatusCode = (int)statusCode;
return File(ms, "application/xml");
}
private XmlNode BuildErrorXml(string message)
{
XmlDocument doc = new();
XmlElement error = doc.CreateElement("Error");
error.InnerText = message;
doc.AppendChild(error);
return doc;
}
[HttpPost]
[Route("getToken")]
public async Task<IActionResult> GetToken()
{
if (!Request.Headers.TryGetValue("User-Agent", out StringValues userAgent))
return Xml(BuildErrorXml("Missing User-Agent header"), HttpStatusCode.BadRequest);
Match match = Regex.Match(userAgent.ToString(), DatabaseManager.ApiUaRegex);
if (!match.Success)
return Xml(BuildErrorXml("Bad User-Agent header. Please see 'Documentation/API requests'"), HttpStatusCode.BadRequest);
if (match.Groups[1].ToString() != "1.0")
return Xml(BuildErrorXml($"Unknown API version {match.Groups[1]}"), HttpStatusCode.BadRequest);
if (!Request.Form.TryGetValue("user", out StringValues user))
return Xml(BuildErrorXml("Missing request value: 'user'"), HttpStatusCode.BadRequest);
if (!Request.Form.TryGetValue("password", out StringValues password))
return Xml(BuildErrorXml("Missing request value: 'password'"), HttpStatusCode.BadRequest);
if (!Request.Form.TryGetValue("scopes", out StringValues scopes))
return Xml(BuildErrorXml("Missing request value: 'scopes'"), HttpStatusCode.BadRequest);
string[] newScopes = scopes.First().Split(",");
foreach (string s in newScopes)
if (!_scopes.Contains(s))
return Xml(BuildErrorXml($"Unknown scope '{s}'"), HttpStatusCode.BadRequest);
try
{
LTLogin ltLogin =
await DatabaseManager.Logins.CreateToken(user, password, userAgent.ToString(),
scopes.First().Split(","));
return Xml(ltLogin.GetXmlElement(), HttpStatusCode.Created);
}
catch (UnauthorizedAccessException)
{
return Xml(BuildErrorXml("Invalid credentials"), HttpStatusCode.Unauthorized);
}
catch (InvalidOperationException)
{
return Xml(BuildErrorXml("User has API access disabled"), HttpStatusCode.Forbidden);
}
}
[Route("subscriptions/feed")]
public async Task<IActionResult> SubscriptionsFeed()
{
if (!HttpContext.TryGetUser(out LTUser user, "api.subscriptions.read"))
return Xml(BuildErrorXml("Unauthorized"), HttpStatusCode.Unauthorized);
SubscriptionFeed feed = new()
{
videos = await YoutubeRSS.GetMultipleFeeds(user.SubscribedChannels)
};
return Xml(feed.GetXmlDocument(), HttpStatusCode.OK);
}
[HttpGet]
[Route("subscriptions/channels")]
public IActionResult SubscriptionsChannels()
{
if (!HttpContext.TryGetUser(out LTUser user, "api.subscriptions.read"))
return Xml(BuildErrorXml("Unauthorized"), HttpStatusCode.Unauthorized);
SubscriptionChannels feed = new()
{
Channels = user.SubscribedChannels.Select(DatabaseManager.Channels.GetChannel).ToArray()
};
Array.Sort(feed.Channels, (p, q) => string.Compare(p.Name, q.Name, StringComparison.OrdinalIgnoreCase));
return Xml(feed.GetXmlDocument(), HttpStatusCode.OK);
}
[HttpPut]
[Route("subscriptions/channels")]
public async Task<IActionResult> Subscribe()
{
if (!HttpContext.TryGetUser(out LTUser user, "api.subscriptions.write"))
return Xml(BuildErrorXml("Unauthorized"), HttpStatusCode.Unauthorized);
Request.Form.TryGetValue("id", out StringValues ids);
string id = ids.ToString();
if (user.SubscribedChannels.Contains(id))
return StatusCode((int)HttpStatusCode.NotModified);
try
{
YoutubeChannel channel = await _youtube.GetChannelAsync(id);
if (channel.Id is null)
return StatusCode((int)HttpStatusCode.NotFound);
(LTChannel ltChannel, bool _) = await DatabaseManager.Logins.SubscribeToChannel(user, channel);
XmlDocument doc = new();
doc.AppendChild(ltChannel.GetXmlElement(doc));
return Xml(doc, HttpStatusCode.OK);
}
catch (Exception e)
{
return Xml(BuildErrorXml(e.Message), HttpStatusCode.InternalServerError);
}
}
[HttpDelete]
[Route("subscriptions/channels")]
public async Task<IActionResult> Unsubscribe()
{
if (!HttpContext.TryGetUser(out LTUser user, "api.subscriptions.write"))
return Xml(BuildErrorXml("Unauthorized"), HttpStatusCode.Unauthorized);
Request.Form.TryGetValue("id", out StringValues ids);
string id = ids.ToString();
if (!user.SubscribedChannels.Contains(id))
return StatusCode((int)HttpStatusCode.NotModified);
try
{
YoutubeChannel channel = await _youtube.GetChannelAsync(id);
if (channel.Id is null)
return StatusCode((int)HttpStatusCode.NotFound);
(LTChannel ltChannel, bool _) = await DatabaseManager.Logins.SubscribeToChannel(user, channel);
XmlDocument doc = new();
doc.AppendChild(ltChannel.GetXmlElement(doc));
return Xml(doc, HttpStatusCode.OK);
}
catch (Exception e)
{
return Xml(BuildErrorXml(e.Message), HttpStatusCode.InternalServerError);
}
}
}
}

View File

@ -1,104 +0,0 @@
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using LightTube.Contexts;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using InnerTube;
using LightTube.Database;
namespace LightTube.Controllers
{
[Route("/feed")]
public class FeedController : Controller
{
private readonly ILogger<FeedController> _logger;
private readonly Youtube _youtube;
public FeedController(ILogger<FeedController> logger, Youtube youtube)
{
_logger = logger;
_youtube = youtube;
}
[Route("subscriptions")]
public async Task<IActionResult> Subscriptions()
{
if (!HttpContext.TryGetUser(out LTUser user, "web"))
return Redirect("/Account/Login");
try
{
FeedContext context = new()
{
Channels = user.SubscribedChannels.Select(DatabaseManager.Channels.GetChannel).ToArray(),
Videos = await YoutubeRSS.GetMultipleFeeds(user.SubscribedChannels),
RssToken = user.RssToken,
MobileLayout = Utils.IsClientMobile(Request)
};
Array.Sort(context.Channels, (p, q) => string.Compare(p.Name, q.Name, StringComparison.OrdinalIgnoreCase));
return View(context);
}
catch
{
HttpContext.Response.Cookies.Delete("token");
return Redirect("/Account/Login");
}
}
[Route("channels")]
public IActionResult Channels()
{
if (!HttpContext.TryGetUser(out LTUser user, "web"))
return Redirect("/Account/Login");
try
{
FeedContext context = new()
{
Channels = user.SubscribedChannels.Select(DatabaseManager.Channels.GetChannel).ToArray(),
Videos = null,
MobileLayout = Utils.IsClientMobile(Request)
};
Array.Sort(context.Channels, (p, q) => string.Compare(p.Name, q.Name, StringComparison.OrdinalIgnoreCase));
return View(context);
}
catch
{
HttpContext.Response.Cookies.Delete("token");
return Redirect("/Account/Login");
}
}
[Route("explore")]
public IActionResult Explore()
{
return View(new BaseContext
{
MobileLayout = Utils.IsClientMobile(Request)
});
}
[Route("/feed/library")]
public async Task<IActionResult> Playlists()
{
if (!HttpContext.TryGetUser(out LTUser user, "web"))
Redirect("/Account/Login");
return View(new PlaylistsContext
{
MobileLayout = Utils.IsClientMobile(Request),
Playlists = await DatabaseManager.Playlists.GetUserPlaylists(user.UserID)
});
}
[Route("/rss")]
public async Task<IActionResult> Playlists(string token, int limit = 15)
{
if (!DatabaseManager.TryGetRssUser(token, out LTUser user))
return Unauthorized();
return File(Encoding.UTF8.GetBytes(await user.GenerateRssFeed(Request.Host.ToString(), Math.Clamp(limit, 0, 50))), "application/xml");
}
}
}

View File

@ -1,50 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using LightTube.Contexts;
using LightTube.Models;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using InnerTube;
using InnerTube.Models;
using ErrorContext = LightTube.Contexts.ErrorContext;
namespace LightTube.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly Youtube _youtube;
public HomeController(ILogger<HomeController> logger, Youtube youtube)
{
_logger = logger;
_youtube = youtube;
}
public IActionResult Index()
{
return View(new BaseContext
{
MobileLayout = Utils.IsClientMobile(Request)
});
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorContext
{
Path = HttpContext.Features.Get<IExceptionHandlerPathFeature>().Path,
MobileLayout = Utils.IsClientMobile(Request)
});
}
}
}

View File

@ -1,62 +0,0 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using InnerTube;
using InnerTube.Models;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
namespace LightTube.Controllers
{
[Route("/manifest")]
public class ManifestController : Controller
{
private readonly Youtube _youtube;
private readonly HttpClient _client = new();
public ManifestController(Youtube youtube)
{
_youtube = youtube;
}
[Route("{v}")]
public async Task<IActionResult> DefaultManifest(string v)
{
YoutubePlayer player = await _youtube.GetPlayerAsync(v, HttpContext.GetLanguage(), HttpContext.GetRegion());
if (!string.IsNullOrWhiteSpace(player.ErrorMessage))
return StatusCode(500, player.ErrorMessage);
return Redirect(player.IsLive ? $"/manifest/{v}.m3u8" : $"/manifest/{v}.mpd" + Request.QueryString);
}
[Route("{v}.mpd")]
public async Task<IActionResult> DashManifest(string v, string videoCodec = null, string audioCodec = null, bool useProxy = true)
{
YoutubePlayer player = await _youtube.GetPlayerAsync(v, HttpContext.GetLanguage(), HttpContext.GetRegion());
string manifest = player.GetMpdManifest(useProxy ? $"https://{Request.Host}/proxy/" : null, videoCodec, audioCodec);
return File(Encoding.UTF8.GetBytes(manifest), "application/dash+xml");
}
[Route("{v}.m3u8")]
public async Task<IActionResult> HlsManifest(string v, bool useProxy = true)
{
YoutubePlayer player = await _youtube.GetPlayerAsync(v, HttpContext.GetLanguage(), HttpContext.GetRegion(), true);
if (!string.IsNullOrWhiteSpace(player.ErrorMessage))
return StatusCode(403, player.ErrorMessage);
if (player.IsLive)
{
string manifest = await player.GetHlsManifest(useProxy ? $"https://{Request.Host}/proxy" : null);
return File(Encoding.UTF8.GetBytes(manifest), "application/vnd.apple.mpegurl");
}
if (useProxy)
return StatusCode(400, "HLS proxy for non-live videos are not supported at the moment.");
return Redirect(player.HlsManifestUrl);
}
}
}

View File

@ -1,517 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using InnerTube;
using InnerTube.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
namespace LightTube.Controllers
{
[Route("/proxy")]
public class ProxyController : Controller
{
private readonly ILogger<YoutubeController> _logger;
private readonly Youtube _youtube;
private string[] BlockedHeaders =
{
"host",
"cookies"
};
public ProxyController(ILogger<YoutubeController> logger, Youtube youtube)
{
_logger = logger;
_youtube = youtube;
}
[Route("media/{videoId}/{formatId}")]
public async Task Media(string videoId, string formatId)
{
try
{
YoutubePlayer player = await _youtube.GetPlayerAsync(videoId);
if (!string.IsNullOrWhiteSpace(player.ErrorMessage))
{
Response.StatusCode = (int) HttpStatusCode.InternalServerError;
await Response.Body.WriteAsync(Encoding.UTF8.GetBytes(player.ErrorMessage));
await Response.StartAsync();
return;
}
List<Format> formats = new();
formats.AddRange(player.Formats);
formats.AddRange(player.AdaptiveFormats);
if (!formats.Any(x => x.FormatId == formatId))
{
Response.StatusCode = (int) HttpStatusCode.NotFound;
await Response.Body.WriteAsync(Encoding.UTF8.GetBytes(
$"Format with ID {formatId} not found.\nAvailable IDs are: {string.Join(", ", formats.Select(x => x.FormatId.ToString()))}"));
await Response.StartAsync();
return;
}
string url = formats.First(x => x.FormatId == formatId).Url;
if (!url.StartsWith("http://") && !url.StartsWith("https://"))
url = "https://" + url;
HttpWebRequest request = (HttpWebRequest) WebRequest.Create(url);
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
request.Method = Request.Method;
foreach ((string header, StringValues values) in HttpContext.Request.Headers.Where(header =>
!header.Key.StartsWith(":") && !BlockedHeaders.Contains(header.Key.ToLower())))
foreach (string value in values)
request.Headers.Add(header, value);
HttpWebResponse response;
try
{
response = (HttpWebResponse) request.GetResponse();
}
catch (WebException e)
{
response = e.Response as HttpWebResponse;
}
if (response == null)
await Response.StartAsync();
foreach (string header in response.Headers.AllKeys)
if (Response.Headers.ContainsKey(header))
Response.Headers[header] = response.Headers.Get(header);
else
Response.Headers.Add(header, response.Headers.Get(header));
Response.StatusCode = (int) response.StatusCode;
await using Stream stream = response.GetResponseStream();
try
{
await stream.CopyToAsync(Response.Body, HttpContext.RequestAborted);
}
catch (Exception)
{
// an exception is thrown if the client suddenly stops streaming
}
await Response.StartAsync();
}
catch (Exception e)
{
Response.StatusCode = (int) HttpStatusCode.InternalServerError;
await Response.Body.WriteAsync(Encoding.UTF8.GetBytes(e.ToString()));
await Response.StartAsync();
}
}
[Route("download/{videoId}/{formatId}/{filename}")]
public async Task Download(string videoId, string formatId, string filename)
{
try
{
YoutubePlayer player = await _youtube.GetPlayerAsync(videoId);
if (!string.IsNullOrWhiteSpace(player.ErrorMessage))
{
Response.StatusCode = (int) HttpStatusCode.InternalServerError;
await Response.Body.WriteAsync(Encoding.UTF8.GetBytes(player.ErrorMessage));
await Response.StartAsync();
return;
}
List<Format> formats = new();
formats.AddRange(player.Formats);
formats.AddRange(player.AdaptiveFormats);
if (!formats.Any(x => x.FormatId == formatId))
{
Response.StatusCode = (int) HttpStatusCode.NotFound;
await Response.Body.WriteAsync(Encoding.UTF8.GetBytes(
$"Format with ID {formatId} not found.\nAvailable IDs are: {string.Join(", ", formats.Select(x => x.FormatId.ToString()))}"));
await Response.StartAsync();
return;
}
string url = formats.First(x => x.FormatId == formatId).Url;
if (!url.StartsWith("http://") && !url.StartsWith("https://"))
url = "https://" + url;
HttpWebRequest request = (HttpWebRequest) WebRequest.Create(url);
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
request.Method = Request.Method;
foreach ((string header, StringValues values) in HttpContext.Request.Headers.Where(header =>
!header.Key.StartsWith(":") && !BlockedHeaders.Contains(header.Key.ToLower())))
foreach (string value in values)
request.Headers.Add(header, value);
HttpWebResponse response;
try
{
response = (HttpWebResponse) request.GetResponse();
}
catch (WebException e)
{
response = e.Response as HttpWebResponse;
}
if (response == null)
await Response.StartAsync();
foreach (string header in response.Headers.AllKeys)
if (Response.Headers.ContainsKey(header))
Response.Headers[header] = response.Headers.Get(header);
else
Response.Headers.Add(header, response.Headers.Get(header));
Response.Headers.Add("Content-Disposition", $"attachment; filename=\"{Regex.Replace(filename, @"[^\u0000-\u007F]+", string.Empty)}\"");
Response.StatusCode = (int) response.StatusCode;
await using Stream stream = response.GetResponseStream();
try
{
await stream.CopyToAsync(Response.Body, HttpContext.RequestAborted);
}
catch (Exception)
{
// an exception is thrown if the client suddenly stops streaming
}
await Response.StartAsync();
}
catch (Exception e)
{
Response.StatusCode = (int) HttpStatusCode.InternalServerError;
await Response.Body.WriteAsync(Encoding.UTF8.GetBytes(e.ToString()));
await Response.StartAsync();
}
}
[Route("caption/{videoId}/{language}")]
public async Task<FileStreamResult> SubtitleProxy(string videoId, string language)
{
YoutubePlayer player = await _youtube.GetPlayerAsync(videoId);
if (!string.IsNullOrWhiteSpace(player.ErrorMessage))
{
Response.StatusCode = (int) HttpStatusCode.InternalServerError;
return File(new MemoryStream(Encoding.UTF8.GetBytes(player.ErrorMessage)),
"text/plain");
}
string url = null;
Subtitle? subtitle = player.Subtitles.FirstOrDefault(x => string.Equals(x.Language, language, StringComparison.InvariantCultureIgnoreCase));
if (subtitle is null)
{
Response.StatusCode = (int) HttpStatusCode.NotFound;
return File(
new MemoryStream(Encoding.UTF8.GetBytes(
$"There are no available subtitles for {language}. Available language codes are: {string.Join(", ", player.Subtitles.Select(x => $"\"{x.Language}\""))}")),
"text/plain");
}
url = subtitle.Url.Replace("fmt=srv3", "fmt=vtt");
if (!url.StartsWith("http://") && !url.StartsWith("https://"))
url = "https://" + url;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
foreach ((string header, StringValues values) in HttpContext.Request.Headers.Where(header =>
!header.Key.StartsWith(":") && !BlockedHeaders.Contains(header.Key.ToLower())))
foreach (string value in values)
request.Headers.Add(header, value);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();
await using Stream stream = response.GetResponseStream();
using StreamReader reader = new(stream);
return File(new MemoryStream(Encoding.UTF8.GetBytes(await reader.ReadToEndAsync())),
"text/vtt");
}
[Route("image")]
[Obsolete("Use /proxy/thumbnail instead")]
public async Task ImageProxy(string url)
{
if (!url.StartsWith("http://") && !url.StartsWith("https://"))
url = "https://" + url;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
foreach ((string header, StringValues values) in HttpContext.Request.Headers.Where(header =>
!header.Key.StartsWith(":") && !BlockedHeaders.Contains(header.Key.ToLower())))
foreach (string value in values)
request.Headers.Add(header, value);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();
foreach (string header in response.Headers.AllKeys)
if (Response.Headers.ContainsKey(header))
Response.Headers[header] = response.Headers.Get(header);
else
Response.Headers.Add(header, response.Headers.Get(header));
Response.StatusCode = (int)response.StatusCode;
await using Stream stream = response.GetResponseStream();
await stream.CopyToAsync(Response.Body);
await Response.StartAsync();
}
[Route("thumbnail/{videoId}/{index:int}")]
public async Task ThumbnailProxy(string videoId, int index = 0)
{
YoutubePlayer player = await _youtube.GetPlayerAsync(videoId);
if (index == -1) index = player.Thumbnails.Length - 1;
if (index >= player.Thumbnails.Length)
{
Response.StatusCode = 404;
await Response.Body.WriteAsync(Encoding.UTF8.GetBytes(
$"Cannot find thumbnail #{index} for {videoId}. The maximum quality is {player.Thumbnails.Length - 1}"));
await Response.StartAsync();
return;
}
string url = player.Thumbnails.FirstOrDefault()?.Url;
if (!url.StartsWith("http://") && !url.StartsWith("https://"))
url = "https://" + url;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
foreach ((string header, StringValues values) in HttpContext.Request.Headers.Where(header =>
!header.Key.StartsWith(":") && !BlockedHeaders.Contains(header.Key.ToLower())))
foreach (string value in values)
request.Headers.Add(header, value);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();
foreach (string header in response.Headers.AllKeys)
if (Response.Headers.ContainsKey(header))
Response.Headers[header] = response.Headers.Get(header);
else
Response.Headers.Add(header, response.Headers.Get(header));
Response.StatusCode = (int)response.StatusCode;
await using Stream stream = response.GetResponseStream();
await stream.CopyToAsync(Response.Body);
await Response.StartAsync();
}
[Route("storyboard/{videoId}")]
public async Task StoryboardProxy(string videoId)
{
try
{
YoutubePlayer player = await _youtube.GetPlayerAsync(videoId);
if (!string.IsNullOrWhiteSpace(player.ErrorMessage))
{
Response.StatusCode = (int) HttpStatusCode.InternalServerError;
await Response.Body.WriteAsync(Encoding.UTF8.GetBytes(player.ErrorMessage));
await Response.StartAsync();
return;
}
if (!player.Storyboards.Any())
{
Response.StatusCode = (int) HttpStatusCode.NotFound;
await Response.Body.WriteAsync(Encoding.UTF8.GetBytes("No usable storyboard found."));
await Response.StartAsync();
return;
}
string url = player.Storyboards.First();
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
foreach ((string header, StringValues values) in HttpContext.Request.Headers.Where(header =>
!header.Key.StartsWith(":") && !BlockedHeaders.Contains(header.Key.ToLower())))
foreach (string value in values)
request.Headers.Add(header, value);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();
foreach (string header in response.Headers.AllKeys)
if (Response.Headers.ContainsKey(header))
Response.Headers[header] = response.Headers.Get(header);
else
Response.Headers.Add(header, response.Headers.Get(header));
Response.StatusCode = (int)response.StatusCode;
await using Stream stream = response.GetResponseStream();
await stream.CopyToAsync(Response.Body);
await Response.StartAsync();
}
catch (Exception e)
{
Response.StatusCode = (int) HttpStatusCode.InternalServerError;
await Response.Body.WriteAsync(Encoding.UTF8.GetBytes(e.ToString()));
await Response.StartAsync();
}
}
[Route("hls")]
public async Task<IActionResult> HlsProxy(string url)
{
if (!url.StartsWith("http://") && !url.StartsWith("https://"))
url = "https://" + url;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
foreach ((string header, StringValues values) in HttpContext.Request.Headers.Where(header =>
!header.Key.StartsWith(":") && !BlockedHeaders.Contains(header.Key.ToLower())))
foreach (string value in values)
request.Headers.Add(header, value);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();
await using Stream stream = response.GetResponseStream();
using StreamReader reader = new(stream);
string manifest = await reader.ReadToEndAsync();
StringBuilder proxyManifest = new ();
foreach (string s in manifest.Split("\n"))
{
// also check if proxy enabled
proxyManifest.AppendLine(!s.StartsWith("http")
? s
: $"https://{Request.Host}/proxy/video?url={HttpUtility.UrlEncode(s)}");
}
return File(new MemoryStream(Encoding.UTF8.GetBytes(proxyManifest.ToString())),
"application/vnd.apple.mpegurl");
}
[Route("manifest/{videoId}")]
public async Task<IActionResult> ManifestProxy(string videoId, string formatId, bool useProxy = true)
{
YoutubePlayer player = await _youtube.GetPlayerAsync(videoId, iOS: true);
if (!string.IsNullOrWhiteSpace(player.ErrorMessage))
{
Response.StatusCode = (int) HttpStatusCode.InternalServerError;
return File(new MemoryStream(Encoding.UTF8.GetBytes(player.ErrorMessage)),
"text/plain");
}
if (player.HlsManifestUrl == null)
{
Response.StatusCode = (int) HttpStatusCode.NotFound;
return File(new MemoryStream(Encoding.UTF8.GetBytes("This video does not have an HLS manifest URL")),
"text/plain");
}
string url = player.HlsManifestUrl;
if (!url.StartsWith("http://") && !url.StartsWith("https://"))
url = "https://" + url;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
foreach ((string header, StringValues values) in HttpContext.Request.Headers.Where(header =>
!header.Key.StartsWith(":") && !BlockedHeaders.Contains(header.Key.ToLower())))
foreach (string value in values)
request.Headers.Add(header, value);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();
await using Stream stream = response.GetResponseStream();
using StreamReader reader = new(stream);
string manifest = await reader.ReadToEndAsync();
StringBuilder proxyManifest = new ();
if (useProxy)
foreach (string s in manifest.Split("\n"))
{
// also check if proxy enabled
proxyManifest.AppendLine(!s.StartsWith("http")
? s
: $"https://{Request.Host}/proxy/ytmanifest?path=" + HttpUtility.UrlEncode(s[46..]));
}
else
proxyManifest.Append(manifest);
return File(new MemoryStream(Encoding.UTF8.GetBytes(proxyManifest.ToString())),
"application/vnd.apple.mpegurl");
}
[Route("ytmanifest")]
public async Task<IActionResult> YoutubeManifestProxy(string path)
{
string url = "https://manifest.googlevideo.com" + path;
StringBuilder sb = new();
if (!url.StartsWith("http://") && !url.StartsWith("https://"))
url = "https://" + url;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
foreach ((string header, StringValues values) in HttpContext.Request.Headers.Where(header =>
!header.Key.StartsWith(":") && !BlockedHeaders.Contains(header.Key.ToLower())))
foreach (string value in values)
request.Headers.Add(header, value);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();
await using Stream stream = response.GetResponseStream();
using StreamReader reader = new(stream);
string manifest = await reader.ReadToEndAsync();
foreach (string line in manifest.Split("\n"))
{
if (string.IsNullOrWhiteSpace(line))
sb.AppendLine();
else if (line.StartsWith("#"))
sb.AppendLine(line);
else
{
Uri u = new(line);
sb.AppendLine($"https://{Request.Host}/proxy/videoplayback?host={u.Host}&path={HttpUtility.UrlEncode(u.PathAndQuery)}");
}
}
return File(new MemoryStream(Encoding.UTF8.GetBytes(sb.ToString())),
"application/vnd.apple.mpegurl");
}
[Route("videoplayback")]
public async Task VideoPlaybackProxy(string path, string host)
{
// make sure this is only used in livestreams
string url = $"https://{host}{path}";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
foreach ((string header, StringValues values) in HttpContext.Request.Headers.Where(header =>
!header.Key.StartsWith(":") && !BlockedHeaders.Contains(header.Key.ToLower())))
foreach (string value in values)
request.Headers.Add(header, value);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();
await using Stream stream = response.GetResponseStream();
Response.ContentType = "application/octet-stream";
await Response.StartAsync();
await stream.CopyToAsync(Response.Body, HttpContext.RequestAborted);
}
}
}

View File

@ -1,67 +0,0 @@
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace LightTube.Controllers
{
[Route("/toggles")]
public class TogglesController : Controller
{
[Route("theme")]
public IActionResult ToggleTheme(string redirectUrl)
{
if (Request.Cookies.TryGetValue("theme", out string theme))
Response.Cookies.Append("theme", theme switch
{
"light" => "dark",
"dark" => "light",
var _ => "dark"
}, new CookieOptions
{
Expires = DateTimeOffset.MaxValue
});
else
Response.Cookies.Append("theme", "light");
return Redirect(redirectUrl);
}
[Route("compatibility")]
public IActionResult ToggleCompatibility(string redirectUrl)
{
if (Request.Cookies.TryGetValue("compatibility", out string compatibility))
Response.Cookies.Append("compatibility", compatibility switch
{
"true" => "false",
"false" => "true",
var _ => "true"
}, new CookieOptions
{
Expires = DateTimeOffset.MaxValue
});
else
Response.Cookies.Append("compatibility", "true");
return Redirect(redirectUrl);
}
[Route("collapse_guide")]
public IActionResult ToggleCollapseGuide(string redirectUrl)
{
if (Request.Cookies.TryGetValue("minmode", out string minmode))
Response.Cookies.Append("minmode", minmode switch
{
"true" => "false",
"false" => "true",
var _ => "true"
}, new CookieOptions
{
Expires = DateTimeOffset.MaxValue
});
else
Response.Cookies.Append("minmode", "true");
return Redirect(redirectUrl);
}
}
}

View File

@ -1,226 +0,0 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using LightTube.Contexts;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using InnerTube;
using InnerTube.Models;
using LightTube.Database;
namespace LightTube.Controllers
{
public class YoutubeController : Controller
{
private readonly ILogger<YoutubeController> _logger;
private readonly Youtube _youtube;
public YoutubeController(ILogger<YoutubeController> logger, Youtube youtube)
{
_logger = logger;
_youtube = youtube;
}
[Route("/watch")]
public async Task<IActionResult> Watch(string v, string quality = null)
{
Task[] tasks = {
_youtube.GetPlayerAsync(v, HttpContext.GetLanguage(), HttpContext.GetRegion()),
_youtube.GetVideoAsync(v, HttpContext.GetLanguage(), HttpContext.GetRegion()),
ReturnYouTubeDislike.GetDislikes(v)
};
await Task.WhenAll(tasks);
bool cookieCompatibility = false;
if (Request.Cookies.TryGetValue("compatibility", out string compatibilityString))
bool.TryParse(compatibilityString, out cookieCompatibility);
PlayerContext context = new()
{
Player = (tasks[0] as Task<YoutubePlayer>)?.Result,
Video = (tasks[1] as Task<YoutubeVideo>)?.Result,
Engagement = (tasks[2] as Task<YoutubeDislikes>)?.Result,
Resolution = quality ?? (tasks[0] as Task<YoutubePlayer>)?.Result.Formats.FirstOrDefault(x => x.FormatId != "17")?.FormatNote,
MobileLayout = Utils.IsClientMobile(Request),
CompatibilityMode = cookieCompatibility
};
return View(context);
}
[Route("/download")]
public async Task<IActionResult> Download(string v)
{
Task[] tasks = {
_youtube.GetPlayerAsync(v, HttpContext.GetLanguage(), HttpContext.GetRegion()),
_youtube.GetVideoAsync(v, HttpContext.GetLanguage(), HttpContext.GetRegion()),
ReturnYouTubeDislike.GetDislikes(v)
};
await Task.WhenAll(tasks);
bool cookieCompatibility = false;
if (Request.Cookies.TryGetValue("compatibility", out string compatibilityString))
bool.TryParse(compatibilityString, out cookieCompatibility);
PlayerContext context = new()
{
Player = (tasks[0] as Task<YoutubePlayer>)?.Result,
Video = (tasks[1] as Task<YoutubeVideo>)?.Result,
Engagement = null,
MobileLayout = Utils.IsClientMobile(Request),
CompatibilityMode = cookieCompatibility
};
return View(context);
}
[Route("/embed/{v}")]
public async Task<IActionResult> Embed(string v, string quality = null, bool compatibility = false)
{
Task[] tasks = {
_youtube.GetPlayerAsync(v, HttpContext.GetLanguage(), HttpContext.GetRegion()),
_youtube.GetVideoAsync(v, HttpContext.GetLanguage(), HttpContext.GetRegion()),
ReturnYouTubeDislike.GetDislikes(v)
};
try
{
await Task.WhenAll(tasks);
}
catch { }
bool cookieCompatibility = false;
if (Request.Cookies.TryGetValue("compatibility", out string compatibilityString))
bool.TryParse(compatibilityString, out cookieCompatibility);
PlayerContext context = new()
{
Player = (tasks[0] as Task<YoutubePlayer>)?.Result,
Video = (tasks[1] as Task<YoutubeVideo>)?.Result,
Engagement = (tasks[2] as Task<YoutubeDislikes>)?.Result,
Resolution = quality ?? (tasks[0] as Task<YoutubePlayer>)?.Result.Formats.FirstOrDefault(x => x.FormatId != "17")?.FormatNote,
CompatibilityMode = compatibility || cookieCompatibility,
MobileLayout = Utils.IsClientMobile(Request)
};
return View(context);
}
[Route("/results")]
public async Task<IActionResult> Search(string search_query, string continuation = null)
{
SearchContext context = new()
{
Query = search_query,
ContinuationKey = continuation,
MobileLayout = Utils.IsClientMobile(Request)
};
if (!string.IsNullOrWhiteSpace(search_query))
{
context.Results = await _youtube.SearchAsync(search_query, continuation, HttpContext.GetLanguage(),
HttpContext.GetRegion());
Response.Cookies.Append("search_query", search_query);
}
else
{
context.Results =
new YoutubeSearchResults
{
Refinements = Array.Empty<string>(),
EstimatedResults = 0,
Results = Array.Empty<DynamicItem>(),
ContinuationKey = null
};
}
return View(context);
}
[Route("/playlist")]
public async Task<IActionResult> Playlist(string list, string continuation = null, int? delete = null, string add = null, string remove = null)
{
HttpContext.TryGetUser(out LTUser user, "web");
YoutubePlaylist pl = list.StartsWith("LT-PL")
? await (await DatabaseManager.Playlists.GetPlaylist(list)).ToYoutubePlaylist()
: await _youtube.GetPlaylistAsync(list, continuation, HttpContext.GetLanguage(), HttpContext.GetRegion());
string message = "";
if (list.StartsWith("LT-PL") && (await DatabaseManager.Playlists.GetPlaylist(list)).Visibility == PlaylistVisibility.PRIVATE && pl.Channel.Name != user?.UserID)
pl = new YoutubePlaylist
{
Id = null,
Title = "",
Description = "",
VideoCount = "",
ViewCount = "",
LastUpdated = "",
Thumbnail = Array.Empty<Thumbnail>(),
Channel = new Channel
{
Name = "",
Id = "",
SubscriberCount = "",
Avatars = Array.Empty<Thumbnail>()
},
Videos = Array.Empty<DynamicItem>(),
ContinuationKey = null
};
if (string.IsNullOrWhiteSpace(pl.Title)) message = "Playlist unavailable";
if (list.StartsWith("LT-PL") && pl.Channel.Name == user?.UserID)
{
if (delete != null)
{
LTVideo removed = await DatabaseManager.Playlists.RemoveVideoFromPlaylist(list, delete.Value);
message += $"Removed video '{removed.Title}'";
}
if (add != null)
{
LTVideo added = await DatabaseManager.Playlists.AddVideoToPlaylist(list, add);
message += $"Added video '{added.Title}'";
}
if (!string.IsNullOrWhiteSpace(remove))
{
await DatabaseManager.Playlists.DeletePlaylist(list);
message = "Playlist deleted";
}
pl = await (await DatabaseManager.Playlists.GetPlaylist(list)).ToYoutubePlaylist();
}
PlaylistContext context = new()
{
Playlist = pl,
Id = list,
ContinuationToken = continuation,
MobileLayout = Utils.IsClientMobile(Request),
Message = message,
Editable = list.StartsWith("LT-PL") && pl.Channel.Name == user?.UserID
};
return View(context);
}
[Route("/channel/{id}")]
public async Task<IActionResult> Channel(string id, string continuation = null)
{
ChannelContext context = new()
{
Channel = await _youtube.GetChannelAsync(id, ChannelTabs.Videos, continuation, HttpContext.GetLanguage(), HttpContext.GetRegion()),
Id = id,
ContinuationToken = continuation,
MobileLayout = Utils.IsClientMobile(Request)
};
await DatabaseManager.Channels.UpdateChannel(context.Channel.Id, context.Channel.Name, context.Channel.Subscribers,
context.Channel.Avatars.First().Url.ToString());
return View(context);
}
[Route("/shorts/{id}")]
public IActionResult Shorts(string id)
{
// yea no fuck shorts
return Redirect("/watch?v=" + id);
}
}
}

View File

@ -1,46 +0,0 @@
using System.Threading.Tasks;
using MongoDB.Driver;
namespace LightTube.Database
{
public class ChannelManager
{
private static IMongoCollection<LTChannel> _channelCacheCollection;
public ChannelManager(IMongoCollection<LTChannel> channelCacheCollection)
{
_channelCacheCollection = channelCacheCollection;
}
public LTChannel GetChannel(string id)
{
LTChannel res = _channelCacheCollection.FindSync(x => x.ChannelId == id).FirstOrDefault();
return res ?? new LTChannel
{
Name = "Unknown Channel",
ChannelId = id,
IconUrl = "",
Subscribers = ""
};
}
public async Task<LTChannel> UpdateChannel(string id, string name, string subscribers, string iconUrl)
{
LTChannel channel = new()
{
ChannelId = id,
Name = name,
Subscribers = subscribers,
IconUrl = iconUrl
};
if (channel.IconUrl is null && !string.IsNullOrWhiteSpace(GetChannel(id).IconUrl))
channel.IconUrl = GetChannel(id).IconUrl;
if (await _channelCacheCollection.CountDocumentsAsync(x => x.ChannelId == id) > 0)
await _channelCacheCollection.ReplaceOneAsync(x => x.ChannelId == id, channel);
else
await _channelCacheCollection.InsertOneAsync(channel);
return channel;
}
}
}

View File

@ -1,154 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using InnerTube;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using MongoDB.Driver;
using Newtonsoft.Json;
namespace LightTube.Database
{
public static class DatabaseManager
{
public static readonly string ApiUaRegex = "LightTubeApiClient\\/([0-9.]*) ([\\S]+?)\\/([0-9.]*) \\(([\\s\\S]+?)\\)";
private static IMongoCollection<LTUser> _userCollection;
private static IMongoCollection<LTLogin> _tokenCollection;
private static IMongoCollection<LTChannel> _channelCacheCollection;
private static IMongoCollection<LTPlaylist> _playlistCollection;
private static IMongoCollection<LTVideo> _videoCacheCollection;
public static LoginManager Logins { get; private set; }
public static ChannelManager Channels { get; private set; }
public static PlaylistManager Playlists { get; private set; }
public static void Init(string connstr, Youtube youtube)
{
MongoClient client = new(connstr);
IMongoDatabase database = client.GetDatabase("lighttube");
_userCollection = database.GetCollection<LTUser>("users");
_tokenCollection = database.GetCollection<LTLogin>("tokens");
_playlistCollection = database.GetCollection<LTPlaylist>("playlists");
_channelCacheCollection = database.GetCollection<LTChannel>("channelCache");
_videoCacheCollection = database.GetCollection<LTVideo>("videoCache");
Logins = new LoginManager(_userCollection, _tokenCollection);
Channels = new ChannelManager(_channelCacheCollection);
Playlists = new PlaylistManager(_userCollection, _playlistCollection, _videoCacheCollection, youtube);
}
public static void CreateLocalAccount(this HttpContext context)
{
bool accountExists = false;
// Check local account
if (context.Request.Cookies.TryGetValue("account_data", out string accountJson))
{
try
{
if (accountJson != null)
{
LTUser tempUser = JsonConvert.DeserializeObject<LTUser>(accountJson) ?? new LTUser();
if (tempUser.UserID == "Local Account" && tempUser.PasswordHash == "local_account")
accountExists = true;
}
}
catch { }
}
// Account already exists, just leave it there
if (accountExists) return;
LTUser user = new()
{
UserID = "Local Account",
PasswordHash = "local_account",
SubscribedChannels = new List<string>()
};
context.Response.Cookies.Append("account_data", JsonConvert.SerializeObject(user), new CookieOptions
{
Expires = DateTimeOffset.MaxValue
});
}
public static bool TryGetUser(this HttpContext context, out LTUser user, string requiredScope)
{
// Check local account
if (context.Request.Cookies.TryGetValue("account_data", out string accountJson))
{
try
{
if (accountJson != null)
{
LTUser tempUser = JsonConvert.DeserializeObject<LTUser>(accountJson) ?? new LTUser();
if (tempUser.UserID == "Local Account" && tempUser.PasswordHash == "local_account")
{
user = tempUser;
return true;
}
}
}
catch
{
user = null;
return false;
}
}
// Check cloud account
if (!context.Request.Cookies.TryGetValue("token", out string token))
if (context.Request.Headers.TryGetValue("Authorization", out StringValues tokens))
token = tokens.ToString();
else
{
user = null;
return false;
}
try
{
if (token != null)
{
user = Logins.GetUserFromToken(token).Result;
LTLogin login = Logins.GetLoginFromToken(token).Result;
if (login.Scopes.Contains(requiredScope))
{
#pragma warning disable 4014
login.UpdateLastAccess(DateTimeOffset.Now);
#pragma warning restore 4014
return true;
}
return false;
}
}
catch
{
user = null;
return false;
}
user = null;
return false;
}
public static bool TryGetRssUser(string token, out LTUser user)
{
if (token is null)
{
user = null;
return false;
}
try
{
user = Logins.GetUserFromRssToken(token).Result;
return true;
}
catch
{
user = null;
return false;
}
}
}
}

View File

@ -1,31 +0,0 @@
using System.Xml;
using MongoDB.Bson.Serialization.Attributes;
namespace LightTube.Database
{
[BsonIgnoreExtraElements]
public class LTChannel
{
public string ChannelId;
public string Name;
public string Subscribers;
public string IconUrl;
public XmlNode GetXmlElement(XmlDocument doc)
{
XmlElement item = doc.CreateElement("Channel");
item.SetAttribute("id", ChannelId);
item.SetAttribute("subscribers", Subscribers);
XmlElement title = doc.CreateElement("Name");
title.InnerText = Name;
item.AppendChild(title);
XmlElement thumbnail = doc.CreateElement("Avatar");
thumbnail.InnerText = IconUrl;
item.AppendChild(thumbnail);
return item;
}
}
}

View File

@ -1,84 +0,0 @@
using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using System.Xml;
using Humanizer;
using MongoDB.Bson.Serialization.Attributes;
using MyCSharp.HttpUserAgentParser;
namespace LightTube.Database
{
[BsonIgnoreExtraElements]
public class LTLogin
{
public string Identifier;
public string Email;
public string Token;
public string UserAgent;
public string[] Scopes;
public DateTimeOffset Created = DateTimeOffset.MinValue;
public DateTimeOffset LastSeen = DateTimeOffset.MinValue;
public XmlDocument GetXmlElement()
{
XmlDocument doc = new();
XmlElement login = doc.CreateElement("Login");
login.SetAttribute("id", Identifier);
login.SetAttribute("user", Email);
XmlElement token = doc.CreateElement("Token");
token.InnerText = Token;
login.AppendChild(token);
XmlElement scopes = doc.CreateElement("Scopes");
foreach (string scope in Scopes)
{
XmlElement scopeElement = doc.CreateElement("Scope");
scopeElement.InnerText = scope;
login.AppendChild(scopeElement);
}
login.AppendChild(scopes);
doc.AppendChild(login);
return doc;
}
public string GetTitle()
{
Match match = Regex.Match(UserAgent, DatabaseManager.ApiUaRegex);
if (match.Success)
return $"API App: {match.Groups[2]} {match.Groups[3]}";
HttpUserAgentInformation client = HttpUserAgentParser.Parse(UserAgent);
StringBuilder sb = new($"{client.Name} {client.Version}");
if (client.Platform.HasValue)
sb.Append($" on {client.Platform.Value.PlatformType.ToString()}");
return sb.ToString();
}
public string GetDescription()
{
StringBuilder sb = new();
sb.AppendLine($"Created: {Created.Humanize(DateTimeOffset.Now)}");
sb.AppendLine($"Last seen: {LastSeen.Humanize(DateTimeOffset.Now)}");
Match match = Regex.Match(UserAgent, DatabaseManager.ApiUaRegex);
if (match.Success)
{
sb.AppendLine($"API version: {HttpUtility.HtmlEncode(match.Groups[1])}");
sb.AppendLine($"App info: {HttpUtility.HtmlEncode(match.Groups[4])}");
sb.AppendLine("Allowed scopes:");
foreach (string scope in Scopes) sb.AppendLine($"- {scope}");
}
return sb.ToString();
}
public async Task UpdateLastAccess(DateTimeOffset newTime)
{
await DatabaseManager.Logins.UpdateLastAccess(Identifier, newTime);
}
}
}

View File

@ -1,68 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using InnerTube.Models;
namespace LightTube.Database
{
public class LTPlaylist
{
public string Id;
public string Name;
public string Description;
public PlaylistVisibility Visibility;
public List<string> VideoIds;
public string Author;
public DateTimeOffset LastUpdated;
public async Task<YoutubePlaylist> ToYoutubePlaylist()
{
List<Thumbnail> t = new();
if (VideoIds.Count > 0)
t.Add(new Thumbnail { Url = $"https://i.ytimg.com/vi_webp/{VideoIds.First()}/maxresdefault.webp" });
YoutubePlaylist playlist = new()
{
Id = Id,
Title = Name,
Description = Description,
VideoCount = VideoIds.Count.ToString(),
ViewCount = "0",
LastUpdated = "Last updated " + LastUpdated.ToString("MMMM dd, yyyy"),
Thumbnail = t.ToArray(),
Channel = new Channel
{
Name = Author,
Id = GenerateChannelId(),
SubscriberCount = "0 subscribers",
Avatars = Array.Empty<Thumbnail>()
},
Videos = (await DatabaseManager.Playlists.GetPlaylistVideos(Id)).Select(x =>
{
x.Index = VideoIds.IndexOf(x.Id) + 1;
return x;
}).Cast<DynamicItem>().ToArray(),
ContinuationKey = null
};
return playlist;
}
private string GenerateChannelId()
{
StringBuilder sb = new("LTU-" + Author.Trim() + "_");
string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
Random rng = new(Author.GetHashCode());
while (sb.Length < 32) sb.Append(alphabet[rng.Next(0, alphabet.Length)]);
return sb.ToString();
}
}
public enum PlaylistVisibility
{
PRIVATE,
UNLISTED,
VISIBLE
}
}

View File

@ -1,102 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Xml;
using MongoDB.Bson.Serialization.Attributes;
namespace LightTube.Database
{
[BsonIgnoreExtraElements]
public class LTUser
{
public string UserID;
public string PasswordHash;
public List<string> SubscribedChannels;
public bool ApiAccess;
public string RssToken;
public async Task<string> GenerateRssFeed(string hostUrl, int limit)
{
XmlDocument document = new();
XmlElement rss = document.CreateElement("rss");
rss.SetAttribute("version", "2.0");
XmlElement channel = document.CreateElement("channel");
XmlElement title = document.CreateElement("title");
title.InnerText = "LightTube subscriptions RSS feed for " + UserID;
channel.AppendChild(title);
XmlElement description = document.CreateElement("description");
description.InnerText = $"LightTube subscriptions RSS feed for {UserID} with {SubscribedChannels.Count} channels";
channel.AppendChild(description);
FeedVideo[] feeds = await YoutubeRSS.GetMultipleFeeds(SubscribedChannels);
IEnumerable<FeedVideo> feedVideos = feeds.Take(limit);
foreach (FeedVideo video in feedVideos)
{
XmlElement item = document.CreateElement("item");
XmlElement id = document.CreateElement("id");
id.InnerText = $"id:video:{video.Id}";
item.AppendChild(id);
XmlElement vtitle = document.CreateElement("title");
vtitle.InnerText = video.Title;
item.AppendChild(vtitle);
XmlElement vdescription = document.CreateElement("description");
vdescription.InnerText = video.Description;
item.AppendChild(vdescription);
XmlElement link = document.CreateElement("link");
link.InnerText = $"https://{hostUrl}/watch?v={video.Id}";
item.AppendChild(link);
XmlElement published = document.CreateElement("pubDate");
published.InnerText = video.PublishedDate.ToString("R");
item.AppendChild(published);
XmlElement author = document.CreateElement("author");
XmlElement name = document.CreateElement("name");
name.InnerText = video.ChannelName;
author.AppendChild(name);
XmlElement uri = document.CreateElement("uri");
uri.InnerText = $"https://{hostUrl}/channel/{video.ChannelId}";
author.AppendChild(uri);
item.AppendChild(author);
/*
XmlElement mediaGroup = document.CreateElement("media_group");
XmlElement mediaTitle = document.CreateElement("media_title");
mediaTitle.InnerText = video.Title;
mediaGroup.AppendChild(mediaTitle);
XmlElement mediaThumbnail = document.CreateElement("media_thumbnail");
mediaThumbnail.SetAttribute("url", video.Thumbnail);
mediaGroup.AppendChild(mediaThumbnail);
XmlElement mediaContent = document.CreateElement("media_content");
mediaContent.SetAttribute("url", $"https://{hostUrl}/embed/{video.Id}");
mediaContent.SetAttribute("type", "text/html");
mediaGroup.AppendChild(mediaContent);
item.AppendChild(mediaGroup);
*/
channel.AppendChild(item);
}
rss.AppendChild(channel);
document.AppendChild(rss);
return document.OuterXml;//.Replace("<media_", "<media:").Replace("</media_", "</media:");
}
}
}

View File

@ -1,39 +0,0 @@
using System;
using System.Xml;
using InnerTube.Models;
namespace LightTube.Database
{
public class LTVideo : PlaylistVideoItem
{
public string UploadedAt;
public long Views;
public override XmlElement GetXmlElement(XmlDocument doc)
{
XmlElement item = doc.CreateElement("Video");
item.SetAttribute("id", Id);
item.SetAttribute("duration", Duration);
item.SetAttribute("views", Views.ToString());
item.SetAttribute("uploadedAt", UploadedAt);
item.SetAttribute("index", Index.ToString());
XmlElement title = doc.CreateElement("Title");
title.InnerText = Title;
item.AppendChild(title);
if (Channel is not null)
item.AppendChild(Channel.GetXmlElement(doc));
foreach (Thumbnail t in Thumbnails ?? Array.Empty<Thumbnail>())
{
XmlElement thumbnail = doc.CreateElement("Thumbnail");
thumbnail.SetAttribute("width", t.Width.ToString());
thumbnail.SetAttribute("height", t.Height.ToString());
thumbnail.InnerText = t.Url;
item.AppendChild(thumbnail);
}
return item;
}
}
}

View File

@ -1,172 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using InnerTube.Models;
using MongoDB.Driver;
namespace LightTube.Database
{
public class LoginManager
{
private IMongoCollection<LTUser> _userCollection;
private IMongoCollection<LTLogin> _tokenCollection;
public LoginManager(IMongoCollection<LTUser> userCollection, IMongoCollection<LTLogin> tokenCollection)
{
_userCollection = userCollection;
_tokenCollection = tokenCollection;
}
public async Task<LTLogin> CreateToken(string email, string password, string userAgent, string[] scopes)
{
IAsyncCursor<LTUser> users = await _userCollection.FindAsync(x => x.UserID == email);
if (!await users.AnyAsync())
throw new UnauthorizedAccessException("Invalid credentials");
LTUser user = (await _userCollection.FindAsync(x => x.UserID == email)).First();
if (!BCrypt.Net.BCrypt.Verify(password, user.PasswordHash))
throw new UnauthorizedAccessException("Invalid credentials");
if (!scopes.Contains("web") && !user.ApiAccess)
throw new InvalidOperationException("This user has API access disabled");
LTLogin login = new()
{
Identifier = Guid.NewGuid().ToString(),
Email = email,
Token = GenerateToken(256),
UserAgent = userAgent,
Scopes = scopes.ToArray(),
Created = DateTimeOffset.Now,
LastSeen = DateTimeOffset.Now
};
await _tokenCollection.InsertOneAsync(login);
return login;
}
public async Task UpdateLastAccess(string id, DateTimeOffset offset)
{
LTLogin login = (await _tokenCollection.FindAsync(x => x.Identifier == id)).First();
login.LastSeen = offset;
await _tokenCollection.ReplaceOneAsync(x => x.Identifier == id, login);
}
public async Task RemoveToken(string token)
{
await _tokenCollection.FindOneAndDeleteAsync(t => t.Token == token);
}
public async Task RemoveToken(string email, string password, string identifier)
{
IAsyncCursor<LTUser> users = await _userCollection.FindAsync(x => x.UserID == email);
if (!await users.AnyAsync())
throw new KeyNotFoundException("Invalid credentials");
LTUser user = (await _userCollection.FindAsync(x => x.UserID == email)).First();
if (!BCrypt.Net.BCrypt.Verify(password, user.PasswordHash))
throw new UnauthorizedAccessException("Invalid credentials");
await _tokenCollection.FindOneAndDeleteAsync(t => t.Identifier == identifier && t.Email == user.UserID);
}
[EditorBrowsable(EditorBrowsableState.Never)]
public async Task RemoveTokenFromId(string sourceToken, string identifier)
{
LTLogin login = (await _tokenCollection.FindAsync(x => x.Token == sourceToken)).First();
LTLogin deletedLogin = (await _tokenCollection.FindAsync(x => x.Identifier == identifier)).First();
if (login.Email == deletedLogin.Email)
await _tokenCollection.FindOneAndDeleteAsync(t => t.Identifier == identifier);
else
throw new UnauthorizedAccessException(
"Logged in user does not match the token that is supposed to be deleted");
}
public async Task<LTUser> GetUserFromToken(string token)
{
string email = (await _tokenCollection.FindAsync(x => x.Token == token)).First().Email;
return (await _userCollection.FindAsync(u => u.UserID == email)).First();
}
public async Task<LTUser> GetUserFromRssToken(string token) => (await _userCollection.FindAsync(u => u.RssToken == token)).First();
public async Task<LTLogin> GetLoginFromToken(string token)
{
var res = await _tokenCollection.FindAsync(x => x.Token == token);
return res.First();
}
public async Task<List<LTLogin>> GetAllUserTokens(string token)
{
string email = (await _tokenCollection.FindAsync(x => x.Token == token)).First().Email;
return await (await _tokenCollection.FindAsync(u => u.Email == email)).ToListAsync();
}
public async Task<string> GetCurrentLoginId(string token)
{
return (await _tokenCollection.FindAsync(t => t.Token == token)).First().Identifier;
}
public async Task<(LTChannel channel, bool subscribed)> SubscribeToChannel(LTUser user, YoutubeChannel channel)
{
LTChannel ltChannel = await DatabaseManager.Channels.UpdateChannel(channel.Id, channel.Name, channel.Subscribers,
channel.Avatars.FirstOrDefault()?.Url);
if (user.SubscribedChannels.Contains(ltChannel.ChannelId))
user.SubscribedChannels.Remove(ltChannel.ChannelId);
else
user.SubscribedChannels.Add(ltChannel.ChannelId);
await _userCollection.ReplaceOneAsync(x => x.UserID == user.UserID, user);
return (ltChannel, user.SubscribedChannels.Contains(ltChannel.ChannelId));
}
public async Task SetApiAccess(LTUser user, bool access)
{
user.ApiAccess = access;
await _userCollection.ReplaceOneAsync(x => x.UserID == user.UserID, user);
}
public async Task DeleteUser(string email, string password)
{
IAsyncCursor<LTUser> users = await _userCollection.FindAsync(x => x.UserID == email);
if (!await users.AnyAsync())
throw new KeyNotFoundException("Invalid credentials");
LTUser user = (await _userCollection.FindAsync(x => x.UserID == email)).First();
if (!BCrypt.Net.BCrypt.Verify(password, user.PasswordHash))
throw new UnauthorizedAccessException("Invalid credentials");
await _userCollection.DeleteOneAsync(x => x.UserID == email);
await _tokenCollection.DeleteManyAsync(x => x.Email == email);
foreach (LTPlaylist pl in await DatabaseManager.Playlists.GetUserPlaylists(email))
await DatabaseManager.Playlists.DeletePlaylist(pl.Id);
}
public async Task CreateUser(string email, string password)
{
IAsyncCursor<LTUser> users = await _userCollection.FindAsync(x => x.UserID == email);
if (await users.AnyAsync())
throw new DuplicateNameException("A user with that email already exists");
LTUser user = new()
{
UserID = email,
PasswordHash = BCrypt.Net.BCrypt.HashPassword(password),
SubscribedChannels = new List<string>(),
RssToken = GenerateToken(32)
};
await _userCollection.InsertOneAsync(user);
}
private string GenerateToken(int length)
{
string tokenAlphabet = @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-+*/()[]{}";
Random rng = new();
StringBuilder sb = new();
for (int i = 0; i < length; i++)
sb.Append(tokenAlphabet[rng.Next(0, tokenAlphabet.Length)]);
return sb.ToString();
}
}
}

View File

@ -1,161 +0,0 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using InnerTube;
using InnerTube.Models;
using MongoDB.Driver;
using Newtonsoft.Json.Linq;
namespace LightTube.Database
{
public class PlaylistManager
{
private IMongoCollection<LTUser> _userCollection;
private IMongoCollection<LTPlaylist> _playlistCollection;
private IMongoCollection<LTVideo> _videoCacheCollection;
private Youtube _youtube;
public PlaylistManager(IMongoCollection<LTUser> userCollection, IMongoCollection<LTPlaylist> playlistCollection,
IMongoCollection<LTVideo> videoCacheCollection, Youtube youtube)
{
_userCollection = userCollection;
_playlistCollection = playlistCollection;
_videoCacheCollection = videoCacheCollection;
_youtube = youtube;
}
public async Task<LTPlaylist> CreatePlaylist(LTUser user, string name, string description,
PlaylistVisibility visibility, string idPrefix = null)
{
if (await _userCollection.CountDocumentsAsync(x => x.UserID == user.UserID) == 0)
throw new UnauthorizedAccessException("Local accounts cannot create playlists");
LTPlaylist pl = new()
{
Id = GenerateAuthorId(idPrefix),
Name = name,
Description = description,
Visibility = visibility,
VideoIds = new List<string>(),
Author = user.UserID,
LastUpdated = DateTimeOffset.Now
};
await _playlistCollection.InsertOneAsync(pl).ConfigureAwait(false);
return pl;
}
public async Task<LTPlaylist> GetPlaylist(string id)
{
IAsyncCursor<LTPlaylist> cursor = await _playlistCollection.FindAsync(x => x.Id == id);
return await cursor.FirstOrDefaultAsync() ?? new LTPlaylist
{
Id = null,
Name = "",
Description = "",
Visibility = PlaylistVisibility.VISIBLE,
VideoIds = new List<string>(),
Author = "",
LastUpdated = DateTimeOffset.MinValue
};
}
public async Task<List<LTVideo>> GetPlaylistVideos(string id)
{
LTPlaylist pl = await GetPlaylist(id);
List<LTVideo> videos = new();
foreach (string videoId in pl.VideoIds)
{
IAsyncCursor<LTVideo> cursor = await _videoCacheCollection.FindAsync(x => x.Id == videoId);
videos.Add(await cursor.FirstAsync());
}
return videos;
}
public async Task<LTVideo> AddVideoToPlaylist(string playlistId, string videoId)
{
LTPlaylist pl = await GetPlaylist(playlistId);
YoutubeVideo vid = await _youtube.GetVideoAsync(videoId);
JObject ytPlayer = await InnerTube.Utils.GetAuthorizedPlayer(videoId, new HttpClient());
if (string.IsNullOrEmpty(vid.Id))
throw new KeyNotFoundException($"Couldn't find a video with ID '{videoId}'");
LTVideo v = new()
{
Id = vid.Id,
Title = vid.Title,
Thumbnails = ytPlayer?["videoDetails"]?["thumbnail"]?["thumbnails"]?.ToObject<Thumbnail[]>() ?? new []
{
new Thumbnail { Url = $"https://i.ytimg.com/vi_webp/{vid.Id}/maxresdefault.webp" }
},
UploadedAt = vid.UploadDate,
Views = long.Parse(vid.Views.Split(" ")[0].Replace(",", "").Replace(".", "")),
Channel = vid.Channel,
Duration = GetDurationString(ytPlayer?["videoDetails"]?["lengthSeconds"]?.ToObject<long>() ?? 0),
Index = pl.VideoIds.Count
};
pl.VideoIds.Add(vid.Id);
if (await _videoCacheCollection.CountDocumentsAsync(x => x.Id == vid.Id) == 0)
await _videoCacheCollection.InsertOneAsync(v);
else
await _videoCacheCollection.FindOneAndReplaceAsync(x => x.Id == vid.Id, v);
UpdateDefinition<LTPlaylist> update = Builders<LTPlaylist>.Update
.Push(x => x.VideoIds, vid.Id);
_playlistCollection.FindOneAndUpdate(x => x.Id == playlistId, update);
return v;
}
public async Task<LTVideo> RemoveVideoFromPlaylist(string playlistId, int videoIndex)
{
LTPlaylist pl = await GetPlaylist(playlistId);
IAsyncCursor<LTVideo> cursor = await _videoCacheCollection.FindAsync(x => x.Id == pl.VideoIds[videoIndex]);
LTVideo v = await cursor.FirstAsync();
pl.VideoIds.RemoveAt(videoIndex);
await _playlistCollection.FindOneAndReplaceAsync(x => x.Id == playlistId, pl);
return v;
}
public async Task<IEnumerable<LTPlaylist>> GetUserPlaylists(string userId)
{
IAsyncCursor<LTPlaylist> cursor = await _playlistCollection.FindAsync(x => x.Author == userId);
return cursor.ToEnumerable();
}
private string GetDurationString(long length)
{
string s = TimeSpan.FromSeconds(length).ToString();
while (s.StartsWith("00:") && s.Length > 5) s = s[3..];
return s;
}
public static string GenerateAuthorId(string prefix)
{
StringBuilder sb = new(string.IsNullOrWhiteSpace(prefix) || prefix.Trim().Length > 20
? "LT-PL"
: "LT-PL-" + prefix.Trim() + "_");
string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
Random rng = new();
while (sb.Length < 32) sb.Append(alphabet[rng.Next(0, alphabet.Length)]);
return sb.ToString();
}
public async Task DeletePlaylist(string playlistId)
{
await _playlistCollection.DeleteOneAsync(x => x.Id == playlistId);
}
}
}

View File

@ -1,18 +0,0 @@
using System.Xml;
namespace LightTube.Database
{
public class SubscriptionChannels
{
public LTChannel[] Channels { get; set; }
public XmlNode GetXmlDocument()
{
XmlDocument doc = new();
XmlElement feed = doc.CreateElement("Subscriptions");
foreach (LTChannel channel in Channels) feed.AppendChild(channel.GetXmlElement(doc));
doc.AppendChild(feed);
return doc;
}
}
}

View File

@ -1,18 +0,0 @@
using System.Xml;
namespace LightTube.Database
{
public class SubscriptionFeed
{
public FeedVideo[] videos;
public XmlDocument GetXmlDocument()
{
XmlDocument doc = new();
XmlElement feed = doc.CreateElement("Feed");
foreach (FeedVideo feedVideo in videos) feed.AppendChild(feedVideo.GetXmlElement(doc));
doc.AppendChild(feed);
return doc;
}
}
}

View File

@ -1,19 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\InnerTube\InnerTube.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.2" />
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="MongoDB.Driver" Version="2.14.1" />
<PackageReference Include="MyCSharp.HttpUserAgentParser" Version="1.1.5" />
<PackageReference Include="YamlDotNet" Version="11.2.1" />
</ItemGroup>
</Project>

View File

@ -1,11 +0,0 @@
using System;
namespace LightTube.Models
{
public class ErrorViewModel
{
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}
}

View File

@ -1,26 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace LightTube
{
public class Program
{
public static void Main(string[] args)
{
Configuration.LoadConfiguration();
InnerTube.Utils.SetAuthorization(Configuration.Instance.Credentials.CanUseAuthorizedEndpoints(),
Configuration.Instance.Credentials.Sapisid, Configuration.Instance.Credentials.Psid);
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
}
}

View File

@ -1,60 +0,0 @@
@using LightTube.Database
@using System.Web
@model LightTube.Contexts.BaseContext
@{
ViewBag.Title = "Account";
Layout = "_Layout";
Context.Request.Cookies.TryGetValue("theme", out string theme);
if (!new[] { "light", "dark" }.Contains(theme)) theme = "light";
string newTheme = theme switch {
"light" => "dark",
"dark" => "light",
var _ => "dark"
};
bool compatibility = false;
if (Context.Request.Cookies.TryGetValue("compatibility", out string compatibilityString))
bool.TryParse(compatibilityString, out compatibility);
}
<div class="login-container">
<div>
<div class="fullscreen-account-menu">
<h1>Settings</h1>
<br>
<div class="guide-item">
<a href="/toggles/theme?redirectUrl=@(HttpUtility.UrlEncode($"{Context.Request.Path}{Context.Request.QueryString}"))">Switch to @(newTheme) theme</a>
</div>
<br>
@if (Context.TryGetUser(out LTUser user, "web"))
{
<div class="guide-item">
<a href="/Account/Settings">Settings</a>
</div>
@if (user.PasswordHash != "local_account")
{
<div class="guide-item">
<a href="/Account/Logins">Active logins</a>
</div>
}
<div class="guide-item">
<a href="/Account/Logout">Log out</a>
</div>
}
else
{
<div class="guide-item">
<a href="/Account/Login">Log in</a>
</div>
<div class="guide-item">
<a href="/Account/Register">Register</a>
</div>
}
</div>
</div>
<div>
</div>
</div>

View File

@ -1,52 +0,0 @@
@using System.Web
@using LightTube.Database
@model LightTube.Contexts.AddToPlaylistContext
@{
ViewBag.Metadata = new Dictionary<string, string>();
ViewBag.Metadata["author"] = Model.Video.Channel.Name;
ViewBag.Metadata["og:title"] = Model.Video.Title;
ViewBag.Metadata["og:url"] = $"{Url.ActionContext.HttpContext.Request.Scheme}://{Url.ActionContext.HttpContext.Request.Host}{Url.ActionContext.HttpContext.Request.Path}{Url.ActionContext.HttpContext.Request.QueryString}";
ViewBag.Metadata["og:image"] = $"{Url.ActionContext.HttpContext.Request.Scheme}://{Url.ActionContext.HttpContext.Request.Host}/proxy/image?url={HttpUtility.UrlEncode(Model.Thumbnail)}";
ViewBag.Metadata["twitter:card"] = $"{Url.ActionContext.HttpContext.Request.Scheme}://{Url.ActionContext.HttpContext.Request.Host}/proxy/image?url={HttpUtility.UrlEncode(Model.Thumbnail)}";
ViewBag.Title = Model.Video.Title;
Layout = "_Layout";
}
<div class="playlist-page">
<div class="playlist-info">
<div class="thumbnail" style="background-image: url('@Model.Thumbnail')">
<a href="/watch?v=@Model.Video.Id">Watch</a>
</div>
<p class="title">@Model.Video.Title</p>
<span class="info">@Model.Video.Views • @Model.Video.UploadDate</span>
<div class="channel-info">
<a href="/channel/@Model.Video.Channel.Id" class="avatar">
<img src="@Model.Video.Channel.Avatars.LastOrDefault()?.Url">
</a>
<div class="name">
<a class="name" href="/channel/@Model.Video.Channel.Id">@Model.Video.Channel.Name</a>
</div>
</div>
</div>
<div class="video-list playlist-list playlist-video-list">
<h3>Add to one of these playlists:</h3>
<a class="login-button" href="/Account/CreatePlaylist" style="margin:unset;">Create playlist</a>
@foreach (LTPlaylist playlist in Model.Playlists)
{
<div class="playlist-video">
<a href="/playlist?list=@playlist.Id&add=@Model.Id" class="thumbnail"
style="background-image: url('https://i.ytimg.com/vi_webp/@playlist.VideoIds.FirstOrDefault()/maxresdefault.webp')">
</a>
<div class="info">
<a href="/playlist?list=@playlist.Id&add=@Model.Id" class="title max-lines-2">
@playlist.Name
</a>
<div>
<span>@playlist.VideoIds.Count videos</span>
</div>
</div>
</div>
}
</div>
</div>

View File

@ -1,25 +0,0 @@
@model LightTube.Contexts.BaseContext
@{
ViewBag.Title = "Create Playlist";
Layout = "_Layout";
}
<div class="login-container">
<div>
<div>
<form asp-action="CreatePlaylist" method="POST" class="playlist-form">
<h1>Create Playlist</h1>
<input name="name" type="text" placeholder="Playlist Name">
<input name="description" type="text" placeholder="Description">
<select name="visibility">
<option value="UNLISTED">Anyone with the link can view</option>
<option value="PRIVATE">Only you can view</option>
</select>
<input type="submit" value="Create">
</form>
</div>
</div>
<div>
</div>
</div>

View File

@ -1,46 +0,0 @@
@using LightTube.Database
@model LightTube.Contexts.MessageContext
@{
ViewData["Title"] = "Delete Account";
Layout = "_Layout";
}
@if (!string.IsNullOrWhiteSpace(Model.Message))
{
<div class="login-message">
@Model.Message
</div>
}
<div class="login-container">
<div>
<div>
@if (Context.Request.Cookies.TryGetValue("account_data", out string _))
{
Context.TryGetUser(out LTUser user, "web");
<form asp-action="Delete" method="POST" class="login-form">
<h1>Delete Account</h1>
<p>Deleting a local account</p>
<input name="email" type="hidden" value="@user.UserID">
<input name="password" type="hidden" value="@user.PasswordHash">
<input type="submit" value="Delete Account" class="login-button danger">
</form>
}
else
{
<form asp-action="Delete" method="POST" class="login-form">
<h1>Delete Account</h1>
<input name="userid" type="text" placeholder="UserID">
<input name="password" type="password" placeholder="Password">
<input type="submit" value="Delete Account" class="login-button danger">
</form>
}
</div>
</div>
<div>
<div>
<h1>Warning!</h1>
<p>You cannot undo this operation! After you enter your username and password, your account will get deleted forever.</p>
</div>
</div>
</div>

View File

@ -1,29 +0,0 @@
@model LightTube.Contexts.MessageContext
@{
ViewData["Title"] = "Login";
Layout = "_Layout";
}
@if (!string.IsNullOrWhiteSpace(Model.Message))
{
<div class="login-message">
@Model.Message
</div>
}
<div class="login-container">
<div>
<div>
<form asp-action="Login" method="POST" class="login-form">
<h1>Log in</h1>
<input name="userid" type="text" placeholder="UserID">
<input name="password" type="password" placeholder="Password">
<input type="submit" value="Login">
</form>
</div>
</div>
<div>
<h2>Don't have an account?</h2>
<a href="/Account/Register" class="login-button">Create an account</a>
</div>
</div>

View File

@ -1,18 +0,0 @@
@using LightTube.Database
@model LightTube.Contexts.LoginsContext
@{
ViewData["Title"] = "Active Logins";
Layout = "_Layout";
}
<h1 style="text-align:center;">Active Logins</h1>
<div class="logins-container">
@foreach (LTLogin login in Model.Logins)
{
<div class="login">
<h2 class="max-lines-1">@(login.Identifier == Model.CurrentLogin ? "(This window) " : "")@login.GetTitle()</h2>
<p>@Html.Raw(login.GetDescription().Replace("\n", "<br>"))</p>
<a href="/Account/DisableLogin?id=@login.Identifier" class="login-button" style="color:red;">Disable</a>
</div>
}
</div>

View File

@ -1,42 +0,0 @@
@model LightTube.Contexts.MessageContext
@{
ViewData["Title"] = "Register";
Layout = "_Layout";
}
@if (!string.IsNullOrWhiteSpace(Model.Message))
{
<div class="login-message">
@Model.Message
</div>
}
<div class="login-container">
<div>
<div>
<form asp-action="Register" method="POST" class="login-form">
<h1>Register</h1>
<input name="userid" type="text" placeholder="UserID">
<input name="password" type="password" placeholder="Password">
<input type="submit" value="Register">
</form>
</div>
</div>
<div>
<div>
<h1>...or register with a local account</h1>
<h2>What is the difference?</h2>
<ul>
<li>Remote account data is saved in this lighttube instance, while local account data is stored in
your browser's cookies
<ul>
<li>This means that the author of this lighttube instance cannot see your account data</li>
<li>It also means that, if you clear your cookies a lot, your account data will also get
lost with the cookies</li>
</ul>
</li>
</ul>
<a href="/Account/RegisterLocal" class="login-button">Create local account</a>
</div>
</div>
</div>

View File

@ -1,63 +0,0 @@
@model LightTube.Contexts.SettingsContext
@{
ViewBag.Title = "Settings";
Layout = "_Layout";
}
<form method="post">
<div class="settings-content">
<h1 style="text-align:center">Settings</h1>
<div>
<label for="settings-theme">Theme</label>
<select id="settings-theme" name="theme">
@Html.Raw($"<option value='light' {(Model.Theme == "light" ? "selected" : "")}>Light</option>")
@Html.Raw($"<option value='dark' {(Model.Theme == "dark" ? "selected" : "")}>Dark</option>")
</select>
<p>This is the visual theme the website will use.</p>
</div>
<div>
<label for="settings-yhl">Content Language</label>
<select id="settings-yhl" name="hl">
@foreach (KeyValuePair<string, string> o in Model.Languages)
{
@Html.Raw($"<option value='{o.Key}' {(o.Key == Model.CurrentLanguage ? "selected" : "")}>{o.Value}</option>")
}
</select>
<p>The language YouTube will deliver the content in. This will not affect LightTube's UI language.</p>
</div>
<div>
<label for="settings-ygl">Content Region</label>
<select id="settings-ygl" name="gl">
@foreach (KeyValuePair<string, string> o in Model.Regions)
{
@Html.Raw($"<option value='{o.Key}' {(o.Key == Model.CurrentRegion ? "selected" : "")}>{o.Value}</option>")
}
</select>
<p>The language YouTube will deliver the content for. It is used for the explore page and the recommendations.</p>
</div>
<div>
<label for="settings-player">Player</label>
<select id="settings-player" name="compatibility">
@Html.Raw($"<option value=\"false\" {(Model.CompatibilityMode ? "" : "selected")}>DASH playback with muxed fallback (recommended)</option>")
@Html.Raw($"<option value=\"true\" {(Model.CompatibilityMode ? "selected" : "")}>Muxed formats only (only supports 360p & 720p)</option>")
</select>
<p>Player behaviour. DASH playback allows for resolutions over 720p, but it is not compatible in all browsers. (e.g: Firefox Mobile)</p>
</div>
<div>
<label for="settings-api">API Access</label>
<select id="settings-api" name="api-access">
@Html.Raw($"<option value=\"true\" {(Model.ApiAccess ? "selected" : "")}>Enabled</option>")
@Html.Raw($"<option value=\"false\" {(Model.ApiAccess ? "" : "selected")}>Disabled</option>")
</select>
<p>This will allow apps to log in using your username and password</p>
</div>
<div style="display:flex;flex-direction:row">
<a href="/Account/Logins" class="login-button">Active Logins</a>
<a href="/Account/Delete" class="login-button" style="color:red">Delete Account</a>
</div>
</div>
<br>
<input type="submit" class="login-button" value="Save"/>
</form>

View File

@ -1,26 +0,0 @@
@using LightTube.Database
@model LightTube.Contexts.FeedContext
@{
ViewBag.Title = "Channel list";
}
<div class="video-list">
@foreach (LTChannel channel in Model.Channels)
{
<div class="channel">
<a href="/channel/@channel.ChannelId" class="avatar">
<img src="@channel.IconUrl" alt="Channel Avatar">
</a>
<a href="/channel/@channel.ChannelId" class="info">
<span class="name max-lines-2">@channel.Name</span>
<div>
<div>
<span>@channel.Subscribers</span>
</div>
</div>
</a>
<button class="subscribe-button" data-cid="@channel.ChannelId">Subscribe</button>
</div>
}
</div>

View File

@ -1,8 +0,0 @@
@{
ViewData["Title"] = "Explore";
ViewData["SelectedGuideItem"] = "explore";
}
<div style="text-align: center">
<h1>Coming soon!</h1>
</div>

View File

@ -1,35 +0,0 @@
@using LightTube.Database
@model LightTube.Contexts.PlaylistsContext
@{
ViewData["Title"] = "Playlists";
ViewData["SelectedGuideItem"] = "library";
Layout = "_Layout";
}
<div class="video-list">
<h2>Playlists</h2>
@foreach (LTPlaylist playlist in Model.Playlists)
{
<div class="playlist">
<a href="/watch?v=@playlist.VideoIds.FirstOrDefault()&list=@playlist.Id" class="thumbnail" style="background-image: url('https://i.ytimg.com/vi_webp/@playlist.VideoIds.FirstOrDefault()/maxresdefault.webp')">
<div>
<span>@playlist.VideoIds.Count</span><span>VIDEOS</span>
</div>
</a>
<div class="info">
<a href="/watch?v=@playlist.VideoIds.FirstOrDefault()&list=@playlist.Id" class="title max-lines-2">@playlist.Name</a>
<div>
<a href="/channel/@PlaylistManager.GenerateAuthorId(playlist.Author)">@playlist.Author</a>
<ul>
<li>
<a href="/playlist?list=@playlist.Id">
<b>View Full Playlist</b>
</a>
</li>
</ul>
</div>
</div>
</div>
}
</div>

View File

@ -1,55 +0,0 @@
@using Humanizer
@using LightTube.Database
@using System.Web
@model LightTube.Contexts.FeedContext
@{
ViewData["Title"] = "Subscriptions";
ViewData["SelectedGuideItem"] = "subs";
bool minMode = false;
if (Context.Request.Cookies.TryGetValue("minMode", out string minModeString))
bool.TryParse(minModeString, out minMode);
}
<div class="horizontal-channel-list" style="max-width: @(!Model.MobileLayout ? $"calc(100vw - {(minMode ? 80 : 312)}px);" : "")">
<a href="/feed/channels" class="channel">
<i class="bi bi-gear"></i>
<div class="name max-lines-2">Manage Channels</div>
</a>
<a href="/rss?token=@HttpUtility.UrlEncode(Model.RssToken)" class="channel">
<i class="bi bi-rss"></i>
<div class="name max-lines-2">RSS Feed</div>
</a>
@foreach (LTChannel channel in Model.Channels)
{
<a href="/channel/@channel.ChannelId" class="channel">
<img src="@channel.IconUrl" loading="lazy">
<div class="name max-lines-2">@channel.Name</div>
</a>
}
</div>
<div class="rich-video-grid">
@foreach (FeedVideo video in Model.Videos)
{
<div class="video">
<a href="/watch?v=@video.Id" class="thumbnail img-thumbnail">
<img src="@video.Thumbnail" loading="lazy">
</a>
<a href="/channel/@video.ChannelId" class="avatar">
<img src="@Model.Channels.First(x => x.ChannelId == video.ChannelId).IconUrl">
</a>
<div class="info">
<a href="/watch?v=@video.Id" class="title max-lines-2">@video.Title</a>
<div>
<a href="/channel/@video.ChannelId">@video.ChannelName</a>
<div>
<span>@video.ViewCount views</span>
<span>•</span>
<span>@video.PublishedDate.Humanize(DateTimeOffset.Now)</span>
</div>
</div>
</div>
</div>
}
</div>

View File

@ -1,15 +0,0 @@
@model LightTube.Contexts.BaseContext
@{
ViewBag.Metadata = new Dictionary<string, string>
{
["og:title"] = "LightTube",
["og:url"] = $"{Url.ActionContext.HttpContext.Request.Scheme}://{Url.ActionContext.HttpContext.Request.Host}{Url.ActionContext.HttpContext.Request.Path}{Url.ActionContext.HttpContext.Request.QueryString}",
["og:description"] = "An alternative, privacy respecting front end for YouTube",
};
ViewData["Title"] = "Home Page";
ViewData["SelectedGuideItem"] = "home";
}
<div style="text-align: center">
<h1>@Configuration.Instance.Interface.MessageOfTheDay</h1>
</div>

View File

@ -1,17 +0,0 @@
@model LightTube.Contexts.ErrorContext
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
<p>
You can try other alternatives to access this resource such as:
<b>
<a href="https://invidio.us@($"{Model.Path}{Context.Request.QueryString}")">Invidious</a>
</b>
or
<b>
<a href="https://youtube.com@($"{Model.Path}{Context.Request.QueryString}")">YouTube</a>
</b>
</p>

View File

@ -1,139 +0,0 @@
@using System.Web
@using LightTube.Contexts
@model LightTube.Contexts.BaseContext
@{
bool compatibility = false;
if (Context.Request.Cookies.TryGetValue("compatibility", out string compatibilityString))
bool.TryParse(compatibilityString, out compatibility);
bool minMode = false;
if (Context.Request.Cookies.TryGetValue("minMode", out string minModeString))
bool.TryParse(minModeString, out minMode);
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta property="og:site_name" content="lighttube" />
<meta property="og:type" content="website" />
@if (ViewBag.Metadata is not null)
{
@foreach (KeyValuePair<string, string> metaTag in ViewBag.Metadata)
{
if (metaTag.Key.StartsWith("og:"))
{
<meta property="@metaTag.Key" content="@metaTag.Value"/>
}
else
{
<meta name="@metaTag.Key" content="@metaTag.Value"/>
}
}
}
<meta property="theme-color" content="#AA0000" />
<title>@ViewData["Title"] - lighttube</title>
@if ((ViewData["HideGuide"] ?? false).Equals(true))
{
<style> .guide { display: none !important; } </style>
}
@{
Context.Request.Cookies.TryGetValue("theme", out string theme);
if (!new[] { "light", "dark" }.Contains(theme)) theme = "light";
}
<link rel="stylesheet" href="@($"~/css/colors-{theme}.css")" asp-append-version="true"/>
@if (Model.MobileLayout)
{
<link rel="stylesheet" href="~/css/mobile.css" asp-append-version="true"/>
<link rel="stylesheet" href="~/css/lt-video/player-mobile.css" asp-append-version="true"/>
}
else
{
<link rel="stylesheet" href="~/css/desktop.css" asp-append-version="true"/>
<link rel="stylesheet" href="~/css/lt-video/player-desktop.css" asp-append-version="true"/>
}
<link rel="stylesheet" href="~/css/bootstrap-icons/bootstrap-icons.css" asp-append-version="true"/>
<link rel="icon" href="~/favicon.ico">
</head>
<body>
<div class="top-bar @(ViewData["UseFullSizeSearchBar"]?.Equals(true) ?? false ? "full-size-search" : "")">
<a class="logo" href="/">light<b>tube</b></a>
<div class="divider"></div>
<form action="/results">
<input type="text" placeholder="Search" name="search_query" value="@(Model is SearchContext ctx ? ctx.Query : Context.Request.Cookies.TryGetValue("search_query", out string s) ? s : "")">
<input type="submit" value="Search">
</form>
<div class="divider"></div>
<div class="search-button">
<a class="icon-link" href="/results">
<i class="bi bi-search"></i>
</a>
</div>
<div class="account" tabindex="-1">
<a class="icon-link" href="/Account">
<i class="bi bi-person-circle"></i>
</a>
<div class="account-menu">
@Html.Partial("_LoginLogoutPartial")
<div class="guide-item"><a href="/toggles/theme?redirectUrl=@(HttpUtility.UrlEncode($"{Context.Request.Path}{Context.Request.QueryString}"))">Toggle Theme</a></div>
</div>
</div>
</div>
<div class="guide @(minMode ? "minmode" : "")">
<div class="guide-item @(ViewData["SelectedGuideItem"] as string == "home" ? "active" : "")">
<a href="/">
<i class="icon bi bi-house-door"></i>
Home
</a>
</div>
<div class="guide-item @(ViewData["SelectedGuideItem"] as string == "explore" ? "active" : "")">
<a href="/feed/explore">
<i class="icon bi bi-compass"></i>
Explore
</a>
</div>
<div class="guide-item @(ViewData["SelectedGuideItem"] as string == "subs" ? "active" : "")">
<a href="/feed/subscriptions">
<i class="icon bi bi-inboxes"></i>
Subscriptions
</a>
</div>
<div class="guide-item @(ViewData["SelectedGuideItem"] as string == "library" ? "active" : "")">
<a href="/feed/library">
<i class="icon bi bi-list-ul"></i>
Library
</a>
</div>
<div class="hide-on-minmode guide-item">
<a href="/toggles/collapse_guide?redirectUrl=@(HttpUtility.UrlEncode($"{Context.Request.Path}{Context.Request.QueryString}"))">
<i class="icon"><i class="bi bi-arrow-left-square"></i></i>
Collapse Guide
</a>
</div>
<div class="show-on-minmode guide-item">
<a href="/toggles/collapse_guide?redirectUrl=@(HttpUtility.UrlEncode($"{Context.Request.Path}{Context.Request.QueryString}"))">
<i class="icon"><i class="bi bi-arrow-right-square"></i></i>
Expand
</a>
</div>
<hr class="hide-on-minmode">
<p class="hide-on-minmode">
<a href="https://gitlab.com/kuylar/lighttube/-/blob/master/README.md">About</a><br>
<a href="https://gitlab.com/kuylar/lighttube/-/blob/master/OTHERLIBS.md">How LightTube works</a><br>
<a href="https://gitlab.com/kuylar/lighttube">Source code</a>
<a href="https://gitlab.com/kuylar/lighttube/-/wikis/XML-API">API</a>
<a href="https://gitlab.com/kuylar/lighttube/-/blob/master/LICENSE">License</a><br>
<span style="font-weight: normal">Running on LightTube v@(Utils.GetVersion())</span>
</p>
</div>
<div class="app">
@RenderBody()
</div>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>

View File

@ -1,16 +0,0 @@
@using LightTube.Database
@if (Context.TryGetUser(out LTUser user, "web"))
{
<div class="guide-item"><a>@user.UserID.Split("@")[0]</a></div>
@if (user.PasswordHash != "local_account")
{
<div class="guide-item"><a href="/Account/Logins">Active logins</a></div>
}
<div class="guide-item"><a href="/Account/Logout">Log out</a></div>
}
else
{
<div class="guide-item"><a href="/Account/Login">Log in</a></div>
<div class="guide-item"><a href="/Account/Register">Register</a></div>
}
<div class="guide-item"><a href="/Account/Settings">Settings</a></div>

View File

@ -1,80 +0,0 @@
@using InnerTube.Models
@using System.Web
@model LightTube.Contexts.ChannelContext
@{
ViewBag.Metadata = new Dictionary<string, string>();
ViewBag.Metadata["og:title"] = Model.Channel.Name;
ViewBag.Metadata["og:url"] = $"{Url.ActionContext.HttpContext.Request.Scheme}://{Url.ActionContext.HttpContext.Request.Host}{Url.ActionContext.HttpContext.Request.Path}{Url.ActionContext.HttpContext.Request.QueryString}";
ViewBag.Metadata["og:image"] = $"{Url.ActionContext.HttpContext.Request.Scheme}://{Url.ActionContext.HttpContext.Request.Host}/proxy/image?url={HttpUtility.UrlEncode(Model.Channel.Avatars.FirstOrDefault()?.Url?.ToString())}";
ViewBag.Metadata["twitter:card"] = $"{Url.ActionContext.HttpContext.Request.Scheme}://{Url.ActionContext.HttpContext.Request.Host}/proxy/image?url={HttpUtility.UrlEncode(Model.Channel.Avatars.LastOrDefault()?.Url?.ToString())}";
ViewBag.Metadata["og:description"] = Model.Channel.Description;
ViewBag.Title = Model.Channel.Name;
Layout = "_Layout";
DynamicItem[] contents;
try
{
contents = ((ItemSectionItem)((ItemSectionItem)Model.Channel.Videos[0]).Contents[0]).Contents;
}
catch
{
contents = Model.Channel.Videos;
}
}
<div class="channel-page">
@if (Model.Channel.Banners.Length > 0)
{
<img class="channel-banner" alt="Channel Banner" src="@Model.Channel.Banners.Last().Url">
}
<div class="channel-info-container">
<div class="channel-info">
<a href="/channel/@Model.Channel.Id" class="avatar">
<img src="@Model.Channel.Avatars.LastOrDefault()?.Url" alt="Channel Avatar">
</a>
<div class="name">
<a>@Model.Channel.Name</a>
<span>@Model.Channel.Subscribers</span>
</div>
<button class="subscribe-button" data-cid="@Model.Channel.Id">Subscribe</button>
</div>
</div>
<h3>About</h3>
<p>@Html.Raw(Model.Channel.GetHtmlDescription())</p>
<br><br>
<h3>Uploads</h3>
<div class="video-grid">
@foreach (VideoItem video in contents.Where(x => x is VideoItem).Cast<VideoItem>())
{
<a href="/watch?v=@video.Id" class="video">
<div class="thumbnail" style="background-image: url('@video.Thumbnails.LastOrDefault()?.Url')"><span class="video-length">@video.Duration</span></div>
<div class="info">
<span class="title max-lines-2">@video.Title</span>
<div>
<div>
<span>@video.Views views</span>
<span>@video.UploadedAt</span>
</div>
</div>
</div>
</a>
}
</div>
<div class="pagination-buttons">
@if (!string.IsNullOrWhiteSpace(Model.ContinuationToken))
{
<a href="/channel?id=@Model.Id">First Page</a>
}
<div class="divider"></div>
<span>•</span>
<div class="divider"></div>
@if (!string.IsNullOrWhiteSpace(contents.FirstOrDefault(x => x is ContinuationItem)?.Id))
{
<a href="/channel/@Model.Id?continuation=@(contents.FirstOrDefault(x => x is ContinuationItem)?.Id)">Next Page</a>
}
</div>
</div>

View File

@ -1,95 +0,0 @@
@using System.Web
@using InnerTube
@using InnerTube.Models
@model LightTube.Contexts.PlayerContext
@{
ViewBag.Metadata = new Dictionary<string, string>();
ViewBag.Metadata["author"] = Model.Video.Channel.Name;
ViewBag.Metadata["og:title"] = Model.Player.Title;
ViewBag.Metadata["og:url"] = $"{Url.ActionContext.HttpContext.Request.Scheme}://{Url.ActionContext.HttpContext.Request.Host}{Url.ActionContext.HttpContext.Request.Path}{Url.ActionContext.HttpContext.Request.QueryString}";
ViewBag.Metadata["og:image"] = $"{Url.ActionContext.HttpContext.Request.Scheme}://{Url.ActionContext.HttpContext.Request.Host}/proxy/image?url={HttpUtility.UrlEncode(Model.Player.Thumbnails.FirstOrDefault()?.Url?.ToString())}";
ViewBag.Metadata["twitter:card"] = $"{Url.ActionContext.HttpContext.Request.Scheme}://{Url.ActionContext.HttpContext.Request.Host}/proxy/image?url={HttpUtility.UrlEncode(Model.Player.Thumbnails.LastOrDefault()?.Url?.ToString())}";
ViewBag.Title = Model.Player.Title;
Layout = "_Layout";
}
<div class="playlist-page">
<div class="playlist-info">
<div class="thumbnail" style="background-image: url('@Model.Player.Thumbnails.Last().Url')">
<a href="/watch?v=@Model.Player.Id">Watch</a>
</div>
<p class="title">@Model.Player.Title</p>
<span class="info">@Model.Video.Views • @Model.Video.UploadDate</span>
<div class="channel-info">
<a href="/channel/@Model.Player.Channel.Id" class="avatar">
<img src="@Model.Player.Channel.Avatars.LastOrDefault()?.Url">
</a>
<div class="name">
<a class="name" href="/channel/@Model.Player.Channel.Id">@Model.Player.Channel.Name</a>
</div>
</div>
</div>
<div class="video-list download-list playlist-video-list">
<div class="format-list">
<h2>Muxed formats</h2>
<p>These downloads have both video and audio in them</p>
@foreach (Format format in Model.Player.Formats)
{
<div class="download-format">
<div>
@format.FormatNote
</div>
<a href="/proxy/download/@Model.Video.Id/@format.FormatId/@(HttpUtility.UrlEncode(Model.Video.Title)).@format.GetExtension()">
<i class="bi bi-download"></i>
Download through LightTube
</a>
<a href="@format.Url">
<i class="bi bi-cloud-download"></i>
Download through YouTube
</a>
</div>
}
</div>
<div class="format-list">
<h2>Audio only formats</h2>
<p>These downloads have only have audio in them</p>
@foreach (Format format in Model.Player.AdaptiveFormats.Where(x => x.VideoCodec == "none"))
{
<div class="download-format">
<div>
@format.FormatNote (Codec: @format.AudioCodec, Sample Rate: @format.AudioSampleRate)
</div>
<a href="/proxy/download/@Model.Video.Id/@format.FormatId/@(HttpUtility.UrlEncode(Model.Video.Title)).@format.GetExtension()">
<i class="bi bi-download"></i>
Download through LightTube
</a>
<a href="@format.Url">
<i class="bi bi-cloud-download"></i>
Download through YouTube
</a>
</div>
}
</div>
<div class="format-list">
<h2>Video only formats</h2>
<p>These downloads have only have video in them</p>
@foreach (Format format in Model.Player.AdaptiveFormats.Where(x => x.AudioCodec == "none"))
{
<div class="download-format">
<div>
@format.FormatNote (Codec: @format.VideoCodec)
</div>
<a href="/proxy/download/@Model.Video.Id/@format.FormatId/@(HttpUtility.UrlEncode(Model.Video.Title)).@format.GetExtension()">
<i class="bi bi-download"></i>
Download through LightTube
</a>
<a href="@format.Url">
<i class="bi bi-cloud-download"></i>
Download through YouTube
</a>
</div>
}
</div>
</div>
</div>

View File

@ -1,146 +0,0 @@
@using System.Collections.Specialized
@using System.Web
@using InnerTube.Models
@model LightTube.Contexts.PlayerContext
@{
ViewBag.Metadata = new Dictionary<string, string>();
ViewBag.Metadata["author"] = Model.Video.Channel.Name;
ViewBag.Metadata["og:title"] = Model.Player.Title;
ViewBag.Metadata["og:url"] = $"{Url.ActionContext.HttpContext.Request.Scheme}://{Url.ActionContext.HttpContext.Request.Host}{Url.ActionContext.HttpContext.Request.Path}{Url.ActionContext.HttpContext.Request.QueryString}";
ViewBag.Metadata["og:image"] = $"{Url.ActionContext.HttpContext.Request.Scheme}://{Url.ActionContext.HttpContext.Request.Host}/proxy/image?url={HttpUtility.UrlEncode(Model.Player.Thumbnails.FirstOrDefault()?.Url?.ToString())}";
ViewBag.Metadata["twitter:card"] = $"{Url.ActionContext.HttpContext.Request.Scheme}://{Url.ActionContext.HttpContext.Request.Host}/proxy/image?url={HttpUtility.UrlEncode(Model.Player.Thumbnails.LastOrDefault()?.Url?.ToString())}";
ViewBag.Metadata["og:description"] = Model.Player.Description;
ViewBag.Title = Model.Player.Title;
Layout = null;
try
{
ViewBag.Metadata["og:video"] = $"/proxy/video?url={HttpUtility.UrlEncode(Model.Player.Formats.First().Url.ToString())}";
Model.Resolution ??= Model.Player.Formats.First().FormatNote;
}
catch
{
}
bool live = Model.Player.Formats.Length == 0 && Model.Player.AdaptiveFormats.Length > 0;
bool canPlay = true;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta property="og:site_name" content="lighttube"/>
<meta property="og:type" content="website"/>
@if (ViewBag.Metadata is not null)
{
@foreach (KeyValuePair<string, string> metaTag in ViewBag.Metadata)
{
if (metaTag.Key.StartsWith("og:"))
{
<meta property="@metaTag.Key" content="@metaTag.Value"/>
}
else
{
<meta name="@metaTag.Key" content="@metaTag.Value"/>
}
}
}
<meta property="theme-color" content="#AA0000"/>
<title>@ViewData["Title"] - lighttube</title>
<link rel="stylesheet" href="~/css/bootstrap-icons/bootstrap-icons.css"/>
<link rel="stylesheet" href="~/css/desktop.css" asp-append-version="true"/>
<link rel="stylesheet" href="~/css/lt-video/player-desktop.css" asp-append-version="true"/>
<link rel="icon" href="~/favicon.ico">
</head>
<body>
@if (live)
{
<video class="player" poster="@Model.Player.Thumbnails.LastOrDefault()?.Url">
</video>
}
else if (Model.Player.Formats.Length > 0)
{
<video class="player" controls src="/proxy/media/@Model.Player.Id/@HttpUtility.UrlEncode(Model.Player.Formats.First(x => x.FormatNote == Model.Resolution && x.FormatId != "17").FormatId)" poster="@Model.Player.Thumbnails.LastOrDefault()?.Url">
@foreach (Subtitle subtitle in Model.Player.Subtitles ?? Array.Empty<Subtitle>())
{
@:<track src="/proxy/caption/@Model.Player.Id/@HttpUtility.UrlEncode(subtitle.Language).Replace("+", "%20")" label="@subtitle.Language" kind="subtitles">
}
</video>
}
else
{
canPlay = false;
<div id="player" class="player error" style="background-image: url('@Model.Player.Thumbnails.LastOrDefault()?.Url')">
@if (string.IsNullOrWhiteSpace(Model.Player.ErrorMessage))
{
<span>
No playable streams returned from the API (@Model.Player.Formats.Length/@Model.Player.AdaptiveFormats.Length)
</span>
}
else
{
<span>
@Model.Player.ErrorMessage
</span>
}
</div>
}
@if (canPlay)
{
<script src="/js/lt-video/player-desktop.js"></script>
@if (!Model.CompatibilityMode && !live)
{
<script src="/js/shaka-player/shaka-player.compiled.min.js"></script>
<script>
let player = undefined;
loadPlayerWithShaka("video", {
"id": "@Model.Video.Id",
"title": "@Html.Raw(Model.Video.Title.Replace("\"", "\\\""))",
"embed": true,
"live": false,
"storyboard": "/proxy/image?url=@HttpUtility.UrlEncode(Model.Player.Storyboards.FirstOrDefault())"
}, [
@foreach(Format f in Model.Player.Formats.Reverse())
{
@:{"height": @f.Resolution.Split("x")[1],"label":"@f.FormatName","src": "/proxy/video?url=@HttpUtility.UrlEncode(f.Url)"},
}
], "https://@(Context.Request.Host)/manifest/@(Model.Video.Id).mpd").then(x => player = x).catch(alert);;
</script>
}
else if (live)
{
<script src="/js/hls.js/hls.min.js"></script>
<script>
let player = undefined;
loadPlayerWithHls("video", {
"id": "@(Model.Video.Id)",
"title": "@Html.Raw(Model.Video.Title.Replace("\"", "\\\""))",
"embed": true,
"live": true
}, "https://@(Context.Request.Host)/manifest/@(Model.Video.Id).m3u8").then(x => player = x).catch(alert);
</script>
}
else
{
<script>
const player = new Player("video", {
"id": "@Model.Video.Id",
"title": "@Html.Raw(Model.Video.Title.Replace("\"", "\\\""))",
"embed": true,
"live": false,
"storyboard": "/proxy/image?url=@HttpUtility.UrlEncode(Model.Player.Storyboards.FirstOrDefault())"
}, [
@foreach(Format f in Model.Player.Formats.Reverse())
{
@:{"height": @f.Resolution.Split("x")[1],"label":"@f.FormatName","src": "/proxy/video?url=@HttpUtility.UrlEncode(f.Url)"},
}
]);
</script>
}
}
</body>
</html>

View File

@ -1,85 +0,0 @@
@using InnerTube.Models
@using System.Web
@model LightTube.Contexts.PlaylistContext
@{
ViewBag.Title = Model.Playlist.Title;
ViewBag.Metadata = new Dictionary<string, string>();
ViewBag.Metadata["og:title"] = Model.Playlist.Title;
ViewBag.Metadata["og:url"] = $"{Url.ActionContext.HttpContext.Request.Scheme}://{Url.ActionContext.HttpContext.Request.Host}{Url.ActionContext.HttpContext.Request.Path}{Url.ActionContext.HttpContext.Request.QueryString}";
ViewBag.Metadata["og:image"] = $"{Url.ActionContext.HttpContext.Request.Scheme}://{Url.ActionContext.HttpContext.Request.Host}/proxy/image?url={HttpUtility.UrlEncode(Model.Playlist.Thumbnail.FirstOrDefault()?.Url?.ToString())}";
ViewBag.Metadata["twitter:card"] = $"{Url.ActionContext.HttpContext.Request.Scheme}://{Url.ActionContext.HttpContext.Request.Host}/proxy/image?url={HttpUtility.UrlEncode(Model.Playlist.Thumbnail.LastOrDefault()?.Url?.ToString())}";
ViewBag.Metadata["og:description"] = Model.Playlist.Description;
Layout = "_Layout";
}
@if (!string.IsNullOrWhiteSpace(Model.Message))
{
<div class="playlist-message" style="padding: 16px;background-color: var(--border-color); color: var(--text-primary);">
@Model.Message
</div>
}
<div class="playlist-page">
<div class="playlist-info">
<div class="thumbnail" style="background-image: url('@Model.Playlist.Thumbnail.LastOrDefault()?.Url')">
<a href="/watch?v=@Model.Playlist.Videos.FirstOrDefault()?.Id&list=@Model.Id">Play all</a>
</div>
<p class="title">@Model.Playlist.Title</p>
<span class="info">@Model.Playlist.VideoCount videos • @Model.Playlist.ViewCount views • @Model.Playlist.LastUpdated</span>
<span class="description">@Html.Raw(Model.Playlist.GetHtmlDescription())</span>
<a href="/playlist?list=@Model.Id&remove=true" class="login-button" style="margin:unset;">
<i class="bi bi-trash"></i>
Delete playlist
</a>
<div class="channel-info">
<a href="/channel/@Model.Playlist.Channel.Id" class="avatar">
<img src="@Model.Playlist.Channel.Avatars.LastOrDefault()?.Url">
</a>
<div class="name">
<a class="name" href="/channel/@Model.Playlist.Channel.Id">@Model.Playlist.Channel.Name</a>
</div>
<button class="subscribe-button" data-cid="@Model.Playlist.Channel.Id">Subscribe</button>
</div>
</div>
<div class="video-list playlist-video-list">
@foreach (PlaylistVideoItem video in Model.Playlist.Videos.Cast<PlaylistVideoItem>())
{
<div class="playlist-video">
<a href="/watch?v=@video.Id&list=@Model.Id" class="index">
@video.Index
</a>
<a href="/watch?v=@video.Id&list=@Model.Id" class="thumbnail"
style="background-image: url('@video.Thumbnails.LastOrDefault()?.Url')">
<span class="video-length">@video.Duration</span>
</a>
<div class="info">
<a href="/watch?v=@video.Id&list=@Model.Id" class="title max-lines-2">
@video.Title
</a>
<div>
<a href="/channel/@video.Channel.Name">@video.Channel.Name</a>
</div>
</div>
@if (Model.Editable)
{
<a href="/playlist?list=@Model.Id&delete=@(video.Index - 1)" class="edit">
<i class="bi bi-trash"></i>
</a>
}
</div>
}
</div>
</div>
<div class="pagination-buttons">
@if (!string.IsNullOrWhiteSpace(Model.ContinuationToken))
{
<a href="/playlist?list=@Model.Id">First Page</a>
}
<div class="divider"></div>
<span>•</span>
<div class="divider"></div>
@if (!string.IsNullOrWhiteSpace(Model.Playlist.ContinuationKey))
{
<a href="/playlist?list=@Model.Id&continuation=@Model.Playlist.ContinuationKey">Next Page</a>
}
</div>

View File

@ -1,28 +0,0 @@
@using InnerTube.Models
@model LightTube.Contexts.SearchContext
@{
ViewBag.Title = Model.Query;
Layout = "_Layout";
ViewData["UseFullSizeSearchBar"] = Model.MobileLayout;
}
<div class="video-list">
@foreach (DynamicItem preview in Model.Results.Results)
{
@preview.GetHtml()
}
</div>
<div class="pagination-buttons">
@if (!string.IsNullOrWhiteSpace(Model.ContinuationKey))
{
<a href="/results?search_query=@Model.Query">First Page</a>
}
<div class="divider"></div>
<span>•</span>
<div class="divider"></div>
@if (!string.IsNullOrWhiteSpace(Model.Results.ContinuationKey))
{
<a href="/results?search_query=@Model.Query&continuation=@Model.Results.ContinuationKey">Next Page</a>
}
</div>

View File

@ -1,325 +0,0 @@
@using System.Text.RegularExpressions
@using System.Web
@using InnerTube.Models
@model LightTube.Contexts.PlayerContext
@{
bool compatibility = false;
if (Context.Request.Cookies.TryGetValue("compatibility", out string compatibilityString))
bool.TryParse(compatibilityString, out compatibility);
ViewBag.Metadata = new Dictionary<string, string>();
ViewBag.Metadata["author"] = Model.Video.Channel.Name;
ViewBag.Metadata["og:title"] = Model.Player.Title;
ViewBag.Metadata["og:url"] = $"{Url.ActionContext.HttpContext.Request.Scheme}://{Url.ActionContext.HttpContext.Request.Host}{Url.ActionContext.HttpContext.Request.Path}{Url.ActionContext.HttpContext.Request.QueryString}";
ViewBag.Metadata["og:image"] = $"{Url.ActionContext.HttpContext.Request.Scheme}://{Url.ActionContext.HttpContext.Request.Host}/proxy/image?url={HttpUtility.UrlEncode(Model.Player.Thumbnails.FirstOrDefault()?.Url?.ToString())}";
ViewBag.Metadata["twitter:card"] = $"{Url.ActionContext.HttpContext.Request.Scheme}://{Url.ActionContext.HttpContext.Request.Host}/proxy/image?url={HttpUtility.UrlEncode(Model.Player.Thumbnails.LastOrDefault()?.Url?.ToString())}";
ViewBag.Metadata["og:description"] = Model.Player.Description;
ViewBag.Title = Model.Player.Title;
Layout = "_Layout";
try
{
ViewBag.Metadata["og:video"] = $"/proxy/video?url={HttpUtility.UrlEncode(Model.Player.Formats.First().Url.ToString())}";
Model.Resolution ??= Model.Player.Formats.First().FormatNote;
}
catch
{
}
ViewData["HideGuide"] = true;
bool live = Model.Player.Formats.Length == 0 && Model.Player.AdaptiveFormats.Length > 0;
string description = Model.Video.GetHtmlDescription();
const string youtubePattern = @"[w.]*youtube[-nockie]*\.com";
// turn URLs into hyperlinks
Regex urlRegex = new(youtubePattern, RegexOptions.IgnoreCase);
Match m;
for (m = urlRegex.Match(description); m.Success; m = m.NextMatch())
description = description.Replace(m.Groups[0].ToString(),
$"{Url.ActionContext.HttpContext.Request.Host}");
bool canPlay = true;
}
<!-- TODO: chapters -->
<div class="watch-page">
<div class="primary">
<div class="video-player-container">
@if (live)
{
<video class="player" poster="@Model.Player.Thumbnails.LastOrDefault()?.Url">
</video>
}
else if (Model.Player.Formats.Length > 0)
{
<video class="player" controls src="/proxy/media/@Model.Player.Id/@HttpUtility.UrlEncode(Model.Player.Formats.First(x => x.FormatNote == Model.Resolution && x.FormatId != "17").FormatId)" poster="@Model.Player.Thumbnails.LastOrDefault()?.Url">
@foreach (Subtitle subtitle in Model.Player.Subtitles ?? Array.Empty<Subtitle>())
{
@:<track src="/proxy/caption/@Model.Player.Id/@HttpUtility.UrlEncode(subtitle.Language).Replace("+", "%20")" label="@subtitle.Language" kind="subtitles">
}
</video>
}
else
{
canPlay = false;
<div id="player" class="player error" style="background-image: url('@Model.Player.Thumbnails.LastOrDefault()?.Url')">
@if (string.IsNullOrWhiteSpace(Model.Player.ErrorMessage))
{
<span>
No playable streams returned from the API (@Model.Player.Formats.Length/@Model.Player.AdaptiveFormats.Length)
</span>
}
else
{
<span>
@Model.Player.ErrorMessage
</span>
}
</div>
}
</div>
@if (Model.MobileLayout)
{
<div class="video-info">
<div class="video-title">@Model.Video.Title</div>
<div class="video-info-bar">
<span>@Model.Video.Views</span>
<span>Published @Model.Video.UploadDate</span>
<div class="divider"></div>
<div class="video-info-buttons">
<div>
<i class="bi bi-hand-thumbs-up"></i><span>@Model.Engagement.Likes</span>
</div>
<div>
<i class="bi bi-hand-thumbs-down"></i><span>@Model.Engagement.Dislikes</span>
</div>
<a href="/download?v=@Model.Video.Id">
<i class="bi bi-download"></i>
Download
</a>
<a href="/Account/AddVideoToPlaylist?v=@Model.Video.Id">
<i class="bi bi-folder-plus"></i>
Save
</a>
<a href="https://www.youtube.com/watch?v=@Model.Video.Id">
<i class="bi bi-share"></i>
YouTube link
</a>
</div>
</div>
<div class="channel-info">
<a href="/channel/@Model.Video.Channel.Id" class="avatar">
<img src="@Model.Video.Channel.Avatars.LastOrDefault()?.Url">
</a>
<div class="name">
<a href="/channel/@Model.Video.Channel.Id">@Model.Video.Channel.Name</a>
</div>
<button class="subscribe-button" data-cid="@Model.Video.Channel.Id">Subscribe</button>
</div>
<p class="description">@Html.Raw(description)</p>
</div>
<hr>
}
else
{
<div class="video-info">
<div class="video-title">@Model.Video.Title</div>
<p class="video-sub-info description">
<span>@Model.Video.Views&nbsp; @Model.Video.UploadDate</span>&nbsp; @Html.Raw(description)
</p>
<div class="video-info-buttons">
<div>
<i class="bi bi-hand-thumbs-up"></i>
@Model.Engagement.Likes
</div>
<div>
<i class="bi bi-hand-thumbs-down"></i>
@Model.Engagement.Dislikes
</div>
<a href="/download?v=@Model.Player.Id">
<i class="bi bi-download"></i>
Download
</a>
<a href="/Account/AddVideoToPlaylist?v=@Model.Video.Id">
<i class="bi bi-folder-plus"></i>
Save
</a>
<a href="https://www.youtube.com/watch?v=@Model.Video.Id">
<i class="bi bi-share"></i>
YouTube link
</a>
</div>
</div>
<div class="channel-info__bordered">
<a href="/channel/@Model.Video.Channel.Id" class="avatar">
<img src="@Model.Video.Channel.Avatars.FirstOrDefault()?.Url">
</a>
<div class="name">
<a href="/channel/@Model.Video.Channel.Id">@Model.Video.Channel.Name</a>
</div>
<div class="subscriber-count">
@Model.Video.Channel.SubscriberCount
</div>
<button class="subscribe-button" data-cid="@Model.Video.Channel.Id">Subscribe</button>
</div>
}
</div>
<div class="secondary">
<noscript>
<div class="resolutions-list">
<h3>Change Resolution</h3>
<div>
@foreach (Format format in Model.Player.Formats.Where(x => x.FormatId != "17"))
{
@if (format.FormatNote == Model.Resolution)
{
<b>@format.FormatNote (current)</b>
}
else
{
<a href="/watch?v=@Model.Player.Id&quality=@format.FormatNote">@format.FormatNote</a>
}
}
</div>
</div>
</noscript>
<div class="recommended-list">
@if (Model.Video.Recommended.Length == 0)
{
<p style="text-align: center">None :(<br>This is most likely an age-restricted video</p>
}
@foreach (DynamicItem recommendation in Model.Video.Recommended)
{
switch (recommendation)
{
case VideoItem video:
<div class="video">
<a href="/watch?v=@video.Id" class="thumbnail" style="background-image: url('@video.Thumbnails.LastOrDefault()?.Url')">
<span class="video-length">@video.Duration</span>
</a>
<div class="info">
<a href="/watch?v=@video.Id" class="title max-lines-2">@video.Title</a>
<div>
<a href="/channel/@video.Channel.Id" class="max-lines-1">@video.Channel.Name</a>
<div>
<span>@video.Views views</span>
<span>•</span>
<span>@video.UploadedAt</span>
</div>
</div>
</div>
</div>
break;
case PlaylistItem playlist:
<div class="playlist">
<a href="/watch?v=@playlist.FirstVideoId&list=@playlist.Id" class="thumbnail" style="background-image: url('@playlist.Thumbnails.LastOrDefault()?.Url')">
<div>
<span>@playlist.VideoCount</span>
<span>VIDEOS</span>
</div>
</a>
<div class="info">
<a href="/watch?v=@playlist.FirstVideoId&list=@playlist.Id" class="title max-lines-2">@playlist.Title</a>
<div>
<a href="/channel/@playlist.Channel.Id">@playlist.Channel.Name</a>
</div>
</div>
</div>
break;
case RadioItem radio:
<div class="playlist">
<a href="/watch?v=@radio.FirstVideoId&list=@radio.Id" class="thumbnail" style="background-image: url('@radio.Thumbnails.LastOrDefault()?.Url')">
<div>
<span>MIX</span>
</div>
</a>
<div class="info">
<a href="/watch?v=@radio.FirstVideoId&list=@radio.Id" class="title max-lines-2">@radio.Title</a>
<div>
<span>@radio.Channel.Name</span>
</div>
</div>
</div>
break;
case ContinuationItem continuationItem:
break;
default:
<div class="video">
<div class="thumbnail" style="background-image: url('@recommendation.Thumbnails?.LastOrDefault()?.Url')"></div>
<div class="info">
<span class="title max-lines-2">@recommendation.GetType().Name</span>
<div>
<b>WARNING:</b> Unknown recommendation type: @recommendation.Id
</div>
</div>
</div>
break;
}
}
</div>
</div>
</div>
@if (canPlay)
{
@if (Model.MobileLayout)
{
<script src="/js/lt-video/player-mobile.js"></script>
}
else
{
<script src="/js/lt-video/player-desktop.js"></script>
}
@if (!Model.CompatibilityMode && !live)
{
<script src="/js/shaka-player/shaka-player.compiled.min.js"></script>
<script>
let player = undefined;
loadPlayerWithShaka("video", {
"id": "@Model.Video.Id",
"title": "@Html.Raw(Model.Video.Title.Replace("\"", "\\\""))",
"embed": false,
"live": false,
"storyboard": "/proxy/image?url=@HttpUtility.UrlEncode(Model.Player.Storyboards.FirstOrDefault())"
}, [
@foreach (Format f in Model.Player.Formats.Reverse())
{
@:{"height": @f.Resolution.Split("x")[1],"label":"@f.FormatName","src": "/proxy/video?url=@HttpUtility.UrlEncode(f.Url)"},
}
], "https://@(Context.Request.Host)/manifest/@(Model.Video.Id).mpd").then(x => player = x).catch(alert);
</script>
}
else if (live)
{
<script src="/js/hls.js/hls.min.js"></script>
<script>
let player = undefined;
loadPlayerWithHls("video", {
"id": "@(Model.Video.Id)",
"title": "@Html.Raw(Model.Video.Title.Replace("\"", "\\\""))",
"embed": false,
"live": true
}, "https://@(Context.Request.Host)/manifest/@(Model.Video.Id).m3u8").then(x => player = x).catch(alert);
</script>
}
else
{
<script>
const player = new Player("video", {
"id": "@Model.Video.Id",
"title": "@Html.Raw(Model.Video.Title.Replace("\"", "\\\""))",
"embed": false,
"live": false,
"storyboard": "/proxy/image?url=@HttpUtility.UrlEncode(Model.Player.Storyboards.FirstOrDefault())"
}, [
@foreach (Format f in Model.Player.Formats.Reverse())
{
@:{"height": @f.Resolution.Split("x")[1],"label":"@f.FormatName","src": "/proxy/media/@(Model.Player.Id)/@(f.FormatId)"},
}
]);
</script>
}
}

View File

@ -1,3 +0,0 @@
@using LightTube
@using LightTube.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -1,3 +0,0 @@
@{
Layout = "_Layout";
}

View File

@ -1,113 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
namespace LightTube
{
public static class YoutubeRSS
{
private static HttpClient _httpClient = new();
public static async Task<ChannelFeed> GetChannelFeed(string channelId)
{
HttpResponseMessage response =
await _httpClient.GetAsync("https://www.youtube.com/feeds/videos.xml?channel_id=" + channelId);
if (!response.IsSuccessStatusCode)
throw response.StatusCode switch
{
HttpStatusCode.NotFound => new KeyNotFoundException($"Channel '{channelId}' does not exist"),
var _ => new Exception("Failed to fetch RSS feed for channel " + channelId)
};
ChannelFeed feed = new();
string xml = await response.Content.ReadAsStringAsync();
XDocument doc = XDocument.Parse(xml);
feed.Name = doc.Descendants().First(p => p.Name.LocalName == "title").Value;
feed.Id = doc.Descendants().First(p => p.Name.LocalName == "channelId").Value;
feed.Videos = doc.Descendants().Where(p => p.Name.LocalName == "entry").Select(x => new FeedVideo
{
Id = x.Descendants().First(p => p.Name.LocalName == "videoId").Value,
Title = x.Descendants().First(p => p.Name.LocalName == "title").Value,
Description = x.Descendants().First(p => p.Name.LocalName == "description").Value,
ViewCount = long.Parse(x.Descendants().First(p => p.Name.LocalName == "statistics").Attribute("views")?.Value ?? "-1"),
Thumbnail = x.Descendants().First(p => p.Name.LocalName == "thumbnail").Attribute("url")?.Value,
ChannelName = x.Descendants().First(p => p.Name.LocalName == "name").Value,
ChannelId = x.Descendants().First(p => p.Name.LocalName == "channelId").Value,
PublishedDate = DateTimeOffset.Parse(x.Descendants().First(p => p.Name.LocalName == "published").Value)
}).ToArray();
return feed;
}
public static async Task<FeedVideo[]> GetMultipleFeeds(IEnumerable<string> channelIds)
{
Task<ChannelFeed>[] feeds = channelIds.Select(YoutubeRSS.GetChannelFeed).ToArray();
await Task.WhenAll(feeds);
List<FeedVideo> videos = new();
foreach (ChannelFeed feed in feeds.Select(x => x.Result)) videos.AddRange(feed.Videos);
videos.Sort((a, b) => DateTimeOffset.Compare(b.PublishedDate, a.PublishedDate));
return videos.ToArray();
}
}
public class ChannelFeed
{
public string Name;
public string Id;
public FeedVideo[] Videos;
}
public class FeedVideo
{
public string Id;
public string Title;
public string Description;
public long ViewCount;
public string Thumbnail;
public string ChannelName;
public string ChannelId;
public DateTimeOffset PublishedDate;
public XmlElement GetXmlElement(XmlDocument doc)
{
XmlElement item = doc.CreateElement("Video");
item.SetAttribute("id", Id);
item.SetAttribute("views", ViewCount.ToString());
item.SetAttribute("uploadedAt", PublishedDate.ToUnixTimeSeconds().ToString());
XmlElement title = doc.CreateElement("Title");
title.InnerText = Title;
item.AppendChild(title);
XmlElement channel = doc.CreateElement("Channel");
channel.SetAttribute("id", ChannelId);
XmlElement channelTitle = doc.CreateElement("Name");
channelTitle.InnerText = ChannelName;
channel.AppendChild(channelTitle);
item.AppendChild(channel);
XmlElement thumbnail = doc.CreateElement("Thumbnail");
thumbnail.InnerText = Thumbnail;
item.AppendChild(thumbnail);
if (!string.IsNullOrWhiteSpace(Description))
{
XmlElement description = doc.CreateElement("Description");
description.InnerText = Description;
item.AppendChild(description);
}
return item;
}
}
}

View File

@ -1,9 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

View File

@ -1,10 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +0,0 @@
:root {
--text-primary: #fff;
--text-secondary: #808080;
--text-link: #3ea6ff;
--app-background: #181818;
--context-menu-background: #333;
--border-color: #444;
--item-hover-background: #373737;
--item-active-background: #383838;
--top-bar-background: #202020;
--guide-background: #212121;
--thumbnail-background: #252525;
--channel-info-background: #181818;
--channel-contents-background: #0f0f0f;
}

View File

@ -1,19 +0,0 @@
:root {
--text-primary: #000;
--text-secondary: #606060;
--text-link: #3ea6ff;
--app-background: #f9f9f9;
--context-menu-background: #f2f2f2;
--border-color: #c5c5c5;
--item-hover-background: #f2f2f2;
--item-active-background: #E5E5E5;;
--top-bar-background: #FFF;
--guide-background: #FFF;
--thumbnail-background: #CCC;
--channel-info-background: #fff;
--channel-contents-background: #f9f9f9;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,267 +0,0 @@
* {
font-family: sans-serif;
}
.player {
background-color: #000 !important;
display: grid;
grid-template-columns: 1fr min-content;
grid-template-rows: max-content 1fr max-content max-content max-content;
gap: 0 0;
width: 100%;
height: 100%;
}
.player * {
color: #fff;
box-sizing: content-box;
}
.player.embed, video.embed {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.player * {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.player > video {
position: relative;
width: 100%;
height: 100%;
z-index: 0;
grid-area: 1 / 1 / 6 / 3;
}
.player.hide-controls > .player-title,
.player.hide-controls > .player-controls,
.player.hide-controls > .player-playback-bar-container,
.player.hide-controls > .player-menu {
display: none !important;
}
.player-title {
grid-area: 1 / 1 / 2 / 3;
color: white;
z-index: 2;
font-size: 27px;
background-image: linear-gradient(180deg, #0007 0%, #0000 100%);
padding: 8px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
line-clamp: 1;
-webkit-box-orient: vertical;
}
.player-controls {
padding-top: 4px;
color: #ddd !important;
width: 100%;
height: min-content;
position: relative;
bottom: 0;
z-index: 2;
background-image: linear-gradient(0deg, #0007 0%, #0007 80%, #0000 100%);
grid-area: 5 / 1 / 6 / 3;
}
.player-controls {
display: flex;
}
.player-controls > span {
line-height: 48px;
height: 48px;
font-size: 109%;
}
.player-controls-padding {
width: 12px;
}
.player-button {
cursor: pointer;
display: flex;
flex-direction: row;
transition: width ease-in 250ms;
width: 48px;
height: 48px;
font-size: 36px;
text-align: center;
line-height: 48px;
}
.player-button, .player-button * {
color: #dddddd !important;
text-decoration: none;
}
.player-button > i {
min-width: 48px;
}
.player-button:hover, .player-button:hover * {
color: #fff !important;
}
.player-volume {
overflow-x: hidden;
}
.player-volume:hover {
width: 200px;
}
.player-button-divider {
flex-grow: 1;
}
.player-button-menu {
flex-direction: column-reverse;
}
.player-menu {
grid-area: 3 / 2 / 4 / 3;
z-index: 3;
position: relative;
background-color: #000a !important;
width: 200px;
}
.player-menu > div {
overflow-y: scroll;
max-height: 300px;
}
.player-menu-item {
padding: 4px 8px;
height: 2rem;
line-height: 2rem;
color: white;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
line-clamp: 1;
-webkit-box-orient: vertical;
}
.player-menu-item > .bi {
width: 16px;
height: 16px;
margin-right: 8px;
}
.player-menu-item > .bi-::before {
width: 16px;
height: 16px;
content: ""
}
.player-menu-item:hover {
background-color: #fff3 !important;
}
.player-playback-bar {
transition: width linear 100ms;
}
.player-playback-bar-container {
grid-area: 4 / 1 / 5 / 3;
height: 4px;
transition: height linear 100ms;
width: 100%;
z-index: 2;
}
.player-playback-bar-bg {
background-color: #fff3 !important;
width: calc(100% - 24px);
margin: auto;
height: 100%;
z-index: 2;
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr;
}
.player-playback-bar-bg > * {
grid-area: 1 / 1 / 2 / 2;
}
.player-playback-bar-container:hover {
height: 8px;
}
.player-playback-bar-buffer {
background-color: #fffa !important;
height: 100%;
width: 0;
z-index: 3;
}
.player-playback-bar-fg {
background-color: #f00 !important;
height: 100%;
width: 0;
z-index: 4;
}
.player-playback-bar-hover {
width: min-content !important;
padding: 4px;
position: fixed;
color: white;
display: none;
text-align: center;
}
.player-playback-bar-hover > span {
background-color: #000 !important;
padding: 4px;
}
.player-storyboard-image-container {
background-repeat: no-repeat;
display: inline-block;
width: 144px;
height: 81px;
}
.player-storyboard-image {
background-repeat: no-repeat;
display: inline-block;
width: 48px;
height: 27px;
background-position-x: 0;
background-position-y: 0;
transform: scale(3);
position: relative;
box-sizing: content-box;
border: 1px solid white;
top: 10px;
}
.player-buffering {
grid-area: 1 / 1 / 6 / 3;
background-color: #000A;
z-index: 1;
display: flex;
justify-content: center;
align-items: center;
}
.player-buffering-spinner {
width: 80px;
height: 80px;
}

View File

@ -1,153 +0,0 @@
body, html {
margin: 0;
padding: 0;
}
* {
font-family: sans-serif;
}
.player {
background-color: #000 !important;
display: grid;
grid-template-columns: 1fr min-content;
grid-template-rows: max-content 1fr max-content max-content max-content;
gap: 0 0;
width: 100%;
/*
height: 100%;
*/
aspect-ratio: 16 / 9;
}
.player * {
color: #fff;
}
.player.embed, video.embed {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.player * {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.player > video {
position: relative;
width: 100%;
height: 100%;
z-index: 0;
grid-area: 1 / 1 / 6 / 3;
}
.player.hide-controls > .player-title,
.player.hide-controls > .player-controls,
.player.hide-controls > .player-playback-bar-container,
.player.hide-controls > .player-menu {
display: none !important;
}
.player-controls {
background-color: #0007;
display: flex;
justify-content: center;
align-items: center;
z-index: 1;
grid-area: 1 / 1 / 6 / 3;
}
.player-button {
width: 96px;
height: 96px;
font-size: 90px;
text-align: center;
line-height: 48px;
}
.player-tiny-button {
width: 40px;
font-size: 20px;
text-align: center;
}
.player-tiny-button > i {
color: #ddd;
}
.player-button, .player-button * {
color: #dddddd !important;
text-decoration: none;
}
.player-button > i {
min-width: 48px;
}
.player-button:hover, .player-button:hover * {
color: #fff !important;
}
.player-playback-bar {
transition: width linear 100ms;
}
.player-playback-bar-container {
grid-area: 4 / 1 / 5 / 3;
display: flex;
column-gap: 8px;
justify-content: center;
align-items: center;
height: 8px;
transition: height linear 100ms;
width: 100%;
z-index: 2;
margin-bottom: 10px;
}
.player-playback-bar-bg {
background-color: #fff3 !important;
width: 100%;
height: 100%;
z-index: 2;
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr;
}
.player-playback-bar-bg > * {
grid-area: 1 / 1 / 2 / 2;
}
.player-playback-bar-buffer {
background-color: #fffa !important;
height: 100%;
width: 0;
z-index: 3;
}
.player-playback-bar-fg {
background-color: #f00 !important;
height: 100%;
width: 0;
z-index: 4;
}
.player-buffering {
grid-area: 1 / 1 / 6 / 3;
z-index: 1;
display: flex;
justify-content: center;
align-items: center;
}
.player-buffering-spinner {
width: 80px;
height: 80px;
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -1 +0,0 @@
<svg height="21" viewBox="0 0 21 21" width="21" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" transform="translate(3 3)"><path d="m.5 12.5v-10c0-1.1045695.8954305-2 2-2h10c1.1045695 0 2 .8954305 2 2v10c0 1.1045695-.8954305 2-2 2h-10c-1.1045695 0-2-.8954305-2-2z"/><path d="m2.5 12.5v-10c0-1.1045695.8954305-2 2-2h-2c-1 0-2 .8954305-2 2v10c0 1.1045695 1 2 2 2h2c-1.1045695 0-2-.8954305-2-2z" fill="currentColor"/><path d="m7.5 10.5-3-3 3-3"/><path d="m12.5 7.5h-8"/></g></svg>

Before

Width:  |  Height:  |  Size: 568 B

View File

@ -1 +0,0 @@
<svg height="21" viewBox="0 0 21 21" width="21" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" transform="translate(2 2)"><circle cx="8.5" cy="8.5" r="8"/><path d="m10.5 9.5-4 3v-5l4-3z"/></g></svg>

Before

Width:  |  Height:  |  Size: 290 B

View File

@ -1 +0,0 @@
<svg height="21" viewBox="0 0 21 21" width="21" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" transform="matrix(1 0 0 -1 2 18)"><path d="m11.6427217 13.7567397-3.14377399-1.2567396h-4v-7.00000002h2l2.80105246-5.5c.57989907 0 1.07487363.2050252 1.48492373.61507546.4100508.41005058.6150761.90502516.6150755 1.48492425l-.8999994 2.40000029 4.0310597 1.34368655c.9979872.33266243 1.5591794 1.37584131 1.3086286 2.37964122l-.0684258.21997226-1.5536355 4.14302809c-.3878403 1.0342407-1.5406646 1.5582517-2.5749053 1.1704115z"/><path d="m1.5 4.5h2c.55228475 0 1 .44771525 1 1v8c0 .5522847-.44771525 1-1 1h-2c-.55228475 0-1-.4477153-1-1v-8c0-.55228475.44771525-1 1-1z"/></g></svg>

Before

Width:  |  Height:  |  Size: 766 B

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