diff --git a/.env.example b/.env.example index e274ebf..89d2d42 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ -# APP_SUBDIR=/ # Subpath your app is running, leave commented for / +# APP_URL="http://localhost:8000" # Full url path, PLEASE REPLACE TO YOUR OWN ONE # LATTE_CACHE=/tmp/proxitok_api # Path for Latte cache, leave commented for ./cache/latte # API_CACHE=redis # Cache engine for TikTok Api, (more info on README) diff --git a/README.md b/README.md index 18b7ec2..ca32da6 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Use Tiktok with an alternative frontend, inspired by Nitter. * See tags * See video by id * Create a following list, which you can later use to see all the feeds from those users +* RSS Feed for user, trending and tag (just add /rss to the url) ## Installation Clone the repository and fetch the requiered external packages with: @@ -28,7 +29,7 @@ Move the .env.example file to .env and modify it. ### Cache engines Available cache engines: -* redis: Writes response to Redis (check .env.example for config!) +* redis: Writes response to Redis * json: Writes response to JSON file ### Apache @@ -58,7 +59,7 @@ location /tiktok-viewer/.env { ## Credits * [TikTok-API-PHP](https://github.com/ssovit/TikTok-API-PHP) -* [steampixel/simplePHPRouter](https://github.com/steampixel/simplePHPRouter) +* [bramus/router](https://github.com/bramus/router) * [PHP dotenv](https://github.com/vlucas/phpdotenv) * [Bulma](https://github.com/jgthms/bulma) * [Bulmaswatch](https://github.com/jenil/bulmaswatch) diff --git a/composer.json b/composer.json index 70c33b0..331eba1 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "pablouser1/proxitok", "description": "An alternative frontend for TikTok", - "version": "1.2.0", + "version": "1.3.0", "license": "AGPL-3.0-or-later", "repositories": [ { @@ -12,9 +12,10 @@ "require": { "ext-redis": "^5.3.2", "ssovit/tiktok-api": "dev-rework", - "steampixel/simple-php-router": "^0.7.0", "latte/latte": "^2.10", - "vlucas/phpdotenv": "^5.4" + "vlucas/phpdotenv": "^5.4", + "bramus/router": "^1.6", + "bhaktaraz/php-rss-generator": "dev-master" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index abb96fc..57d09c0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,108 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2c6e8300b7f0672000799c44a5fd19a3", + "content-hash": "c86d4aba41374f807418dde1044dba45", "packages": [ + { + "name": "bhaktaraz/php-rss-generator", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/bhaktaraz/php-rss-generator.git", + "reference": "53cf11db18d87e65973e6df453fb8c1382e5a3bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bhaktaraz/php-rss-generator/zipball/53cf11db18d87e65973e6df453fb8c1382e5a3bd", + "reference": "53cf11db18d87e65973e6df453fb8c1382e5a3bd", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "default-branch": true, + "type": "library", + "autoload": { + "psr-4": { + "Bhaktaraz\\RSSGenerator\\": "Source/Bhaktaraz/RSSGenerator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bhaktaraz Bhatta", + "email": "bhattabhakta@gmail.com" + } + ], + "description": "Simple RSS generator library for PHP 5.5 or later.", + "homepage": "https://github.com/bhaktaraz/php-rss-generator", + "keywords": [ + "Facebook product feed generator", + "feed", + "generator", + "rss", + "writer" + ], + "support": { + "issues": "https://github.com/bhaktaraz/php-rss-generator/issues", + "source": "https://github.com/bhaktaraz/php-rss-generator/tree/master" + }, + "time": "2021-03-15T10:59:47+00:00" + }, + { + "name": "bramus/router", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/bramus/router.git", + "reference": "55657b76da8a0a509250fb55b9dd24e1aa237eba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bramus/router/zipball/55657b76da8a0a509250fb55b9dd24e1aa237eba", + "reference": "55657b76da8a0a509250fb55b9dd24e1aa237eba", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.14", + "phpunit/php-code-coverage": "~2.0", + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "autoload": { + "psr-0": { + "Bramus": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bram(us) Van Damme", + "email": "bramus@bram.us", + "homepage": "http://www.bram.us" + } + ], + "description": "A lightweight and simple object oriented PHP Router", + "homepage": "https://github.com/bramus/router", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/bramus/router/issues", + "source": "https://github.com/bramus/router/tree/1.6.1" + }, + "time": "2021-11-18T19:24:07+00:00" + }, { "name": "graham-campbell/result-type", "version": "v1.0.4", @@ -275,49 +375,6 @@ }, "time": "2022-01-22T19:37:07+00:00" }, - { - "name": "steampixel/simple-php-router", - "version": "0.7.0", - "source": { - "type": "git", - "url": "https://github.com/steampixel/simplePHPRouter.git", - "reference": "91aec2d0bca3619c0552e2bfcebb8936e6f83bb9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/steampixel/simplePHPRouter/zipball/91aec2d0bca3619c0552e2bfcebb8936e6f83bb9", - "reference": "91aec2d0bca3619c0552e2bfcebb8936e6f83bb9", - "shasum": "" - }, - "type": "library", - "autoload": { - "psr-0": { - "Steampixel": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christoph Stitz", - "email": "info@steampixel.de", - "homepage": "https://steampixel.de/" - }, - { - "name": "Maximilian Götz", - "email": "contact@maxbits.net", - "homepage": "https://www.maxbits.net/" - } - ], - "description": "This is a simple and small PHP router that can handel the whole url routing for your project.", - "support": { - "issues": "https://github.com/steampixel/simplePHPRouter/issues", - "source": "https://github.com/steampixel/simplePHPRouter/tree/0.7.0" - }, - "time": "2021-03-22T08:11:59+00:00" - }, { "name": "symfony/polyfill-ctype", "version": "v1.24.0", @@ -651,7 +708,8 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { - "ssovit/tiktok-api": 20 + "ssovit/tiktok-api": 20, + "bhaktaraz/php-rss-generator": 20 }, "prefer-stable": false, "prefer-lowest": false, diff --git a/helpers/Error.php b/helpers/ErrorHandler.php similarity index 65% rename from helpers/Error.php rename to helpers/ErrorHandler.php index 90469eb..b1df7e5 100644 --- a/helpers/Error.php +++ b/helpers/ErrorHandler.php @@ -1,11 +1,9 @@ http_code; - http_response_code($http_code); - + http_response_code($meta->http_code); $latte = Misc::latte(); $latte->render(Misc::getView('error'), ['error' => $meta]); } diff --git a/helpers/Following.php b/helpers/Following.php index fe5e9ea..4d13c3e 100644 --- a/helpers/Following.php +++ b/helpers/Following.php @@ -2,11 +2,36 @@ namespace Helpers; class Following { - static public function get (): array { + static public function getUsers (): array { $following_string = Settings::get('following'); if ($following_string) { return explode(',', $following_string); } return []; } + + static public function getAll (array $users): object { + $allowed_items_total = isset($_GET['max']) && is_numeric($_GET['max']) && $_GET['max'] <= 100 ? $_GET['max'] : 20; + $items = []; + if (count($users) !== 0) { + $api = Misc::api(); + $max_items_per_user = $allowed_items_total / count($users); + foreach ($users as $user) { + $user_feed = $api->getUserFeed($user); + if ($user_feed) { + $max = count($user_feed->items) > $max_items_per_user ? $max_items_per_user : count($user_feed->items); + for ($i = 0; $i < $max; $i++) { + $item = $user_feed->items[$i]; + array_push($items, $item); + } + } + } + } + + $feed = (object) [ + 'items' => $items, + 'hasMore' => false + ]; + return $feed; + } }; diff --git a/helpers/Misc.php b/helpers/Misc.php index 0a56daa..647945a 100644 --- a/helpers/Misc.php +++ b/helpers/Misc.php @@ -7,14 +7,20 @@ use Helpers\CacheEngines\RedisCache; use Helpers\Settings; class Misc { - static public function getSubDir(): string { - return isset($_ENV['APP_SUBDIR']) && !empty($_ENV['APP_SUBDIR']) ? $_ENV['APP_SUBDIR'] : '/'; + static public function env(string $key, mixed $default_value): string { + return isset($_ENV[$key]) && !empty($_ENV[$key]) ? $_ENV[$key] : $default_value; } + /** + * Returns absolute path for view + */ static public function getView(string $template): string { return __DIR__ . "/../views/{$template}.latte"; } + /** + * Setup of TikTok Api wrapper + */ static public function api(): \Sovit\TikTok\Api { $options = []; $cacheEngine = false; @@ -47,26 +53,25 @@ class Misc { return $api; } + /** + * Setup of Latte template engine + */ static public function latte(): \Latte\Engine { // Workaround to avoid weird path issues - $subdir = Misc::getSubDir(); - if ($subdir === '/') { - $subdir = ''; - } + $url = self::env('APP_URL', ''); $latte = new \Latte\Engine; - $cache_path = isset($_ENV['LATTE_CACHE']) && !empty($_ENV['LATTE_CACHE']) ? $_ENV['LATTE_CACHE'] : __DIR__ . '/../cache/latte'; + $cache_path = self::env('LATTE_CACHE', __DIR__ . '/../cache/latte'); $latte->setTempDirectory($cache_path); // -- CUSTOM FUNCTIONS -- // // Import assets - $latte->addFunction('assets', function (string $name, string $type) use ($subdir) { - $path = "{$subdir}/{$type}/{$name}"; + $latte->addFunction('assets', function (string $name, string $type) use ($url) { + $path = "{$url}/{$type}/{$name}"; return $path; }); - // Relative path - $latte->addFunction('path', function (string $name) use ($subdir) { - $path = "{$subdir}/{$name}"; - return $path; + // Get base URL + $latte->addFunction('path', function (string $path = '') use ($url) { + return "{$url}/{$path}"; }); // Version being used $latte->addFunction('version', function () { @@ -86,6 +91,10 @@ class Misc { } return $x; }); + $latte->addFunction('size', function (string $url) { + $download = new \Sovit\TikTok\Download(); + return $download->file_size($url); + }); return $latte; } } diff --git a/helpers/RSS.php b/helpers/RSS.php new file mode 100644 index 0000000..44644c3 --- /dev/null +++ b/helpers/RSS.php @@ -0,0 +1,40 @@ +title($title) + ->description($description) + ->url($url . $endpoint) + ->atomLinkSelf($url . $endpoint . '/rss') + ->appendTo($rss_feed); + foreach ($items as $item) { + $rss_item = new Item(); + $video = $item->video->playAddr; + $rss_item + ->title($item->desc) + ->description($item->desc) + ->url($url . '/video/' . $item->id) + ->pubDate((int)$item->createTime) + ->guid($item->id, false) + ->enclosure($url . '/stream?url=' . urlencode($video), $download->file_size($video), 'video/mp4') + ->appendTo($rss_channel); + } + return $rss_feed; + } + + static public function setHeaders (string $filename) { + header('Content-Type: application/rss+xml'); + header('Content-Disposition: attachment; filename="' . $filename . '"'); + } +} diff --git a/helpers/Settings.php b/helpers/Settings.php index 5da0749..4279c24 100644 --- a/helpers/Settings.php +++ b/helpers/Settings.php @@ -16,6 +16,6 @@ class Settings { } static public function set(string $name, string $value) { - setcookie($name, $value, time()+60*60*24*30, Misc::getSubDir(), '', isset($_SERVER['HTTPS']), true); + setcookie($name, $value, time()+60*60*24*30, '/', '', isset($_SERVER['HTTPS']), true); } }; diff --git a/index.php b/index.php index 735d789..0568d73 100644 --- a/index.php +++ b/index.php @@ -1,12 +1,11 @@ safeLoad(); +// ROUTER +$router = new Bramus\Router\Router(); require __DIR__ . '/routes/index.php'; - -Route::run(Misc::getSubDir()); +$router->run(); diff --git a/layouts/default.latte b/layouts/default.latte index 05e2f39..42dff56 100644 --- a/layouts/default.latte +++ b/layouts/default.latte @@ -16,6 +16,6 @@ {block content}{/block} {include '../components/footer.latte'} - {block extra}{/block} + diff --git a/routes/assets.php b/routes/assets.php index 13fba87..51b8137 100644 --- a/routes/assets.php +++ b/routes/assets.php @@ -1,10 +1,10 @@ get('/stream', function () { if (!isset($_GET['url'])) { die('You need to send a url!'); } diff --git a/routes/following.php b/routes/following.php index 1509907..3b0eb6f 100644 --- a/routes/following.php +++ b/routes/following.php @@ -1,33 +1,15 @@ getUserFeed($user); - if ($user_feed) { - $max = count($user_feed->items) > $max_items_per_user ? $max_items_per_user : count($user_feed->items); - for ($i = 0; $i < $max; $i++) { - $item = $user_feed->items[$i]; - array_push($items, $item); - } - } - } - } - - $feed = (object) [ - 'items' => $items, - 'hasMore' => false - ]; +$router->get('/following', function () { + $users = Following::getUsers(); + $feed = Following::getAll($users); $latte = Misc::latte(); - $latte->render(Misc::getView('following'), new FollowingTemplate($following, $feed)); + $latte->render(Misc::getView('following'), new FollowingTemplate($users, $feed)); }); diff --git a/routes/index.php b/routes/index.php index 39d7990..3fa0f65 100644 --- a/routes/index.php +++ b/routes/index.php @@ -2,25 +2,27 @@ require __DIR__ . '/assets.php'; require __DIR__ . '/settings.php'; require __DIR__ . '/following.php'; +require __DIR__ . '/rss.php'; + +/**@var Bramus\Router\Router $router */ -use Steampixel\Route; use Helpers\Misc; -use Helpers\Error; +use Helpers\ErrorHandler; use Views\Models\BaseTemplate; use Views\Models\FeedTemplate; use Views\Models\ItemTemplate; -Route::add('/', function () { +$router->get('/', function () { $latte = Misc::latte(); $latte->render(Misc::getView('home'), new BaseTemplate('Home')); }); -Route::add('/about', function () { +$router->get('/about', function () { $latte = Misc::latte(); $latte->render(Misc::getView('about'), new BaseTemplate('About')); }); -Route::add("/trending", function () { +$router->get("/trending", function () { $cursor = 0; if (isset($_GET['cursor']) && is_numeric($_GET['cursor'])) { $cursor = (int) $_GET['cursor']; @@ -31,11 +33,11 @@ Route::add("/trending", function () { $latte = Misc::latte(); $latte->render(Misc::getView('trending'), new FeedTemplate('Trending', $feed)); } else { - Error::show($feed->meta); + ErrorHandler::show($feed->meta); } }); -Route::add("/@([^/]+)", function (string $username) { +$router->get("/@([^/]+)", function (string $username) { $cursor = 0; if (isset($_GET['cursor']) && is_numeric($_GET['cursor'])) { $cursor = (int) $_GET['cursor']; @@ -50,22 +52,22 @@ Route::add("/@([^/]+)", function (string $username) { $latte = Misc::latte(); $latte->render(Misc::getView('user'), new FeedTemplate($feed->info->detail->user->nickname, $feed)); } else { - Error::show($feed->meta); + ErrorHandler::show($feed->meta); } }); -Route::add('/video/([^/]+)', function (string $video_id) { +$router->get('/video/([^/]+)', function (string $video_id) { $api = Misc::api(); $item = $api->getVideoByID($video_id); if ($item->meta->success) { $latte = Misc::latte(); $latte->render(Misc::getView('video'), new ItemTemplate($item->info->detail->user->nickname, $item)); } else { - Error::show($item->meta); + ErrorHandler::show($item->meta); } }); -Route::add('/music/([^/]+)', function (string $music_id) { +$router->get('/music/([^/]+)', function (string $music_id) { $cursor = 0; if (isset($_GET['cursor']) && is_numeric($_GET['cursor'])) { $cursor = (int) $_GET['cursor']; @@ -77,11 +79,11 @@ Route::add('/music/([^/]+)', function (string $music_id) { $latte = Misc::latte(); $latte->render(Misc::getView('music'), new FeedTemplate('Music', $feed)); } else { - Error::show($feed->meta); + ErrorHandler::show($feed->meta); } }); -Route::add('/tag/(\w+)', function (string $name) { +$router->get('/tag/(\w+)', function (string $name) { $cursor = 0; if (isset($_GET['cursor']) && is_numeric($_GET['cursor'])) { $cursor = (int) $_GET['cursor']; @@ -92,6 +94,6 @@ Route::add('/tag/(\w+)', function (string $name) { $latte = Misc::latte(); $latte->render(Misc::getView('tag'), new FeedTemplate('Tag', $feed)); } else { - Error::show($feed->meta); + ErrorHandler::show($feed->meta); } }); diff --git a/routes/rss.php b/routes/rss.php new file mode 100644 index 0000000..f33198e --- /dev/null +++ b/routes/rss.php @@ -0,0 +1,46 @@ +all("/@([^/]+)/rss", function (string $username) { + $api = Misc::api(); + $feed = $api->getUserFeed($username); + if ($feed->meta->success) { + $feed = RSS::build('/@'.$username, $feed->info->detail->user->nickname, $feed->info->detail->user->signature, $feed->items); + // Setup headers + RSS::setHeaders('user.rss'); + echo $feed; + } else { + ErrorHandler::show($feed->meta); + } +}); + +$router->all("/trending/rss", function () { + $api = Misc::api(); + $feed = $api->getTrendingFeed(); + if ($feed->meta->success) { + $feed = RSS::build('/trending', 'Trending', 'Tiktok trending', $feed->items); + // Setup headers + RSS::setHeaders('trending.rss'); + echo $feed; + } else { + ErrorHandler::show($feed->meta); + } +}); + +$router->all("/tag/(\w+)/rss", function (string $name) { + $api = Misc::api(); + $feed = $api->getChallengeFeed($name); + if ($feed->meta->success) { + $feed = RSS::build("/tag/{$name}", "{$name} Tag", $feed->info->detail->challenge->desc, $feed->items); + // Setup headers + RSS::setHeaders('tag.rss'); + echo $feed; + } else { + ErrorHandler::show($feed->meta); + } +}); diff --git a/routes/settings.php b/routes/settings.php index 70f1f31..4f9cb60 100644 --- a/routes/settings.php +++ b/routes/settings.php @@ -1,52 +1,54 @@ render(Misc::getView('settings'), new SettingsTemplate()); -}); +$router->mount('/settings', function () use ($router) { + $router->get('/', function () { + $latte = Misc::latte(); + $latte->render(Misc::getView('settings'), new SettingsTemplate()); + }); -Route::add("/settings/proxy", function () { - if (in_array(Settings::PROXY, $_POST)) { - foreach (Settings::PROXY as $proxy_element) { - Settings::set($proxy_element, $_POST[$proxy_element]); - } - } - http_response_code(302); - header('Location: ./home'); -}, 'POST'); - - -Route::add("/settings/following", function () { - $following = Following::get(); - if (!isset($_POST['mode']) || empty($_POST['mode'])) { - die('You need to send a mode'); - } - - switch ($_POST['mode']) { - case 'add': - // Add following - array_push($following, $_POST['account']); - break; - case 'remove': - // Remove following - $index = array_search($_POST['account'], $following); - if ($index !== false) { - unset($following[$index]); + $router->post('/proxy', function () { + if (in_array(Settings::PROXY, $_POST)) { + foreach (Settings::PROXY as $proxy_element) { + Settings::set($proxy_element, $_POST[$proxy_element]); } - break; - default: - // Invalid - die('Invalid mode'); - } + } + http_response_code(302); + header('Location: ./home'); + }); - // Build string - $following_string = implode(',', $following); - Settings::set('following', $following_string); - header('Location: ../settings'); -}, 'POST'); + $router->post('/following', function () { + $following = Following::getUsers(); + if (!isset($_POST['mode']) || empty($_POST['mode'])) { + die('You need to send a mode'); + } + + switch ($_POST['mode']) { + case 'add': + // Add following + array_push($following, $_POST['account']); + break; + case 'remove': + // Remove following + $index = array_search($_POST['account'], $following); + if ($index !== false) { + unset($following[$index]); + } + break; + default: + // Invalid + die('Invalid mode'); + } + + // Build string + $following_string = implode(',', $following); + Settings::set('following', $following_string); + header('Location: ../settings'); + }); +}); diff --git a/views/models/SettingsTemplate.php b/views/models/SettingsTemplate.php index 6005373..6897401 100644 --- a/views/models/SettingsTemplate.php +++ b/views/models/SettingsTemplate.php @@ -14,6 +14,6 @@ class SettingsTemplate extends BaseTemplate { function __construct() { parent::__construct('Settings'); $this->proxy_elements = Settings::PROXY; - $this->following = Following::get(); + $this->following = Following::getUsers(); } } diff --git a/views/tag.latte b/views/tag.latte index 43aea6e..9313214 100644 --- a/views/tag.latte +++ b/views/tag.latte @@ -4,6 +4,7 @@

{$feed->info->detail->challenge->title}

{$feed->info->detail->challenge->desc}

Videos: {number($feed->info->detail->stats->videoCount)} / Views: {number($feed->info->detail->stats->viewCount)}

+ RSS {/block} {block content} diff --git a/views/trending.latte b/views/trending.latte index bc367bd..7bed5ef 100644 --- a/views/trending.latte +++ b/views/trending.latte @@ -2,6 +2,7 @@ {block header}

Trending

+ RSS {/block} {block content} diff --git a/views/user.latte b/views/user.latte index 7aa8c2f..6f44078 100644 --- a/views/user.latte +++ b/views/user.latte @@ -8,6 +8,7 @@

{$feed->info->detail->user->signature}

Following: {number($feed->info->detail->stats->followingCount)} / Followers: {number($feed->info->detail->stats->followerCount)}

Hearts: {number($feed->info->detail->stats->heartCount)} / Videos: {$feed->info->detail->stats->videoCount}

+ RSS {/block} {block content} diff --git a/views/video.latte b/views/video.latte index 7192eff..6e65f09 100644 --- a/views/video.latte +++ b/views/video.latte @@ -3,7 +3,9 @@ {block content}
- +