Icons, themes, removed instance proxy and more
This commit is contained in:
parent
9a35c61023
commit
44ee065ec6
|
@ -1,4 +1,4 @@
|
|||
# APP_URL="http://localhost:8000" # Full url path, PLEASE REPLACE TO YOUR OWN ONE
|
||||
# APP_PATH="/proxitok" # Relative path, PLEASE LEAVE EMPTY IF /
|
||||
# LATTE_CACHE=/tmp/proxitok_api # Path for Latte cache, leave commented for ./cache/latte
|
||||
|
||||
# API CONFIG
|
||||
|
@ -8,12 +8,6 @@
|
|||
# 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
|
||||
|
||||
# Redis cache, used on Helpers\CacheEngines\RedisCache (CHOOSE ONE)
|
||||
# REDIS_HOST=localhost # Host or path to unix socket
|
||||
# REDIS_PORT=6379 # Set to -1 to use unix socket
|
||||
|
|
|
@ -4,10 +4,10 @@ Use Tiktok with an alternative frontend, inspired by Nitter.
|
|||
## Features
|
||||
* Privacy: All requests made to TikTok are server-side, so you will never connect to their servers
|
||||
* See user's feed
|
||||
* See trending
|
||||
* See trending and discovery tab
|
||||
* See tags
|
||||
* See video by id
|
||||
* Discovery
|
||||
* Themes
|
||||
* RSS Feed for user, trending and tag (just add /rss to the url)
|
||||
|
||||
## Self-hosting
|
||||
|
@ -33,9 +33,8 @@ Apply to: Main window (address bar)
|
|||
```
|
||||
|
||||
## TODO / Known issues
|
||||
* Add a NoJS version / Make the whole program without required JS
|
||||
* Make video on /video fit screen and don't overflow
|
||||
* i18n
|
||||
* Search
|
||||
|
||||
## Credits
|
||||
[@TheFrenchGhosty](https://github.com/TheFrenchGhosty): Initial Dockerfile and fixes to a usable state. You can check his Docker image [here](https://github.com/PussTheCat-org/docker-proxitok-quay) on Github or [here](https://quay.io/repository/pussthecatorg/proxitok) on Quay
|
||||
|
@ -45,4 +44,3 @@ Apply to: Main window (address bar)
|
|||
* [bramus/router](https://github.com/bramus/router)
|
||||
* [PHP dotenv](https://github.com/vlucas/phpdotenv)
|
||||
* [Bulma](https://github.com/jgthms/bulma) and [Bulmaswatch](https://github.com/jenil/bulmaswatch)
|
||||
* [FeedWriter](https://github.com/mibe/FeedWriter)
|
||||
|
|
|
@ -3,14 +3,15 @@ namespace App\Controllers;
|
|||
|
||||
use App\Helpers\ErrorHandler;
|
||||
use App\Helpers\Misc;
|
||||
use App\Helpers\Wrappers;
|
||||
use App\Models\FeedTemplate;
|
||||
|
||||
class DiscoverController {
|
||||
static public function get() {
|
||||
$api = Misc::api();
|
||||
$api = Wrappers::api();
|
||||
$feed = $api->getDiscover();
|
||||
if ($feed->meta->success) {
|
||||
$latte = Misc::latte();
|
||||
$latte = Wrappers::latte();
|
||||
$latte->render(Misc::getView('discover'), new FeedTemplate('Discover', $feed));
|
||||
} else {
|
||||
ErrorHandler::show($feed->meta);
|
||||
|
|
|
@ -3,16 +3,17 @@ namespace App\Controllers;
|
|||
|
||||
use App\Helpers\ErrorHandler;
|
||||
use App\Helpers\Misc;
|
||||
use App\Helpers\Wrappers;
|
||||
use App\Models\FeedTemplate;
|
||||
|
||||
class MusicController {
|
||||
static public function get(string $music_id) {
|
||||
$cursor = Misc::getCursor();
|
||||
|
||||
$api = Misc::api();
|
||||
$api = Wrappers::api();
|
||||
$feed = $api->getMusicFeed($music_id, $cursor);
|
||||
if ($feed->meta->success) {
|
||||
$latte = Misc::latte();
|
||||
$latte = Wrappers::latte();
|
||||
$latte->render(Misc::getView('music'), new FeedTemplate('Music', $feed));
|
||||
} else {
|
||||
ErrorHandler::show($feed->meta);
|
||||
|
|
|
@ -7,17 +7,25 @@ use App\Helpers\Misc;
|
|||
*/
|
||||
class RedirectController {
|
||||
static public function redirect() {
|
||||
$endpoint = '';
|
||||
if (isset($_GET['user'])) {
|
||||
$endpoint = '/@' . $_GET['user'];
|
||||
} else if (isset($_GET['tag'])) {
|
||||
$endpoint = '/tag/' . $_GET['tag'];
|
||||
} else if (isset($_GET['music'])) {
|
||||
$endpoint = '/music/' . $_GET['music'];
|
||||
} else if (isset($_GET['video'])) {
|
||||
$endpoint = '/';
|
||||
if (isset($_GET['type'], $_GET['term'])) {
|
||||
$term = $_GET['term'];
|
||||
switch ($_GET['type']) {
|
||||
case 'user':
|
||||
$endpoint = '/@' . $term;
|
||||
break;
|
||||
case 'tag':
|
||||
$endpoint = '/tag/' . $term;
|
||||
break;
|
||||
case 'music':
|
||||
$endpoint = '/music/' . $term;
|
||||
break;
|
||||
case 'video':
|
||||
// The @username part is not used, but
|
||||
// it is the schema that TikTok follows
|
||||
$endpoint = '/@placeholder/video/' . $_GET['video'];
|
||||
$endpoint = '/@placeholder/video/' . $term;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$url = Misc::url($endpoint);
|
||||
|
|
|
@ -3,12 +3,13 @@ namespace App\Controllers;
|
|||
|
||||
use App\Helpers\Misc;
|
||||
use App\Helpers\Cookies;
|
||||
use App\Models\SettingsTemplate;
|
||||
use App\Helpers\Wrappers;
|
||||
use App\Models\BaseTemplate;
|
||||
|
||||
class SettingsController {
|
||||
static public function index() {
|
||||
$latte = Misc::latte();
|
||||
$latte->render(Misc::getView('settings'), new SettingsTemplate);
|
||||
$latte = Wrappers::latte();
|
||||
$latte->render(Misc::getView('settings'), new BaseTemplate('Settings'));
|
||||
}
|
||||
|
||||
static private function redirect() {
|
||||
|
@ -16,19 +17,16 @@ class SettingsController {
|
|||
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]);
|
||||
}
|
||||
static public function general() {
|
||||
if (isset($_POST['theme'])) {
|
||||
$theme = $_POST['theme'];
|
||||
Cookies::set('theme', $theme);
|
||||
}
|
||||
self::redirect();
|
||||
}
|
||||
|
||||
static public function api() {
|
||||
$legacy = 'off';
|
||||
if (isset($_POST['api-legacy'])) {
|
||||
$legacy = 'on';
|
||||
$legacy = $_POST['api-legacy'];
|
||||
}
|
||||
Cookies::set('api-legacy', $legacy);
|
||||
self::redirect();
|
||||
|
|
|
@ -3,16 +3,17 @@ namespace App\Controllers;
|
|||
|
||||
use App\Helpers\ErrorHandler;
|
||||
use App\Helpers\Misc;
|
||||
use App\Helpers\RSS;
|
||||
use App\Helpers\Wrappers;
|
||||
use App\Models\FeedTemplate;
|
||||
use App\Models\RSSTemplate;
|
||||
|
||||
class TagController {
|
||||
static public function get(string $name) {
|
||||
$cursor = Misc::getCursor();
|
||||
$api = Misc::api();
|
||||
$api = Wrappers::api();
|
||||
$feed = $api->getHashtagFeed($name, $cursor);
|
||||
if ($feed->meta->success) {
|
||||
$latte = Misc::latte();
|
||||
$latte = Wrappers::latte();
|
||||
$latte->render(Misc::getView('tag'), new FeedTemplate('Tag', $feed));
|
||||
} else {
|
||||
ErrorHandler::show($feed->meta);
|
||||
|
@ -20,13 +21,11 @@ class TagController {
|
|||
}
|
||||
|
||||
static public function rss(string $name) {
|
||||
$api = Misc::api();
|
||||
$api = Wrappers::api();
|
||||
$feed = $api->getHashtagFeed($name);
|
||||
if ($feed->meta->success) {
|
||||
$feed = RSS::build("/tag/{$name}", "{$name} Tag", $feed->info->detail->desc, $feed->items);
|
||||
// Setup headers
|
||||
RSS::setHeaders('tag.rss');
|
||||
echo $feed;
|
||||
$latte = Wrappers::latte();
|
||||
$latte->render(Misc::getView('rss'), new RSSTemplate($name, $feed->info->detail->desc, "/tag/{$name}", $feed->items));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,12 @@ namespace App\Controllers;
|
|||
use App\Helpers\Misc;
|
||||
use App\Models\FeedTemplate;
|
||||
use App\Helpers\ErrorHandler;
|
||||
use App\Helpers\RSS;
|
||||
use App\Helpers\Wrappers;
|
||||
use App\Models\RSSTemplate;
|
||||
|
||||
class TrendingController {
|
||||
static public function get() {
|
||||
$api = Misc::api();
|
||||
$api = Wrappers::api();
|
||||
|
||||
// Ttwid if normal, cursor if legacy
|
||||
if ($api::class === 'TikScraper\Api') {
|
||||
|
@ -18,7 +19,7 @@ class TrendingController {
|
|||
}
|
||||
$feed = $api->getTrending($cursor);
|
||||
if ($feed->meta->success) {
|
||||
$latte = Misc::latte();
|
||||
$latte = Wrappers::latte();
|
||||
$latte->render(Misc::getView('trending'), new FeedTemplate('Trending', $feed));
|
||||
} else {
|
||||
ErrorHandler::show($feed->meta);
|
||||
|
@ -26,13 +27,11 @@ class TrendingController {
|
|||
}
|
||||
|
||||
static public function rss() {
|
||||
$api = Misc::api();
|
||||
$api = Wrappers::api();
|
||||
$feed = $api->getTrending();
|
||||
if ($feed->meta->success) {
|
||||
$feed = RSS::build('/trending', 'Trending', 'Tiktok trending', $feed->items);
|
||||
// Setup headers
|
||||
RSS::setHeaders('trending.rss');
|
||||
echo $feed;
|
||||
$latte = Wrappers::latte();
|
||||
$latte->render(Misc::getView('rss'), new RSSTemplate('Trending', 'Trending on TikTok', '/trending', $feed->items));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,13 +3,14 @@ namespace App\Controllers;
|
|||
|
||||
use App\Helpers\ErrorHandler;
|
||||
use App\Helpers\Misc;
|
||||
use App\Helpers\RSS;
|
||||
use App\Helpers\Wrappers;
|
||||
use App\Models\FeedTemplate;
|
||||
use App\Models\RSSTemplate;
|
||||
|
||||
class UserController {
|
||||
static public function get(string $username) {
|
||||
$cursor = Misc::getCursor();
|
||||
$api = Misc::api();
|
||||
$api = Wrappers::api();
|
||||
$feed = $api->getUserFeed($username, $cursor);
|
||||
if ($feed->meta->success) {
|
||||
if ($feed->info->detail->privateAccount) {
|
||||
|
@ -17,7 +18,7 @@ class UserController {
|
|||
echo 'Private account detected! Not supported';
|
||||
exit;
|
||||
}
|
||||
$latte = Misc::latte();
|
||||
$latte = Wrappers::latte();
|
||||
$latte->render(Misc::getView('user'), new FeedTemplate($feed->info->detail->nickname, $feed));
|
||||
} else {
|
||||
ErrorHandler::show($feed->meta);
|
||||
|
@ -25,10 +26,10 @@ class UserController {
|
|||
}
|
||||
|
||||
static public function video(string $username, string $video_id) {
|
||||
$api = Misc::api();
|
||||
$api = Wrappers::api();
|
||||
$feed = $api->getVideoByID($video_id);
|
||||
if ($feed->meta->success) {
|
||||
$latte = Misc::latte();
|
||||
$latte = Wrappers::latte();
|
||||
$latte->render(Misc::getView('video'), new FeedTemplate('Video', $feed));
|
||||
} else {
|
||||
ErrorHandler::show($feed->meta);
|
||||
|
@ -36,13 +37,11 @@ class UserController {
|
|||
}
|
||||
|
||||
static public function rss(string $username) {
|
||||
$api = Misc::api();
|
||||
$api = Wrappers::api();
|
||||
$feed = $api->getUserFeed($username);
|
||||
if ($feed->meta->success) {
|
||||
$feed = RSS::build('/@'.$username, $feed->info->detail->nickname, $feed->info->detail->signature, $feed->items);
|
||||
// Setup headers
|
||||
RSS::setHeaders('user.rss');
|
||||
echo $feed;
|
||||
$latte = Wrappers::latte();
|
||||
$latte->render(Misc::getView('rss'), new RSSTemplate($username, $feed->info->detail->signature, '/@' . $username, $feed->items));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,21 @@
|
|||
namespace App\Helpers;
|
||||
|
||||
class Cookies {
|
||||
const PROXY = ['host', 'port', 'user', 'password'];
|
||||
const ALLOWED_THEMES = ['default', 'card'];
|
||||
|
||||
static public function get(string $name): ?string {
|
||||
static public function get(string $name, string $default_value = ''): string {
|
||||
if (isset($_COOKIE[$name]) && !empty($_COOKIE[$name])) {
|
||||
return $_COOKIE[$name];
|
||||
}
|
||||
return null;
|
||||
return $default_value;
|
||||
}
|
||||
|
||||
static public function theme(): string {
|
||||
$theme = self::get('theme');
|
||||
if ($theme && in_array($theme, self::ALLOWED_THEMES)) {
|
||||
return $theme;
|
||||
}
|
||||
return 'default';
|
||||
}
|
||||
|
||||
static public function exists(string $name): bool {
|
||||
|
|
|
@ -6,7 +6,7 @@ use App\Models\ErrorTemplate;
|
|||
class ErrorHandler {
|
||||
static public function show(object $meta) {
|
||||
http_response_code($meta->http_code);
|
||||
$latte = Misc::latte();
|
||||
$latte = Wrappers::latte();
|
||||
$latte->render(Misc::getView('error'), new ErrorTemplate($meta));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
<?php
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Cache\JSONCache;
|
||||
use App\Cache\RedisCache;
|
||||
|
||||
class Misc {
|
||||
static public function getCursor(): int {
|
||||
return isset($_GET['cursor']) && is_numeric($_GET['cursor']) ? (int) $_GET['cursor'] : 0;
|
||||
|
@ -13,8 +10,10 @@ class Misc {
|
|||
return isset($_GET['cursor']) ? $_GET['cursor'] : '';
|
||||
}
|
||||
|
||||
static public function url(string $endpoint = '') {
|
||||
return self::env('APP_URL', '') . $endpoint;
|
||||
static public function url(string $endpoint = ''): string {
|
||||
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http";
|
||||
$root = $protocol . '://' . $_SERVER['HTTP_HOST'];
|
||||
return $root . self::env('APP_PATH', '') . $endpoint;
|
||||
}
|
||||
|
||||
static public function env(string $key, $default_value) {
|
||||
|
@ -27,97 +26,4 @@ class Misc {
|
|||
static public function getView(string $template): string {
|
||||
return __DIR__ . "/../../views/{$template}.latte";
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup of TikTok Api wrapper
|
||||
* @return \TikScraper\Api|\TikScraper\Legacy
|
||||
*/
|
||||
static public function api() {
|
||||
$options = [
|
||||
'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
|
||||
foreach(Cookies::PROXY as $proxy_element) {
|
||||
if (isset($_COOKIE[$proxy_element])) {
|
||||
$options['proxy'][$proxy_element] = $_COOKIE[$proxy_element];
|
||||
}
|
||||
}
|
||||
// Cache config
|
||||
$cacheEngine = false;
|
||||
if (isset($_ENV['API_CACHE'])) {
|
||||
switch ($_ENV['API_CACHE']) {
|
||||
case 'json':
|
||||
$cacheEngine = new JSONCache();
|
||||
break;
|
||||
case 'redis':
|
||||
if (!(isset($_ENV['REDIS_URL']) || isset($_ENV['REDIS_HOST'], $_ENV['REDIS_PORT']))) {
|
||||
throw new \Exception('You need to set REDIS_URL or REDIS_HOST and REDIS_PORT to use Redis Cache!');
|
||||
}
|
||||
|
||||
if (isset($_ENV['REDIS_URL'])) {
|
||||
$url = parse_url($_ENV['REDIS_URL']);
|
||||
$host = $url['host'];
|
||||
$port = $url['port'];
|
||||
$password = $url['pass'] ?? null;
|
||||
} else {
|
||||
$host = $_ENV['REDIS_HOST'];
|
||||
$port = (int) $_ENV['REDIS_PORT'];
|
||||
$password = isset($_ENV['REDIS_PASSWORD']) ? $_ENV['REDIS_PASSWORD'] : null;
|
||||
}
|
||||
$cacheEngine = new RedisCache($host, $port, $password);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy mode
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup of Latte template engine
|
||||
*/
|
||||
static public function latte(): \Latte\Engine {
|
||||
$latte = new \Latte\Engine;
|
||||
$cache_path = self::env('LATTE_CACHE', __DIR__ . '/../../cache/latte');
|
||||
$latte->setTempDirectory($cache_path);
|
||||
|
||||
// -- CUSTOM FUNCTIONS -- //
|
||||
// Get URL with optional endpoint
|
||||
$latte->addFunction('path', function (string $endpoint = ''): string {
|
||||
return self::url($endpoint);
|
||||
});
|
||||
// Version being used
|
||||
$latte->addFunction('version', function (): string {
|
||||
return \Composer\InstalledVersions::getVersion('pablouser1/proxitok');
|
||||
});
|
||||
// https://stackoverflow.com/a/36365553
|
||||
$latte->addFunction('number', function (float $x) {
|
||||
if($x > 1000) {
|
||||
$x_number_format = number_format($x);
|
||||
$x_array = explode(',', $x_number_format);
|
||||
$x_parts = array('K', 'M', 'B', 'T');
|
||||
$x_count_parts = count($x_array) - 1;
|
||||
$x_display = $x;
|
||||
$x_display = $x_array[0] . ((int) $x_array[1][0] !== 0 ? '.' . $x_array[1][0] : '');
|
||||
$x_display .= $x_parts[$x_count_parts - 1];
|
||||
return $x_display;
|
||||
}
|
||||
return $x;
|
||||
});
|
||||
return $latte;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
<?php
|
||||
namespace App\Helpers;
|
||||
|
||||
use \FeedWriter\RSS2;
|
||||
use \TikScraper\Download;
|
||||
|
||||
class RSS {
|
||||
static public function build(string $endpoint, string $title, string $description, array $items): string {
|
||||
$url = Misc::env('APP_URL', '');
|
||||
$download = new Download();
|
||||
$rss = new RSS2();
|
||||
$rss->setTitle($title);
|
||||
$rss->setDescription($description);
|
||||
$rss->setLink($url . $endpoint);
|
||||
$rss->setSelfLink($url . $endpoint . '/rss');
|
||||
foreach ($items as $item) {
|
||||
$item_rss = $rss->createNewItem();
|
||||
$video = $item->video->playAddr;
|
||||
$item_rss->setTitle($item->desc);
|
||||
$item_rss->setDescription($item->desc);
|
||||
$item_rss->setLink($url . '/video/' . $item->id);
|
||||
$item_rss->setDate((int) $item->createTime);
|
||||
$item_rss->setId($item->id, false);
|
||||
$item_rss->addEnclosure($url . '/stream?url=' . urlencode($video), $download->file_size($video), 'video/mp4');
|
||||
$rss->addItem($item_rss);
|
||||
}
|
||||
return $rss->generateFeed();
|
||||
}
|
||||
|
||||
static public function setHeaders (string $filename) {
|
||||
header('Content-Type: application/rss+xml');
|
||||
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
||||
}
|
||||
}
|
90
app/Helpers/Wrappers.php
Normal file
90
app/Helpers/Wrappers.php
Normal file
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Cache\JSONCache;
|
||||
use App\Cache\RedisCache;
|
||||
|
||||
class Wrappers {
|
||||
/**
|
||||
* Setup of Latte template engine
|
||||
*/
|
||||
static public function latte(): \Latte\Engine {
|
||||
$latte = new \Latte\Engine;
|
||||
$cache_path = Misc::env('LATTE_CACHE', __DIR__ . '/../../cache/latte');
|
||||
$latte->setTempDirectory($cache_path);
|
||||
|
||||
// -- CUSTOM FUNCTIONS -- //
|
||||
// Get URL with optional endpoint
|
||||
$latte->addFunction('path', function (string $endpoint = ''): string {
|
||||
return Misc::url($endpoint);
|
||||
});
|
||||
// Version being used
|
||||
$latte->addFunction('version', function (): string {
|
||||
return \Composer\InstalledVersions::getVersion('pablouser1/proxitok');
|
||||
});
|
||||
$latte->addFunction('theme', function(): string {
|
||||
return Cookies::theme();
|
||||
});
|
||||
// https://stackoverflow.com/a/36365553
|
||||
$latte->addFunction('number', function (float $x) {
|
||||
if($x > 1000) {
|
||||
$x_number_format = number_format($x);
|
||||
$x_array = explode(',', $x_number_format);
|
||||
$x_parts = array('K', 'M', 'B', 'T');
|
||||
$x_count_parts = count($x_array) - 1;
|
||||
$x_display = $x;
|
||||
$x_display = $x_array[0] . ((int) $x_array[1][0] !== 0 ? '.' . $x_array[1][0] : '');
|
||||
$x_display .= $x_parts[$x_count_parts - 1];
|
||||
return $x_display;
|
||||
}
|
||||
return $x;
|
||||
});
|
||||
return $latte;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup of TikTok Api wrapper
|
||||
* @return \TikScraper\Api|\TikScraper\Legacy
|
||||
*/
|
||||
static public function api() {
|
||||
$options = [
|
||||
'use_test_endpoints' => Misc::env('API_TEST_ENDPOINTS', false),
|
||||
'signer' => [
|
||||
'remote_url' => Misc::env('API_SIGNER_URL', ''),
|
||||
'browser_url' => Misc::env('API_BROWSER_URL', ''),
|
||||
'close_when_done' => false
|
||||
]
|
||||
];
|
||||
// Cache config
|
||||
$cacheEngine = false;
|
||||
if (isset($_ENV['API_CACHE'])) {
|
||||
switch ($_ENV['API_CACHE']) {
|
||||
case 'json':
|
||||
$cacheEngine = new JSONCache();
|
||||
break;
|
||||
case 'redis':
|
||||
if (!(isset($_ENV['REDIS_URL']) || isset($_ENV['REDIS_HOST'], $_ENV['REDIS_PORT']))) {
|
||||
throw new \Exception('You need to set REDIS_URL or REDIS_HOST and REDIS_PORT to use Redis Cache!');
|
||||
}
|
||||
|
||||
if (isset($_ENV['REDIS_URL'])) {
|
||||
$url = parse_url($_ENV['REDIS_URL']);
|
||||
$host = $url['host'];
|
||||
$port = $url['port'];
|
||||
$password = $url['pass'] ?? null;
|
||||
} else {
|
||||
$host = $_ENV['REDIS_HOST'];
|
||||
$port = (int) $_ENV['REDIS_PORT'];
|
||||
$password = isset($_ENV['REDIS_PASSWORD']) ? $_ENV['REDIS_PASSWORD'] : null;
|
||||
}
|
||||
$cacheEngine = new RedisCache($host, $port, $password);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy mode
|
||||
$legacy = Misc::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,34 +0,0 @@
|
|||
<?php
|
||||
namespace App\Models;
|
||||
|
||||
/**
|
||||
* Exclusive for /
|
||||
*/
|
||||
class HomeTemplate extends BaseTemplate {
|
||||
public array $forms = [
|
||||
[
|
||||
'title' => 'Search by user',
|
||||
'input' => 'user',
|
||||
'placeholder' => 'Type username'
|
||||
],
|
||||
[
|
||||
'title' => 'Search by video ID',
|
||||
'input' => 'video',
|
||||
'placeholder' => 'Type video ID'
|
||||
],
|
||||
[
|
||||
'title' => 'Search by tag',
|
||||
'input' => 'tag',
|
||||
'placeholder' => 'Type tag'
|
||||
],
|
||||
[
|
||||
'title' => 'Search by music ID',
|
||||
'input' => 'music',
|
||||
'placeholder' => 'Type music'
|
||||
]
|
||||
];
|
||||
|
||||
function __construct() {
|
||||
parent::__construct('Home');
|
||||
}
|
||||
}
|
19
app/Models/RSSTemplate.php
Normal file
19
app/Models/RSSTemplate.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
namespace App\Models;
|
||||
|
||||
/**
|
||||
* Base for templates with a feed
|
||||
*/
|
||||
class RSSTemplate {
|
||||
public string $title;
|
||||
public string $desc;
|
||||
public string $link;
|
||||
public array $items;
|
||||
|
||||
function __construct(string $title, string $desc, string $link, array $items) {
|
||||
$this->title = $title;
|
||||
$this->desc = $desc;
|
||||
$this->link = $link;
|
||||
$this->items = $items;
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
<?php
|
||||
namespace App\Models;
|
||||
|
||||
use App\Helpers\Cookies;
|
||||
|
||||
/**
|
||||
* Exclusive for /settings
|
||||
*/
|
||||
class SettingsTemplate extends BaseTemplate {
|
||||
public array $proxy_elements = [];
|
||||
|
||||
function __construct() {
|
||||
parent::__construct('Settings');
|
||||
$this->proxy_elements = Cookies::PROXY;
|
||||
}
|
||||
}
|
|
@ -1,74 +1 @@
|
|||
<link rel="stylesheet" href="{path('/styles/feed.css')}">
|
||||
<noscript>JavaScript is required for this section to work!</noscript>
|
||||
<section class="section">
|
||||
<div class="columns is-multiline is-vcentered">
|
||||
{foreach $feed->items as $item}
|
||||
{do $share_url = 'https://tiktok.com/@' . $item->author->uniqueId . '/video/' . $item->id}
|
||||
<div class="column is-one-quarter clickable-img" id="{$item->id}" onclick="openVideo(this.id)"
|
||||
data-video_url="{path('/stream?url=' . urlencode($item->video->playAddr))}"
|
||||
data-video_download_watermark="{path('/download?url=' . urlencode($item->video->playAddr) . '&id=' . $item->id . '&user=' . $item->author->uniqueId) . '&watermark='}"
|
||||
data-video_download_nowatermark="{path('/download?id=' . $item->id . '&user=' . $item->author->uniqueId)}"
|
||||
data-video_share_url="{$share_url}"
|
||||
data-desc="{$item->desc}"
|
||||
data-music_title="{$item->music->title}"
|
||||
data-music_url="{path('/stream?url=' . urlencode($item->music->playUrl))}">
|
||||
<img loading="lazy" src="{path('/stream?url=' . urlencode($item->video->originCover))}" />
|
||||
<img class="hidden" loading="lazy" data-src="{path('/stream?url=' . urlencode($item->video->dynamicCover))}" />
|
||||
</div>
|
||||
{/foreach}
|
||||
{if empty($feed->items)}
|
||||
<p class="title">No items sent by TikTok!</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div n:ifset="$feed->info" class="buttons">
|
||||
{if isset($_GET['cursor']) && $_GET['cursor'] != 0 }
|
||||
<a class="button is-danger" href="?">First</a>
|
||||
<button class="button is-danger" onclick="history.back()">Back</button>
|
||||
{/if}
|
||||
<a n:attr="disabled => !$feed->hasMore" class="button is-success" href="?cursor={$feed->maxCursor}">Next</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- MODAL -->
|
||||
<div id="modal" class="modal">
|
||||
<div id="modal-background" class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<button id="modal-close" class="delete" aria-label="close"></button>
|
||||
<p class="modal-card-title" id="item_title"></p>
|
||||
</header>
|
||||
<section class="modal-card-body has-text-centered" style="overflow: hidden;">
|
||||
<video id="video" autoplay controls></video>
|
||||
</section>
|
||||
<footer class="modal-card-foot has-text-centered">
|
||||
<div class="container">
|
||||
<div class="field has-addons has-addons-centered">
|
||||
<div class="control">
|
||||
<input id="share_input" class="input" readonly />
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-primary" onclick="copyShare()">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="download_dropdown" class="dropdown is-hoverable">
|
||||
<div class="dropdown-trigger">
|
||||
<button id="download_button" class="button" aria-haspopup="true" aria-controls="dropdown-menu">Download</button>
|
||||
</div>
|
||||
<div class="dropdown-menu" role="menu">
|
||||
<div class="dropdown-content">
|
||||
<a id="download_watermark" target="_blank" class="dropdown-item">With watermark</a>
|
||||
<a id="download_nowatermark" target="_blank" class="dropdown-item">Without watermark</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p id="audio_title"></p>
|
||||
<audio id="audio" controls preload="none"></audio>
|
||||
<div class="buttons is-centered">
|
||||
<button id="back-button" class="button is-danger">Back</button>
|
||||
<button id="next-button" class="button is-success">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<script src="{path('/scripts/feed.js')}"></script>
|
||||
{include './themes/' . theme() . '.latte'}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
<form action="{path($path)}" method="POST">
|
||||
<form action="{path($path)}" method="{$method}">
|
||||
{block fields}{/block}
|
||||
{ifset $submit}
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<button class="button is-success" type="submit">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
{/ifset}
|
||||
</form>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
<meta property="og:title" content="ProxiTok" />
|
||||
<meta property="og:description" content="Alternative frontend for TikTok" />
|
||||
<meta property="og:type" content="website" />
|
||||
<link rel="stylesheet" href="{path('/styles/vendor/cssgg.min.css')}">
|
||||
<link rel="stylesheet" href="{path('/styles/vendor/bulma.min.css')}">
|
||||
<title>{$title} - ProxiTok</title>
|
||||
</head>
|
||||
|
|
14
components/icon.latte
Normal file
14
components/icon.latte
Normal file
|
@ -0,0 +1,14 @@
|
|||
{define icon_common, $icon}
|
||||
<span class="icon">
|
||||
<i class="gg-{$icon}"></i>
|
||||
</span>
|
||||
{/define}
|
||||
|
||||
{if isset($text)}
|
||||
<span class="icon-text">
|
||||
{include icon_common, icon: $icon}
|
||||
<span>{$text}</span>
|
||||
</span>
|
||||
{else}
|
||||
{include icon_common, icon: $icon}
|
||||
{/if}
|
|
@ -9,9 +9,13 @@
|
|||
|
||||
<div id="navbar-menu" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
<a href="{path('/')}" class="navbar-item">Home</a>
|
||||
<a href="{path('/')}" class="navbar-item">
|
||||
{include './icon.latte', icon: 'home', text: 'Home'}
|
||||
</a>
|
||||
<a href="{path('/settings')}" class="navbar-item">Settings</a>
|
||||
<a href="{path('/about')}" class="navbar-item">About</a>
|
||||
<a href="{path('/about')}" class="navbar-item">
|
||||
{include './icon.latte', icon: 'info', text: 'About'}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
<a href="{path($_SERVER['REQUEST_URI'] . '/rss')}">RSS Feed</a>
|
||||
<a href="{path($_SERVER['REQUEST_URI'] . '/rss')}">
|
||||
{include './icon.latte', icon: 'feed', text: 'RSS Feed'}
|
||||
</a>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{embed '../form.latte', path: '/settings/api'}
|
||||
{embed '../form.latte', path: '/settings/api', method: 'POST', submit: true}
|
||||
{block fields}
|
||||
<div class="field">
|
||||
<label class="label">Legacy mode</label>
|
||||
|
|
14
components/settings/general.latte
Normal file
14
components/settings/general.latte
Normal file
|
@ -0,0 +1,14 @@
|
|||
{embed '../form.latte', path: '/settings/general', method: 'POST', submit: true}
|
||||
{block fields}
|
||||
<div class="field">
|
||||
<label class="label">Theme</label>
|
||||
<div class="select">
|
||||
<select name="theme">
|
||||
<option hidden disabled selected value> -- Select an option -- </option>
|
||||
<option value="default">Default</option>
|
||||
<option value="card">Card</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/block}
|
||||
{/embed}
|
|
@ -1,12 +0,0 @@
|
|||
{embed '../form.latte', path: '/settings/proxy'}
|
||||
{block fields}
|
||||
{foreach $proxy_elements as $proxy_element}
|
||||
<div class="field">
|
||||
<label class="label">{$proxy_element|firstUpper}</label>
|
||||
<div class="control">
|
||||
<input name="{$proxy_element}" class="input" value="{isset($_COOKIE[$proxy_element]) ? $_COOKIE[$proxy_element] : ''}" required />
|
||||
</div>
|
||||
</div>
|
||||
{/foreach}
|
||||
{/block}
|
||||
{/embed}
|
68
components/themes/card.latte
Normal file
68
components/themes/card.latte
Normal file
|
@ -0,0 +1,68 @@
|
|||
<link rel="stylesheet" href="{path('/styles/themes/card.css')}">
|
||||
<noscript>JavaScript is required for this section to work!</noscript>
|
||||
<section class="section">
|
||||
<div class="columns is-multiline is-vcentered">
|
||||
{foreach $feed->items as $item}
|
||||
{do $share_url = 'https://tiktok.com/@' . $item->author->uniqueId . '/video/' . $item->id}
|
||||
<div class="column is-one-quarter clickable-img" id="{$item->id}" onclick="openVideo(this.id)"
|
||||
data-video_url="{path('/stream?url=' . urlencode($item->video->playAddr))}"
|
||||
data-video_download_watermark="{path('/download?url=' . urlencode($item->video->playAddr) . '&id=' . $item->id . '&user=' . $item->author->uniqueId) . '&watermark='}"
|
||||
data-video_download_nowatermark="{path('/download?id=' . $item->id . '&user=' . $item->author->uniqueId)}"
|
||||
data-video_share_url="{$share_url}"
|
||||
data-desc="{$item->desc}"
|
||||
data-music_title="{$item->music->title}"
|
||||
data-music_url="{path('/stream?url=' . urlencode($item->music->playUrl))}">
|
||||
<img loading="lazy" src="{path('/stream?url=' . urlencode($item->video->originCover))}" />
|
||||
<img class="hidden" loading="lazy" data-src="{path('/stream?url=' . urlencode($item->video->dynamicCover))}" />
|
||||
</div>
|
||||
{/foreach}
|
||||
{if empty($feed->items)}
|
||||
<p class="title">No items sent by TikTok!</p>
|
||||
{/if}
|
||||
</div>
|
||||
{include './common/controls.latte'}
|
||||
</section>
|
||||
|
||||
<!-- MODAL -->
|
||||
<div id="modal" class="modal">
|
||||
<div id="modal-background" class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<button id="modal-close" class="delete" aria-label="close"></button>
|
||||
<p class="modal-card-title" id="item_title"></p>
|
||||
</header>
|
||||
<section class="modal-card-body has-text-centered" style="overflow: hidden;">
|
||||
<video id="video" autoplay controls></video>
|
||||
</section>
|
||||
<footer class="modal-card-foot has-text-centered">
|
||||
<div class="container">
|
||||
<div class="field has-addons has-addons-centered">
|
||||
<div class="control">
|
||||
<input id="share_input" class="input" readonly />
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-primary" onclick="copyShare()">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="download_dropdown" class="dropdown is-hoverable">
|
||||
<div class="dropdown-trigger">
|
||||
<button id="download_button" class="button" aria-haspopup="true" aria-controls="dropdown-menu">Download</button>
|
||||
</div>
|
||||
<div class="dropdown-menu" role="menu">
|
||||
<div class="dropdown-content">
|
||||
<a id="download_watermark" target="_blank" class="dropdown-item">With watermark</a>
|
||||
<a id="download_nowatermark" target="_blank" class="dropdown-item">Without watermark</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p id="audio_title"></p>
|
||||
<audio id="audio" controls preload="none"></audio>
|
||||
<div class="buttons is-centered">
|
||||
<button id="back-button" class="button is-danger">Back</button>
|
||||
<button id="next-button" class="button is-success">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<script src="{path('/scripts/themes/card.js')}"></script>
|
8
components/themes/common/controls.latte
Normal file
8
components/themes/common/controls.latte
Normal file
|
@ -0,0 +1,8 @@
|
|||
<div n:ifset="$feed->info" class="buttons">
|
||||
{* is_numeric is used to avoid having a back button with ttwid cursors *}
|
||||
{if isset($_GET['cursor']) && is_numeric($_GET['cursor']) && $_GET['cursor'] != 0 }
|
||||
<a class="button is-danger" href="?cursor=0">First</a>
|
||||
<a class="button is-danger" href="?cursor={$feed->minCursor}">Back</a>
|
||||
{/if}
|
||||
<a n:attr="disabled => !$feed->hasMore" class="button is-success" href="?cursor={$feed->maxCursor}">Next</a>
|
||||
</div>
|
13
components/themes/common/download.latte
Normal file
13
components/themes/common/download.latte
Normal file
|
@ -0,0 +1,13 @@
|
|||
<div class="dropdown is-hoverable">
|
||||
<div class="dropdown-trigger">
|
||||
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu">
|
||||
{include '../../icon.latte', icon: 'software-download', text: 'Download'}
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-menu" role="menu">
|
||||
<div class="dropdown-content">
|
||||
<a target="_blank" href="{path('/download?url=' . urlencode($playAddr) . '&id=' . $item->id . '&user=' . $uniqueId) . '&watermark=1'}" class="dropdown-item">Watermark</a>
|
||||
<a target="_blank" href="{path('/download?id=' . $id . '&user=' . $uniqueId)}" class="dropdown-item">No watermark</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
5
components/themes/common/share.latte
Normal file
5
components/themes/common/share.latte
Normal file
|
@ -0,0 +1,5 @@
|
|||
{do $endpoint = '/@' . $uniqueId . '/video/' . $id}
|
||||
<div class="buttons is-centered">
|
||||
<a class="button is-success is-small" href="{path($endpoint)}">Instace Link</a>
|
||||
<a class="button is-danger is-small" href="https://www.tiktok.com{$endpoint}">Original Link</a>
|
||||
</div>
|
6
components/themes/common/stats.latte
Normal file
6
components/themes/common/stats.latte
Normal file
|
@ -0,0 +1,6 @@
|
|||
<p>
|
||||
{include '../../icon.latte', icon: 'eye', text: number($playCount)}
|
||||
{include '../../icon.latte', icon: 'heart', text: number($diggCount)}
|
||||
{include '../../icon.latte', icon: 'comment', text: number($commentCount)}
|
||||
{include '../../icon.latte', icon: 'share', text: number($shareCount)}
|
||||
</p>
|
5
components/themes/common/tags.latte
Normal file
5
components/themes/common/tags.latte
Normal file
|
@ -0,0 +1,5 @@
|
|||
<div class="tags">
|
||||
{foreach $challenges as $challenge}
|
||||
<a class="tag is-rounded is-info" href="{path('/tag/' . $challenge->title)}">{$challenge->title}</a>
|
||||
{/foreach}
|
||||
</div>
|
36
components/themes/default.latte
Normal file
36
components/themes/default.latte
Normal file
|
@ -0,0 +1,36 @@
|
|||
<div class="container">
|
||||
{foreach $feed->items as $item}
|
||||
<article class="media">
|
||||
<figure class="media-left">
|
||||
<p class="image is-64x64">
|
||||
<img src="{path('/stream?url=' . urlencode($item->author->avatarThumb))}" />
|
||||
</p>
|
||||
</figure>
|
||||
<div class="media-content">
|
||||
<div class="content">
|
||||
<p>
|
||||
<strong>{$item->author->nickname}</strong> <small><a href="{path('/@' . $item->author->uniqueId)}">@{$item->author->uniqueId}</a></small>
|
||||
</p>
|
||||
{if !empty($item->challenges)}
|
||||
<p>{include './common/tags.latte', challenges: $item->challenges}</p>
|
||||
{/if}
|
||||
<p n:ifcontent>{$item->desc}</p>
|
||||
{include './common/stats.latte', playCount: $item->stats->playCount, diggCount: $item->stats->diggCount, commentCount: $item->stats->commentCount, shareCount: $item->stats->shareCount}
|
||||
</div>
|
||||
<div class="has-text-centered">
|
||||
<video width="{$item->video->width}" height="{$item->video->height}" controls preload="none" poster="{path('/stream?url=' . urlencode($item->video->originCover))}">
|
||||
<source src="{path('/stream?url=' . $item->video->playAddr)}" type="video/mp4" />
|
||||
</video>
|
||||
</div>
|
||||
<div class="has-text-centered">
|
||||
{include './common/share.latte', uniqueId: $item->author->uniqueId, id: $item->id}
|
||||
{include './common/download.latte', playAddr: $item->video->playAddr, id: $item->id, uniqueId: $item->author->uniqueId}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{/foreach}
|
||||
{if empty($feed->items)}
|
||||
<p class="title">No items sent by TikTok!</p>
|
||||
{/if}
|
||||
</div>
|
||||
{include './common/controls.latte'}
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "pablouser1/proxitok",
|
||||
"description": "An alternative frontend for TikTok",
|
||||
"version": "2.1.0.1",
|
||||
"version": "2.2.0.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"type": "project",
|
||||
"homepage": "https://github.com/pablouser1/ProxiTok",
|
||||
|
@ -17,7 +17,6 @@
|
|||
"latte/latte": "^2.10",
|
||||
"vlucas/phpdotenv": "^5.4",
|
||||
"bramus/router": "^1.6",
|
||||
"mibe/feedwriter": "^1.1",
|
||||
"pablouser1/tikscraper": "^1.3"
|
||||
},
|
||||
"autoload": {
|
||||
|
|
115
composer.lock
generated
115
composer.lock
generated
|
@ -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": "f6b066c447574f88ad9255a88892659e",
|
||||
"content-hash": "def7d3d4bbdae6916d99e50b33b3595a",
|
||||
"packages": [
|
||||
{
|
||||
"name": "bramus/router",
|
||||
|
@ -203,119 +203,18 @@
|
|||
},
|
||||
"time": "2022-02-22T18:39:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mibe/feedwriter",
|
||||
"version": "v1.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mibe/FeedWriter.git",
|
||||
"reference": "f4cc748ad8700e36663f08cfeebe7fd39b00eea2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/mibe/FeedWriter/zipball/f4cc748ad8700e36663f08cfeebe7fd39b00eea2",
|
||||
"reference": "f4cc748ad8700e36663f08cfeebe7fd39b00eea2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"FeedWriter\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"GPL-3.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Michael Bemmerl",
|
||||
"email": "mail@mx-server.de"
|
||||
},
|
||||
{
|
||||
"name": "Phil Freo"
|
||||
},
|
||||
{
|
||||
"name": "Paul Ferrett"
|
||||
},
|
||||
{
|
||||
"name": "Brennen Bearnes"
|
||||
},
|
||||
{
|
||||
"name": "Michael Robinson",
|
||||
"email": "mike@pagesofinterest.net"
|
||||
},
|
||||
{
|
||||
"name": "Baptiste Fontaine"
|
||||
},
|
||||
{
|
||||
"name": "Kristián Valentín"
|
||||
},
|
||||
{
|
||||
"name": "Brandtley McMinn"
|
||||
},
|
||||
{
|
||||
"name": "Julian Bogdani"
|
||||
},
|
||||
{
|
||||
"name": "Anis Uddin Ahmad",
|
||||
"email": "anis.programmer@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Cedric Gampert"
|
||||
},
|
||||
{
|
||||
"name": "Yamek"
|
||||
},
|
||||
{
|
||||
"name": "thielj"
|
||||
},
|
||||
{
|
||||
"name": "Pavel Khakhlou"
|
||||
},
|
||||
{
|
||||
"name": "Daniel"
|
||||
},
|
||||
{
|
||||
"name": "Tino Goratsch"
|
||||
}
|
||||
],
|
||||
"description": "Generate feeds in either RSS 1.0, RSS 2.0 or ATOM formats",
|
||||
"homepage": "https://github.com/mibe/FeedWriter",
|
||||
"keywords": [
|
||||
"RSS 1.0",
|
||||
"atom",
|
||||
"feed",
|
||||
"rss",
|
||||
"rss 2.0",
|
||||
"rss2"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/mibe/FeedWriter/issues",
|
||||
"source": "https://github.com/mibe/FeedWriter/tree/master"
|
||||
},
|
||||
"time": "2016-11-19T20:47:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pablouser1/tikscraper",
|
||||
"version": "v1.3.1.0",
|
||||
"version": "v1.3.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pablouser1/TikScraperPHP.git",
|
||||
"reference": "7edba857ea7bccff3d77d487f2ee041e526ee16c"
|
||||
"reference": "8aa4524d3f11c4087dcae565a874b9440b1468c7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pablouser1/TikScraperPHP/zipball/7edba857ea7bccff3d77d487f2ee041e526ee16c",
|
||||
"reference": "7edba857ea7bccff3d77d487f2ee041e526ee16c",
|
||||
"url": "https://api.github.com/repos/pablouser1/TikScraperPHP/zipball/8aa4524d3f11c4087dcae565a874b9440b1468c7",
|
||||
"reference": "8aa4524d3f11c4087dcae565a874b9440b1468c7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -341,9 +240,9 @@
|
|||
"description": "Get data from TikTok API",
|
||||
"support": {
|
||||
"issues": "https://github.com/pablouser1/TikScraperPHP/issues",
|
||||
"source": "https://github.com/pablouser1/TikScraperPHP/tree/v1.3.1.0"
|
||||
"source": "https://github.com/pablouser1/TikScraperPHP/tree/v1.3.2.0"
|
||||
},
|
||||
"time": "2022-03-28T16:02:56+00:00"
|
||||
"time": "2022-03-29T17:10:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "php-webdriver/webdriver",
|
||||
|
|
16
routes.php
16
routes.php
|
@ -1,21 +1,21 @@
|
|||
<?php
|
||||
/** @var \Bramus\Router\Router $router */
|
||||
use App\Helpers\Misc;
|
||||
use App\Helpers\Wrappers;
|
||||
use App\Models\BaseTemplate;
|
||||
use App\Models\HomeTemplate;
|
||||
|
||||
$router->get('/', function () {
|
||||
$latte = Misc::latte();
|
||||
$latte->render(Misc::getView('home'), new HomeTemplate);
|
||||
$latte = Wrappers::latte();
|
||||
$latte->render(Misc::getView('home'), new BaseTemplate('Home'));
|
||||
});
|
||||
|
||||
$router->get('/about', function () {
|
||||
$latte = Misc::latte();
|
||||
$latte = Wrappers::latte();
|
||||
$latte->render(Misc::getView('about'), new BaseTemplate('About'));
|
||||
});
|
||||
|
||||
$router->get('/verify', function () {
|
||||
$latte = Misc::latte();
|
||||
$latte = Wrappers::latte();
|
||||
$latte->render(Misc::getView('verify'), new BaseTemplate('Verify'));
|
||||
});
|
||||
|
||||
|
@ -39,8 +39,8 @@ $router->mount('/@([^/]+)', function () use ($router) {
|
|||
*/
|
||||
$router->get('/video/(\w+)', 'VideoController@get');
|
||||
|
||||
$router->mount('/tag', function () use ($router) {
|
||||
$router->get('/([^/]+)', 'TagController@get');
|
||||
$router->mount('/tag/([^/]+)', function () use ($router) {
|
||||
$router->get('/', 'TagController@get');
|
||||
$router->get('/rss', 'TagController@rss');
|
||||
});
|
||||
|
||||
|
@ -49,7 +49,7 @@ $router->get('/music/([^/]+)', 'MusicController@get');
|
|||
// -- Settings -- //
|
||||
$router->mount('/settings', function () use ($router) {
|
||||
$router->get('/', 'SettingsController@index');
|
||||
$router->post('/proxy', 'SettingsController@proxy');
|
||||
$router->post('/general', 'SettingsController@general');
|
||||
$router->post('/api', 'SettingsController@api');
|
||||
});
|
||||
|
||||
|
|
6
scss/bulma.scss
vendored
6
scss/bulma.scss
vendored
|
@ -11,8 +11,9 @@ $bulmaswatch-import-font: false;
|
|||
|
||||
// Elements
|
||||
@import "./node_modules/bulma/sass/elements/button.sass";
|
||||
@import "./node_modules/bulma/sass/elements/box.sass";
|
||||
@import "./node_modules/bulma/sass/elements/container.sass";
|
||||
@import "./node_modules/bulma/sass/elements/icon.sass";
|
||||
@import "./node_modules/bulma/sass/elements/image.sass";
|
||||
@import "./node_modules/bulma/sass/elements/other.sass";
|
||||
@import "./node_modules/bulma/sass/elements/tag.sass";
|
||||
@import "./node_modules/bulma/sass/elements/title.sass";
|
||||
|
@ -21,14 +22,15 @@ $bulmaswatch-import-font: false;
|
|||
@import "./node_modules/bulma/sass/form/shared.sass";
|
||||
@import "./node_modules/bulma/sass/form/input-textarea.sass";
|
||||
@import "./node_modules/bulma/sass/form/checkbox-radio.sass";
|
||||
@import "./node_modules/bulma/sass/form/select.sass";
|
||||
@import "./node_modules/bulma/sass/form/tools.sass";
|
||||
|
||||
// Components
|
||||
@import "./node_modules/bulma/sass/components/card.sass";
|
||||
@import "./node_modules/bulma/sass/components/dropdown.sass";
|
||||
@import "./node_modules/bulma/sass/components/media.sass";
|
||||
@import "./node_modules/bulma/sass/components/modal.sass";
|
||||
@import "./node_modules/bulma/sass/components/navbar.sass";
|
||||
@import "./node_modules/bulma/sass/components/media.sass";
|
||||
|
||||
// Grids
|
||||
@import "./node_modules/bulma/sass/grid/columns.sass";
|
||||
|
|
15
scss/cssgg.scss
Normal file
15
scss/cssgg.scss
Normal file
|
@ -0,0 +1,15 @@
|
|||
@charset "utf-8";
|
||||
// Common
|
||||
@import "./node_modules/css.gg/icons/scss/home.scss";
|
||||
@import "./node_modules/css.gg/icons/scss/info.scss";
|
||||
|
||||
// Home
|
||||
@import "./node_modules/css.gg/icons/scss/search.scss";
|
||||
|
||||
// Feed
|
||||
@import "./node_modules/css.gg/icons/scss/feed.scss";
|
||||
@import "./node_modules/css.gg/icons/scss/eye.scss";
|
||||
@import "./node_modules/css.gg/icons/scss/heart.scss";
|
||||
@import "./node_modules/css.gg/icons/scss/comment.scss";
|
||||
@import "./node_modules/css.gg/icons/scss/share.scss";
|
||||
@import "./node_modules/css.gg/icons/scss/software-download.scss";
|
|
@ -2,11 +2,13 @@
|
|||
"name": "proxitok-scss",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"bulma": "sass --style=compressed bulma.scss ../styles/vendor/bulma.min.css"
|
||||
"bulma": "sass --style=compressed bulma.scss ../styles/vendor/bulma.min.css",
|
||||
"cssgg": "sass --style=compressed cssgg.scss ../styles/vendor/cssgg.min.css"
|
||||
},
|
||||
"dependencies": {
|
||||
"bulma": "^0.9.3",
|
||||
"bulmaswatch": "^0.8.1",
|
||||
"css.gg": "^2.0.0",
|
||||
"sass": "^1.46.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,11 @@ bulmaswatch@^0.8.1:
|
|||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
css.gg@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/css.gg/-/css.gg-2.0.0.tgz#1ba891092ceac58a2550540171c2d469a86b27b2"
|
||||
integrity sha512-8qO59kSXLbewo7xaUM4eC8328MTdGmb8atpmw6PRzeDGsZrCkAVjJzKeFDIntagl1/aCee+NG1OQtIvWAjgG/A==
|
||||
|
||||
fill-range@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
|
||||
|
|
2
styles/vendor/bulma.min.css
vendored
2
styles/vendor/bulma.min.css
vendored
File diff suppressed because one or more lines are too long
2
styles/vendor/bulma.min.css.map
vendored
2
styles/vendor/bulma.min.css.map
vendored
File diff suppressed because one or more lines are too long
1
styles/vendor/cssgg.min.css
vendored
Normal file
1
styles/vendor/cssgg.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
styles/vendor/cssgg.min.css.map
vendored
Normal file
1
styles/vendor/cssgg.min.css.map
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"sourceRoot":"","sources":["../../scss/node_modules/css.gg/icons/scss/home.scss","../../scss/node_modules/css.gg/icons/scss/info.scss","../../scss/node_modules/css.gg/icons/scss/search.scss","../../scss/node_modules/css.gg/icons/scss/feed.scss","../../scss/node_modules/css.gg/icons/scss/eye.scss","../../scss/node_modules/css.gg/icons/scss/heart.scss","../../scss/node_modules/css.gg/icons/scss/comment.scss","../../scss/node_modules/css.gg/icons/scss/share.scss","../../scss/node_modules/css.gg/icons/scss/software-download.scss"],"names":[],"mappings":"AAAA,SACE,yLACA,sBACA,kBACA,cACA,+BACA,WACA,YACA,iBACA,aACA,gBACA,4BACA,2BACA,6BACA,4BACA,mBAEA,gBACE,WACA,cACA,sBACA,kBAGF,iBACE,WACA,cACA,sBACA,kBACA,qBACA,sBACA,2BACA,wBACA,SACA,kBACA,WACA,YACA,OAGF,gBACE,UACA,YACA,iBACA,oBACA,4BACA,6BACA,gBACA,SACA,SCjDJ,SACI,sBACA,kBACA,cACA,+BACA,WACA,YACA,iBACA,mBAEA,iCACE,WACA,cACA,sBACA,kBACA,kBACA,UACA,wBACA,SAGF,gBACE,WACA,WAGF,iBACE,WACA,QC5BN,WACI,sBACA,kBACA,cACA,+BACA,WACA,YACA,iBACA,mBACA,iBACA,gBAEA,kBACE,WACA,cACA,sBACA,kBACA,kBACA,UACA,WACA,wBACA,yBACA,SACA,UCvBN,SACI,cACA,sBACA,wBACA,sBASA,iBACA,gBACA,kBACA,+BACA,UACA,WACA,kBAbA,iCACE,cACA,sBACA,wBACA,sBAWF,iCACE,WACA,kBACA,kBACA,WACA,WACA,QACA,SACA,WAGF,gBACE,UACA,QACA,WCnCN,QACI,kBACA,cACA,+BACA,WACA,YACA,iCACA,gCACA,gBACA,sBAEA,+BACE,WACA,cACA,oBACA,kBACA,sBAGF,eACE,QACA,8CACA,WACA,YAGF,gBACE,UACA,WACA,iBACA,WACA,SC/BN,UACI,iBACA,6BACA,8BACA,WACA,WACA,gBAeA,sBACA,kBACA,gHACA,cAhBA,iBACE,iBACA,6BACA,8BACA,WACA,WACA,gBACA,WACA,cACA,sBACA,kBAQF,kBACE,WACA,cACA,sBACA,kBAGF,iBACE,WACA,wBACA,QAGF,kBACE,WACA,YACA,sBACA,wBACA,UACA,QC7CN,YACI,sBACA,kBACA,cACA,+BACA,WACA,YACA,iBACA,gBACA,0CAEA,mBACE,WACA,cACA,sBACA,kBACA,UAGF,oBACE,WACA,cACA,sBACA,kBACA,UACA,iBACA,6BACA,+BACA,UACA,YACA,WAGF,mBACE,WACA,wBACA,qBACA,SACA,QCtCN,UACI,sBACA,kBACA,cACA,+BACA,UACA,WACA,wBACA,oBACA,kCAEA,iBACE,WACA,cACA,sBACA,kBACA,kBACA,WACA,WACA,wBACA,SAGF,kBACE,WACA,cACA,sBACA,kBACA,kBACA,WACA,WACA,wBACA,SACA,MACA,yBAGF,iBACE,SACA,wBCvCN,sBACI,sBACA,kBACA,cACA,+BACA,WACA,WACA,iBACA,aACA,8BACA,+BACA,eAEA,6BACE,WACA,cACA,sBACA,kBACA,UACA,WACA,sBACA,wBACA,yBACA,SACA,WAGF,8BACE,WACA,cACA,sBACA,kBACA,kBACA,UACA,YACA,wBACA,SACA","file":"cssgg.min.css"}
|
|
@ -8,7 +8,6 @@
|
|||
{block content}
|
||||
<p class="title">About this instance</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>
|
||||
<p>
|
||||
|
@ -29,7 +28,6 @@
|
|||
<li><a rel="nofollow" href="https://github.com/bramus/router">bramus/router</a></li>
|
||||
<li><a rel="nofollow" href="https://github.com/vlucas/phpdotenv">PHP dotenv</a></li>
|
||||
<li><a rel="nofollow" href="https://github.com/jgthms/bulma">Bulma</a> and <a href="https://github.com/jenil/bulmaswatch">Bulmaswatch</a></li>
|
||||
<li><a rel="nofollow" href="https://github.com/mibe/FeedWriter">FeedWriter</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
{/block}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
{if !empty($item->cardItem->cover)}
|
||||
<div class="media-left">
|
||||
<figure class="image is-96x96">
|
||||
<img width="96" height="96" src="{path('/stream?url=' . urlencode($item->cardItem->cover))}" />
|
||||
<img loading="lazy" width="96" height="96" src="{path('/stream?url=' . urlencode($item->cardItem->cover))}" />
|
||||
</figure>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -3,28 +3,30 @@
|
|||
{block content}
|
||||
<p class="title">Welcome to ProxiTok!</p>
|
||||
<p class="subtitle">An alternative open source frontend for TikTok</p>
|
||||
<!-- Create forms from App\Models\HomeTemplate -->
|
||||
<div class="columns is-centered is-vcentered is-multiline">
|
||||
{foreach $forms as $form}
|
||||
<div class="column is-narrow">
|
||||
{embed '../components/card.latte'}
|
||||
{block title}{$form['title']}{/block}
|
||||
{block content}
|
||||
<form action="{path('/redirect')}">
|
||||
{embed '../components/form.latte', path: '/redirect', method: 'GET'}
|
||||
{block fields}
|
||||
<div class="field has-addons has-addons-centered">
|
||||
<div class="control">
|
||||
<input name="{$form['input']}" class="input" type="text" placeholder="{$form['placeholder']}" required />
|
||||
<input class="input" name="term" placeholder="Search" required>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-success" type="submit">Search</button>
|
||||
<div class="select">
|
||||
<select name="type">
|
||||
<option value="user">Username</option>
|
||||
<option value="tag">Tag</option>
|
||||
<option value="music">Music ID</option>
|
||||
<option value="video">Video ID</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button type="submit" class="button is-success">
|
||||
{include '../components/icon.latte', icon: 'search', text: 'Search'}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{/block}
|
||||
{/embed}
|
||||
</div>
|
||||
{/foreach}
|
||||
</div>
|
||||
<div class="columns is-centered is-mobile">
|
||||
<div class="column is-narrow">
|
||||
<p>Discover</p>
|
||||
|
|
23
views/rss.latte
Normal file
23
views/rss.latte
Normal file
|
@ -0,0 +1,23 @@
|
|||
{contentType application/rss+xml}
|
||||
{do header('Content-Disposition: attachment; filename="' . $title . '.rss' . '"')}
|
||||
{do $full_link = path($link)}
|
||||
{var $download = new \TikScraper\Download}
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title>{$title}</title>
|
||||
<description><![CDATA[{$desc}]]></description>
|
||||
<link>{$full_link}</link>
|
||||
<atom:link href="{$full_link . '/rss'}" rel="self" type="application/rss+xml"></atom:link>
|
||||
{foreach $items as $item}
|
||||
<item>
|
||||
<title>{$item->desc}</title>
|
||||
<description><![CDATA[{$item->desc}]]></description>
|
||||
<link>{path('/@' . $item->author->uniqueId . '/video/' . $item->id)}</link>
|
||||
<pubDate>{(int) $item->createTime}</pubDate>
|
||||
<guid isPermaLink="false">{$item->id}</guid>
|
||||
<enclosure length="{$download->file_size($item->video->playAddr)}" type="video/mp4" url="{path('/stream?url=' . urlencode($item->video->playAddr))}"></enclosure>
|
||||
</item>
|
||||
{/foreach}
|
||||
</channel>
|
||||
</rss>
|
|
@ -5,10 +5,9 @@
|
|||
{/block}
|
||||
|
||||
{block content}
|
||||
<!-- Proxy settings -->
|
||||
<p class="title">General</p>
|
||||
{include '../components/settings/general.latte'}
|
||||
<hr />
|
||||
<p class="title">Api</p>
|
||||
{include '../components/settings/api.latte'}
|
||||
<hr />
|
||||
<p class="title">Proxy</p>
|
||||
{include '../components/settings/proxy.latte'}
|
||||
{/block}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
{do $item = $feed->items[0]}
|
||||
<div class="columns is-centered is-vcentered">
|
||||
<div class="column">
|
||||
<video controls poster="{path('/stream?url=' . urlencode($item->video->originCover))}">
|
||||
<video width="{$item->video->width}" height="{$item->video->height}" controls poster="{path('/stream?url=' . urlencode($item->video->originCover))}">
|
||||
<source src="{path('/stream?url=' . urlencode($item->video->playAddr))}" type="video/mp4" />
|
||||
</video>
|
||||
</div>
|
||||
|
@ -12,20 +12,10 @@
|
|||
<div class="box">
|
||||
<p class="title">Video by <a href="{path('/@'.$feed->info->detail->uniqueId)}">{$feed->info->detail->uniqueId}</a></p>
|
||||
<p class="subtitle">{$item->desc}</p>
|
||||
<p>Played {number($feed->info->stats->playCount)} times</p>
|
||||
<p>Shared {number($feed->info->stats->shareCount)} times / {number($feed->info->stats->commentCount)} comments</p>
|
||||
{include '../components/themes/common/stats.latte', playCount: $item->stats->playCount, diggCount: $item->stats->diggCount, commentCount: $item->stats->commentCount, shareCount: $item->stats->shareCount}
|
||||
<hr />
|
||||
<div class="dropdown is-hoverable">
|
||||
<div class="dropdown-trigger">
|
||||
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu">Download</button>
|
||||
</div>
|
||||
<div class="dropdown-menu" role="menu">
|
||||
<div class="dropdown-content">
|
||||
<a href="{path('/download?url=' . urlencode($item->video->playAddr) . '&id=' . $item->id . '&user=' . $feed->info->detail->uniqueId) . '&watermark='}" class="dropdown-item">Watermark</a>
|
||||
<a href="{path('/download?id=' . $item->id . '&user=' . $feed->info->detail->uniqueId)}" class="dropdown-item">No watermark</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{include '../components/themes/common/share.latte', uniqueId: $feed->info->detail->uniqueId, id: $item->id}
|
||||
{include '../components/themes/common/download.latte', playAddr: $item->video->playAddr, id: $item->id, uniqueId: $feed->info->detail->uniqueId}
|
||||
<p>{$item->music->title}</p>
|
||||
<audio src="{path('/stream?url=' . urlencode($item->music->playUrl))}" controls preload="none"></audio>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue