Compare commits

...

11 Commits

Author SHA1 Message Date
Pablo Ferreiro 6eee156541 /video follows TikTok schema 2022-03-14 11:50:47 +01:00
Pablo Ferreiro c682432920 Accept chromedriver url for sign and .env cleanup 2022-03-13 23:18:10 +01:00
Pablo Ferreiro d061c210a2 Heroku fix 2022-03-13 18:42:41 +01:00
Pablo Ferreiro 3b5c3cd96c hotfix 2022-03-13 18:32:44 +01:00
Pablo Ferreiro 1b29950d89 Heroku using nginx, reverted public and some fixes 2022-03-13 18:22:36 +01:00
Pablo Ferreiro 8ece2c2b25 Fixed JSON Cache and Cache returning VERIFY_CODE 2022-03-12 19:28:37 +01:00
Pablo Ferreiro 7ba324c935 hotfix 2022-03-12 00:34:42 +01:00
Pablo Ferreiro 9c95620384 Fixed /video 2022-03-11 23:39:56 +01:00
Pablo Ferreiro 7b5ec2211e Hopefully last fix 2022-03-11 23:37:12 +01:00
Pablo Ferreiro 2bdd433a17 Fixed RSS 2022-03-11 23:24:56 +01:00
Pablo Ferreiro b8f8eb710f More fixes 2022-03-11 23:24:13 +01:00
26 changed files with 266 additions and 84 deletions
+6 -5
View File
@@ -1,17 +1,18 @@
# 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 CONFIG
# USE_TEST_ENDPOINTS=1 # Discomment for usage of testing TikTok endpoints, may help sometimes
# SIGNER_URL="https://example.com" # External signing service
# FORCE_LEGACY=1 # Force legacy mode for wrapper
# API_FORCE_LEGACY=1 # Force legacy mode for wrapper
# API_SIGNER_URL="https://example.com" # External signing service
# API_BROWSER_URL="http://localhost:4444" # chromedriver url
# API_TEST_ENDPOINTS=1 # Discomment for usage of testing TikTok endpoints, may help sometimes
# API_CACHE=redis
# Proxy Config, will be used to make TikTok requests, useful if having VERIFY_CODE issues
# PROXY_HOST=HOSTNAME
# PROXY_PORT=8080
# PROXY_USERNAME=USERNAME
# PROXY_PASSWORD=PASSWORD
# 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)
# Redis cache, used on Helpers\CacheEngines\RedisCache (CHOOSE ONE)
# REDIS_HOST=localhost # Host or path to unix socket
+5
View File
@@ -5,3 +5,8 @@ RewriteRule ^ index.php [QSA,L]
# Disable index view
Options -Indexes
<Files .env>
Order allow,deny
Deny from all
</Files>
+1 -1
View File
@@ -1 +1 @@
web: vendor/bin/heroku-php-apache2 public/
web: vendor/bin/heroku-php-nginx -C setup/nginx_heroku.conf
+10 -18
View File
@@ -10,7 +10,7 @@ Use Tiktok with an alternative frontend, inspired by Nitter.
* Discovery
* RSS Feed for user, trending and tag (just add /rss to the url)
## Extension
## Extensions
If you want to automatically redirect Tiktok links to ProxiTok you can use:
* [Libredirect](https://github.com/libredirect/libredirect)
* [Redirector](https://github.com/einaregilsson/Redirector)
@@ -31,18 +31,14 @@ Clone the repository and fetch the requiered external packages with:
```bash
composer install
```
WARNING: You'll need a personal Github token for composer.
Move the .env.example file to .env and modify it.
Then you can run it using for example the PHP Development Server with:
```bash
php -S localhost:8080 -t public
php -S localhost:8080
```
## Configuration
### .env
Move the .env.example file to .env and modify it.
### Cache engines
Available cache engines:
* redis: Writes response to Redis
@@ -52,30 +48,26 @@ Available cache engines:
You don't have to do anything more
### Nginx
Add the following to your config (you can modify the proxitok part if you have or not a subdir):
```
location /proxitok {
return 302 $scheme://$host/proxitok/;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location /proxitok/ {
try_files $uri $uri/ /proxitok/index.php?$query_string;
}
location /proxitok/.env {
location /.env {
deny all;
return 404;
}
```
## TODO
## TODO / Known issues
* Docker
* Full installation instructions
* Add a NoJS version / Make the whole program without required JS
* Better error handling
* Make video on /video fit screen and don't overflow
* i18n
## Credits
* [TikTok-API-PHP](https://github.com/ssovit/TikTok-API-PHP) (Currently using my personal fork)
* [TikScraperPHP](https://github.com/pablouser1/TikScraperPHP)
* [Latte](https://github.com/nette/latte)
* [bramus/router](https://github.com/bramus/router)
* [PHP dotenv](https://github.com/vlucas/phpdotenv)
+4 -12
View File
@@ -9,19 +9,14 @@ class JSONCache {
$this->cache_path = $_ENV['API_CACHE_JSON'];
}
}
public function get(string $cache_key): object|false {
public function get(string $cache_key): ?object {
$filename = $this->cache_path . '/' . $cache_key . '.json';
if (is_file($filename)) {
$time = time();
$json_string = file_get_contents($filename);
$element = json_decode($json_string);
if ($time < $element->expires) {
return $element->data;
}
// Remove file if expired
unlink($filename);
return $element;
}
return false;
return null;
}
public function exists(string $cache_key): bool {
@@ -30,9 +25,6 @@ class JSONCache {
}
public function set(string $cache_key, mixed $data, $timeout = 3600) {
file_put_contents($this->cache_path . '/' . $cache_key . '.json', json_encode([
'data' => $data,
'expires' => time() + $timeout
]));
file_put_contents($this->cache_path . '/' . $cache_key . '.json', $data);
}
}
+3 -1
View File
@@ -15,7 +15,9 @@ class RedirectController {
} else if (isset($_GET['music'])) {
$endpoint = '/music/' . $_GET['music'];
} else if (isset($_GET['video'])) {
$endpoint = '/video/' . $_GET['video'];
// The @username part is not used, but
// it is the schema that TikTok follows
$endpoint = '/@placeholder/video/' . $_GET['video'];
}
$url = Misc::url($endpoint);
+12 -5
View File
@@ -11,19 +11,26 @@ class SettingsController {
$latte->render(Misc::getView('settings'), new SettingsTemplate);
}
static private function redirect() {
$url = Misc::url('/settings');
header("Location: {$url}");
}
static public function proxy() {
if (in_array(Cookies::PROXY, $_POST)) {
foreach (Cookies::PROXY as $proxy_element) {
Cookies::set($proxy_element, $_POST[$proxy_element]);
}
}
$url = Misc::url('/settings');
header("Location: {$url}");
self::redirect();
}
static public function api() {
$_POST['legacy'] ?? Cookies::set('api-legacy', '1');
$url = Misc::url('/settings');
header("Location: {$url}");
$legacy = 'off';
if (isset($_POST['api-legacy'])) {
$legacy = 'on';
}
Cookies::set('api-legacy', $legacy);
self::redirect();
}
}
+1 -1
View File
@@ -28,7 +28,7 @@ class TrendingController {
static public function rss() {
$api = Misc::api();
$feed = $api->getTrendingFeed();
$feed = $api->getTrending();
if ($feed->meta->success) {
$feed = RSS::build('/trending', 'Trending', 'Tiktok trending', $feed->items);
// Setup headers
+11
View File
@@ -24,6 +24,17 @@ class UserController {
}
}
static public function video(string $username, string $video_id) {
$api = Misc::api();
$feed = $api->getVideoByID($video_id);
if ($feed->meta->success) {
$latte = Misc::latte();
$latte->render(Misc::getView('video'), new FeedTemplate('Video', $feed));
} else {
ErrorHandler::show($feed->meta);
}
}
static public function rss(string $username) {
$api = Misc::api();
$feed = $api->getUserFeed($username);
+4 -12
View File
@@ -1,19 +1,11 @@
<?php
namespace App\Controllers;
use App\Helpers\ErrorHandler;
use App\Helpers\Misc;
use App\Models\ItemTemplate;
/**
* @deprecated Please use UserController::video instead
*/
class VideoController {
static public function get(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 {
ErrorHandler::show($item->meta);
}
UserController::video('placeholder', $video_id);
}
}
+11 -4
View File
@@ -9,6 +9,10 @@ class Misc {
return isset($_GET['cursor']) && is_numeric($_GET['cursor']) ? (int) $_GET['cursor'] : 0;
}
static public function getTtwid(): string {
return $_GET['cursor'] ?? '';
}
static public function url(string $endpoint = '') {
return self::env('APP_URL', '') . $endpoint;
}
@@ -30,14 +34,18 @@ class Misc {
*/
static public function api() {
$options = [
'remote_signer' => self::env('SIGNER_URL', 'http://localhost:8080/signature'),
'use_test_endpoints' => self::env('USE_TEST_ENDPOINTS', false),
'use_test_endpoints' => self::env('API_TEST_ENDPOINTS', false),
// Instance level proxy config
'proxy' => [
'host' => self::env('PROXY_HOST', null),
'port' => self::env('PROXY_PORT', null),
'user' => self::env('PROXY_USER', null),
'password' => self::env('PROXY_PASSWORD', null)
],
'signer' => [
'remote_url' => self::env('API_SIGNER_URL', ''),
'browser_url' => self::env('API_BROWSER_URL', ''),
'close_when_done' => false
]
];
// User level proxy config, will overwrite instance config
@@ -74,8 +82,7 @@ class Misc {
}
// Legacy mode
$legacy = self::env('FORCE_LEGACY', false); // Instance level
$_COOKIE['enable_legacy'] ?? $legacy = true; // User level
$legacy = self::env('API_FORCE_LEGACY', false) || isset($_COOKIE['api-legacy']) && $_COOKIE['api-legacy'] === 'on';
return $legacy === false ? new \TikScraper\Api($options, $cacheEngine) : new \TikScraper\Legacy($options, $cacheEngine);
}
+1 -1
View File
@@ -2,7 +2,7 @@
namespace App\Helpers;
use \FeedWriter\RSS2;
use \Sovit\TikTok\Download;
use \TikScraper\Download;
class RSS {
static public function build(string $endpoint, string $title, string $description, array $items): string {
+3 -1
View File
@@ -1,7 +1,9 @@
<form action="{path('/settings/api')}" method="POST">
<div class="field">
<label class="checkbox">
<input name="api-legacy" type="checkbox" checked="{isset($_COOKIE['api-legacy']) ? 'true' : 'false'}">Enable legacy mode
<input name="api-legacy" type="checkbox"
checked="{isset($_COOKIE['api-legacy']) && $_COOKIE['api-legacy'] === 'on' ? 'true' : 'false'}"
value="{isset($_COOKIE['api-legacy']) ? $_COOKIE['api-legacy'] : 'off'}">Enable legacy mode
</label>
</div>
<div class="field">
+2 -8
View File
@@ -1,7 +1,7 @@
{
"name": "pablouser1/proxitok",
"description": "An alternative frontend for TikTok",
"version": "2.0.0.2",
"version": "2.1.0.1",
"license": "AGPL-3.0-or-later",
"type": "project",
"homepage": "https://github.com/pablouser1/ProxiTok",
@@ -11,12 +11,6 @@
"homepage": "https://github.com/pablouser1"
}
],
"repositories": [
{
"type": "vcs",
"url": "https://github.com/pablouser1/TikTok-API-PHP"
}
],
"require": {
"ext-redis": "^5.3.2",
"ext-mbstring": "*",
@@ -24,7 +18,7 @@
"vlucas/phpdotenv": "^5.4",
"bramus/router": "^1.6",
"mibe/feedwriter": "^1.1",
"pablouser1/tikscraper": "v1.2.7.4"
"pablouser1/tikscraper": "^1.3"
},
"autoload": {
"psr-4": {
Generated
+166 -8
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "2e80bafe776f5af404ce602d03f554b6",
"content-hash": "f6b066c447574f88ad9255a88892659e",
"packages": [
{
"name": "bramus/router",
@@ -306,20 +306,22 @@
},
{
"name": "pablouser1/tikscraper",
"version": "v1.2.7.4",
"version": "v1.3.0.3",
"source": {
"type": "git",
"url": "https://github.com/pablouser1/TikScraperPHP.git",
"reference": "f42df2e947cfe9afc31a46737a48e720f161ef1c"
"reference": "793dde26e17362af304b9f50890c7fc178387629"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pablouser1/TikScraperPHP/zipball/f42df2e947cfe9afc31a46737a48e720f161ef1c",
"reference": "f42df2e947cfe9afc31a46737a48e720f161ef1c",
"url": "https://api.github.com/repos/pablouser1/TikScraperPHP/zipball/793dde26e17362af304b9f50890c7fc178387629",
"reference": "793dde26e17362af304b9f50890c7fc178387629",
"shasum": ""
},
"require": {
"php": ">=7.3|^8.0"
"php": ">=7.3|^8.0",
"php-webdriver/webdriver": "^1.12",
"sapistudio/seleniumstealth": "^1.0"
},
"type": "library",
"autoload": {
@@ -339,9 +341,74 @@
"description": "Get data from TikTok API",
"support": {
"issues": "https://github.com/pablouser1/TikScraperPHP/issues",
"source": "https://github.com/pablouser1/TikScraperPHP/tree/v1.2.7.4"
"source": "https://github.com/pablouser1/TikScraperPHP/tree/v1.3.0.3"
},
"time": "2022-03-11T21:42:09+00:00"
"time": "2022-03-13T20:53:27+00:00"
},
{
"name": "php-webdriver/webdriver",
"version": "1.12.0",
"source": {
"type": "git",
"url": "https://github.com/php-webdriver/php-webdriver.git",
"reference": "99d4856ed7dffcdf6a52eccd6551e83d8d557ceb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/99d4856ed7dffcdf6a52eccd6551e83d8d557ceb",
"reference": "99d4856ed7dffcdf6a52eccd6551e83d8d557ceb",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
"ext-zip": "*",
"php": "^5.6 || ~7.0 || ^8.0",
"symfony/polyfill-mbstring": "^1.12",
"symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0 || ^6.0"
},
"replace": {
"facebook/webdriver": "*"
},
"require-dev": {
"ondram/ci-detector": "^2.1 || ^3.5 || ^4.0",
"php-coveralls/php-coveralls": "^2.4",
"php-mock/php-mock-phpunit": "^1.1 || ^2.0",
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpunit/phpunit": "^5.7 || ^7 || ^8 || ^9",
"squizlabs/php_codesniffer": "^3.5",
"symfony/var-dumper": "^3.3 || ^4.0 || ^5.0 || ^6.0"
},
"suggest": {
"ext-SimpleXML": "For Firefox profile creation"
},
"type": "library",
"autoload": {
"files": [
"lib/Exception/TimeoutException.php"
],
"psr-4": {
"Facebook\\WebDriver\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.",
"homepage": "https://github.com/php-webdriver/php-webdriver",
"keywords": [
"Chromedriver",
"geckodriver",
"php",
"selenium",
"webdriver"
],
"support": {
"issues": "https://github.com/php-webdriver/php-webdriver/issues",
"source": "https://github.com/php-webdriver/php-webdriver/tree/1.12.0"
},
"time": "2021-10-14T09:30:02+00:00"
},
{
"name": "phpoption/phpoption",
@@ -414,6 +481,36 @@
],
"time": "2021-12-04T23:24:31+00:00"
},
{
"name": "sapistudio/seleniumstealth",
"version": "1.0.3",
"source": {
"type": "git",
"url": "https://github.com/Sapistudio/SeleniumStealth.git",
"reference": "4b392077e3d609fed564b78ae2e8becb98f01957"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Sapistudio/SeleniumStealth/zipball/4b392077e3d609fed564b78ae2e8becb98f01957",
"reference": "4b392077e3d609fed564b78ae2e8becb98f01957",
"shasum": ""
},
"type": "library",
"autoload": {
"psr-4": {
"SapiStudio\\SeleniumStealth\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"support": {
"issues": "https://github.com/Sapistudio/SeleniumStealth/issues",
"source": "https://github.com/Sapistudio/SeleniumStealth/tree/1.0.3"
},
"time": "2022-01-10T20:04:41+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.25.0",
@@ -662,6 +759,67 @@
],
"time": "2022-03-04T08:16:47+00:00"
},
{
"name": "symfony/process",
"version": "v6.0.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "1ccceccc6497e96f4f646218f04b97ae7d9fa7a1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/1ccceccc6497e96f4f646218f04b97ae7d9fa7a1",
"reference": "1ccceccc6497e96f4f646218f04b97ae7d9fa7a1",
"shasum": ""
},
"require": {
"php": ">=8.0.2"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Process\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v6.0.5"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-01-30T18:19:12+00:00"
},
{
"name": "vlucas/phpdotenv",
"version": "v5.4.1",
+3 -3
View File
@@ -1,14 +1,14 @@
<?php
require __DIR__ . "/../vendor/autoload.php";
require __DIR__ . "/vendor/autoload.php";
// LOAD DOTENV
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->safeLoad();
// ROUTER
$router = new Bramus\Router\Router();
$router->setNamespace('\App\Controllers');
require __DIR__ . '/../routes.php';
require __DIR__ . '/routes.php';
$router->run();
+4
View File
@@ -30,9 +30,13 @@ $router->mount('/trending', function () use ($router) {
$router->mount('/@([^/]+)', function () use ($router) {
$router->get('/', 'UserController@get');
$router->get('/video/(\w+)', 'UserController@video');
$router->get('/rss', 'UserController@rss');
});
/**
* @deprecated Please use /@username/video/id instead
*/
$router->get('/video/(\w+)', 'VideoController@get');
$router->mount('/tag', function () use ($router) {
+2 -2
View File
@@ -2,8 +2,8 @@
"name": "proxitok-scss",
"private": true,
"scripts": {
"bulma": "sass --style=compressed bulma.scss ../public/styles/vendor/bulma.min.css",
"fa": "sass --style=compressed fontawesome.scss ../public/styles/vendor/fontawesome.min.css"
"bulma": "sass --style=compressed bulma.scss ./styles/vendor/bulma.min.css",
"fa": "sass --style=compressed fontawesome.scss ./styles/vendor/fontawesome.min.css"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^6.0.0",
+15
View File
@@ -0,0 +1,15 @@
location / {
# try to serve file directly, fallback to rewrite
try_files $uri @rewriteapp;
}
location @rewriteapp {
# rewrite all to index.php
rewrite ^(.*)$ /index.php/$1 last;
}
location ~ ^/index\.php(/|$) {
try_files @heroku-fcgi @heroku-fcgi;
# ensure that /index.php isn't accessible directly, but only through a rewrite
internal;
}
+1 -1
View File
@@ -7,7 +7,7 @@
{block content}
<p class="title">About this instance</p>
<p>Forcing Legacy mode: {isset($_ENV['FORCE_LEGACY']) ? 'yes' : 'no'}</p>
<p>Forcing Legacy mode: {isset($_ENV['API_FORCE_LEGACY']) ? 'yes' : 'no'}</p>
<p>Instance-level Proxy: {isset($_ENV['PROXY_HOST']) ? 'yes' : 'no'}</p>
<hr />
<p class="title">Why would I want to use ProxiTok?</p>
+1 -1
View File
@@ -4,7 +4,7 @@
<p class="title">{$feed->info->detail->title}</p>
<p class="subtitle">{$feed->info->detail->desc}</p>
<p>Videos: {number($feed->info->stats->videoCount)} / Views: {number($feed->info->stats->viewCount)}</p>
<a href="{path('tag/' . $feed->info->detail->title . '/rss')}">RSS</a>
<a href="{path('/tag/' . $feed->info->detail->title . '/rss')}">RSS</a>
{/block}
{block content}