Compare commits

...

26 Commits

Author SHA1 Message Date
Pablo Ferreiro 7011867fd8 Merge pull request #18 from pablouser1/new-api
New Wrapper using TikTok's new Api
2022-03-11 23:00:00 +01:00
Pablo Ferreiro a4a4b0497c Merged with new-api 2022-03-11 22:59:16 +01:00
Pablo Ferreiro f1de8e979b Fixed trending, added explanation on VERIFY_CODE 2022-03-11 22:46:12 +01:00
Pablo Ferreiro 1e1376506c Bump wrapper 2022-03-11 21:02:34 +01:00
Pablo Ferreiro 198c88a1d7 Bump wrapper 2022-03-11 20:55:30 +01:00
Pablo Ferreiro 73dd93420f Force legacy mode, instance level and user level 2022-03-11 20:50:11 +01:00
Pablo Ferreiro db3799c344 public folder, update wrapper 2022-03-11 18:56:19 +01:00
Pablo Ferreiro 1eb6626153 Added Redirector 2022-03-11 16:11:56 +01:00
Pablo Ferreiro c413017b00 Merge pull request #14 from IronMaltese/patch-1
Libredirect
2022-03-06 12:51:25 +01:00
IronMaltese 669c0d29d4 Update README.md 2022-03-05 16:50:47 -05:00
Pablo Ferreiro 58170665ed Dropdown and updated wrapper 2022-02-24 21:13:38 +01:00
Pablo Ferreiro d60e4400a2 Instance-level proxy and test endpoint fix 2022-02-20 16:25:15 +01:00
Pablo Ferreiro c47d6cd6d4 Reverted to last usable version 2022-02-16 15:20:35 +01:00
Pablo Ferreiro 279a4f50c6 Updated composer.lock 2022-02-16 12:54:28 +01:00
Pablo Ferreiro 9711bb903c Updated Wrapper 2022-02-15 13:55:36 +01:00
Pablo Ferreiro a11a17f9d2 Fixed rss 2022-02-13 23:40:56 +01:00
Pablo Ferreiro c690adab63 Upstream 2022-02-13 22:43:46 +01:00
Pablo Ferreiro 1d37d8c417 Fixed trending 2022-02-13 22:30:47 +01:00
Pablo Ferreiro 90cf0bfb56 Fix Redis 2022-02-13 22:27:21 +01:00
Pablo Ferreiro a3ebb08d26 Using new wrapper 2022-02-13 22:21:08 +01:00
Pablo Ferreiro 19e2c32c77 Merge pull request #10 from ManeraKai/fix-share-video-link
Fixed Video Share Link
2022-02-13 11:58:53 +01:00
ManeraKai 0c4616c695 Fixed Video Share Link 2022-02-13 12:57:08 +03:00
Pablo Ferreiro fccec753d9 No watermark without external service 2022-02-07 23:45:07 +01:00
Pablo Ferreiro 7203bc1509 Share url, removed hash 2022-02-07 22:23:33 +01:00
Pablo Ferreiro d871ed4e68 hotfix 2022-02-07 21:13:34 +01:00
Pablo Ferreiro 2c73bd0388 Allow download without watermark 2022-02-07 21:07:51 +01:00
41 changed files with 491 additions and 286 deletions
+10
View File
@@ -1,5 +1,15 @@
# APP_URL="http://localhost:8000" # Full url path, PLEASE REPLACE TO YOUR OWN ONE
# 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
# 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)
-13
View File
@@ -1,13 +0,0 @@
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . index.php [L]
# Disable index view
Options -Indexes
# Hide a specific file
<Files .env>
Order allow,deny
Deny from all
</Files>
+1 -1
View File
@@ -1 +1 @@
web: vendor/bin/heroku-php-apache2
web: vendor/bin/heroku-php-apache2 public/
+17 -2
View File
@@ -8,9 +8,24 @@ Use Tiktok with an alternative frontend, inspired by Nitter.
* See tags
* See video by id
* Discovery
* 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)
## Extension
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)
You can use the following config if you want to use Redirector (you can change https://proxitok.herokuapp.com with whatever instance you want to use):
```
Description: TikTok to ProxiTok
Example URL: https://www.tiktok.com/@tiktok
Include pattern: (.*//.*)(tiktok.com)(.*)
Redirect to: https://proxitok.herokuapp.com$3
Example result: https://proxitok.herokuapp.com/@tiktok
Pattern type: Regular Expression
Apply to: Main window (address bar)
```
## Installation
Clone the repository and fetch the requiered external packages with:
```bash
@@ -21,7 +36,7 @@ WARNING: You'll need a personal Github token for composer.
Then you can run it using for example the PHP Development Server with:
```bash
php -S localhost:8080
php -S localhost:8080 -t public
```
## Configuration
+40 -16
View File
@@ -9,31 +9,55 @@ class ProxyController {
static private function isValidDomain(string $url) {
$host = parse_url($url, PHP_URL_HOST);
$host_split = explode('.', $host);
return count($host_split) === 3 && in_array($host_split[1] . '.' . $host_split[2], self::VALID_TIKTOK_DOMAINS);
$host_count = count($host_split);
if ($host_count === 2) {
// Using no watermark
return in_array($host_split[0] . '.' . $host_split[1], self::VALID_TIKTOK_DOMAINS);
} elseif ($host_count === 3) {
return in_array($host_split[1] . '.' . $host_split[2], self::VALID_TIKTOK_DOMAINS);
}
return false;
}
static public function stream() {
static private function checkUrl() {
if (!isset($_GET['url'])) {
die('You need to send a url!');
die('You need to send a URL');
}
$url = $_GET['url'];
if (!filter_var($url, FILTER_VALIDATE_URL) || !self::isValidDomain($url)) {
if (!filter_var($_GET['url'], FILTER_VALIDATE_URL) || !self::isValidDomain($_GET['url'])) {
die('Not a valid URL');
}
if (isset($_GET['download'])) {
// Download
$downloader = new \Sovit\TikTok\Download();
$filename = 'tiktok-video';
if (isset($_GET['id'], $_GET['user'])) {
$filename .= '-' . $_GET['user'] . '-' . $_GET['id'];
}
$downloader->url($url, $filename, 'mp4');
}
static private function getFileName(): string {
$filename = 'tiktok-video';
if (isset($_GET['user'])) {
$filename .= '-' . $_GET['user'] . '-' . $_GET['id'];
}
return $filename;
}
static public function stream() {
self::checkUrl();
$url = $_GET['url'];
$streamer = new \TikScraper\Stream();
$streamer->url($url);
}
static public function download() {
$downloader = new \TikScraper\Download();
$watermark = isset($_GET['watermark']);
if ($watermark) {
self::checkUrl();
$filename = self::getFileName();
$downloader->url($_GET['url'], $filename, true);
} else {
// Stream
$streamer = new \Sovit\TikTok\Stream();
$streamer->stream($url);
if (!isset($_GET['id'])) {
die('You need to send an ID!');
}
$filename = self::getFileName();
$downloader->url($_GET['id'], $filename, false);
}
}
}
+7 -1
View File
@@ -8,7 +8,7 @@ use App\Models\SettingsTemplate;
class SettingsController {
static public function index() {
$latte = Misc::latte();
$latte->render(Misc::getView('settings'), new SettingsTemplate());
$latte->render(Misc::getView('settings'), new SettingsTemplate);
}
static public function proxy() {
@@ -20,4 +20,10 @@ class SettingsController {
$url = Misc::url('/settings');
header("Location: {$url}");
}
static public function api() {
$_POST['legacy'] ?? Cookies::set('api-legacy', '1');
$url = Misc::url('/settings');
header("Location: {$url}");
}
}
+9 -2
View File
@@ -8,9 +8,16 @@ use App\Helpers\RSS;
class TrendingController {
static public function get() {
$cursor = Misc::getCursor();
$cursor = Misc::getTtwid();
$api = Misc::api();
$feed = $api->getTrendingFeed($cursor);
// Ttwid if normal, cursor if legacy
if ($api::class === 'TikScraper\Api') {
$cursor = Misc::getTtwid();
} else {
$cursor = Misc::getCursor();
}
$feed = $api->getTrending($cursor);
if ($feed->meta->success) {
$latte = Misc::latte();
$latte->render(Misc::getView('trending'), new FeedTemplate('Trending', $feed));
+3 -2
View File
@@ -12,9 +12,10 @@ class UserController {
$api = Misc::api();
$feed = $api->getUserFeed($username, $cursor);
if ($feed->meta->success) {
if ($feed->info->detail->user->privateAccount) {
http_response_code(400);
if ($feed->info->detail->privateAccount) {
http_response_code(403);
echo 'Private account detected! Not supported';
exit;
}
$latte = Misc::latte();
$latte->render(Misc::getView('user'), new FeedTemplate($feed->info->detail->user->nickname, $feed));
+3 -3
View File
@@ -2,13 +2,13 @@
namespace App\Helpers;
class Cookies {
const PROXY = ['proxy-host', 'proxy-port', 'proxy-username', 'proxy-password'];
const PROXY = ['host', 'port', 'user', 'password'];
static public function get(string $name): string {
static public function get(string $name): ?string {
if (isset($_COOKIE[$name]) && !empty($_COOKIE[$name])) {
return $_COOKIE[$name];
}
return '';
return null;
}
static public function exists(string $name): bool {
+23 -8
View File
@@ -13,8 +13,8 @@ class Misc {
return self::env('APP_URL', '') . $endpoint;
}
static public function env(string $key, string $default_value): string {
return isset($_ENV[$key]) && !empty($_ENV[$key]) ? $_ENV[$key] : $default_value;
static public function env(string $key, $default_value) {
return $_ENV[$key] ?? $default_value;
}
/**
@@ -26,17 +26,28 @@ class Misc {
/**
* Setup of TikTok Api wrapper
* @return \TikScraper\Api|\TikScraper\Legacy
*/
static public function api(): \Sovit\TikTok\Api {
$options = [];
$cacheEngine = false;
// Proxy config
static public function api() {
$options = [
'remote_signer' => self::env('SIGNER_URL', 'http://localhost:8080/signature'),
'use_test_endpoints' => self::env('USE_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)
]
];
// 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':
@@ -61,8 +72,12 @@ class Misc {
break;
}
}
$api = new \Sovit\TikTok\Api($options, $cacheEngine);
return $api;
// Legacy mode
$legacy = self::env('FORCE_LEGACY', false); // Instance level
$_COOKIE['enable_legacy'] ?? $legacy = true; // User level
return $legacy === false ? new \TikScraper\Api($options, $cacheEngine) : new \TikScraper\Legacy($options, $cacheEngine);
}
/**
-14
View File
@@ -1,14 +0,0 @@
<?php
namespace App\Models;
/**
* Base for templates with item (only /video at the time)
*/
class ItemTemplate extends BaseTemplate {
public object $item;
function __construct(string $title, object $item) {
parent::__construct($title);
$this->item = $item;
}
}
+31 -12
View File
@@ -3,16 +3,17 @@
<section class="section">
<div class="columns is-multiline is-vcentered">
{foreach $feed->items as $item}
<div class="column is-one-quarter">
<a class="clickable-img" id="{$item->id}" href="#{$item->id}"
data-video_url="{path('/stream?url=' . urlencode($item->video->playAddr))}"
data-video_download="{path('/stream?url=' . urlencode($item->video->playAddr) . '&download=1&id=' . $item->id . '&user=' . $item->author->uniqueId)}"
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))}" />
</a>
{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}
</div>
@@ -34,11 +35,29 @@
<p class="modal-card-title" id="item_title"></p>
</header>
<section class="modal-card-body has-text-centered" style="overflow: hidden;">
<video id="video" controls preload="none"></video>
<video id="video" autoplay controls></video>
</section>
<footer class="modal-card-foot has-text-centered">
<div class="container">
<a id="download_button" target="_blank" class="button is-info" download>Download</a>
<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">
-9
View File
@@ -1,9 +0,0 @@
<div class="tags">
{if !empty($following)}
{foreach $following as $user}
<span class="tag">{$user}</span>
{/foreach}
{else}
<p>None</p>
{/if}
</div>
+2 -1
View File
@@ -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/bulma.min.css')}">
<!-- <link rel="stylesheet" href="{path('/styles/vendor/fontawesome.min.css')}"> -->
<link rel="stylesheet" href="{path('/styles/vendor/bulma.min.css')}">
<title>{$title} - ProxiTok</title>
</head>
+12
View File
@@ -0,0 +1,12 @@
<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
</label>
</div>
<div class="field">
<div class="control">
<button class="button is-success" type="submit">Submit</button>
</div>
</div>
</form>
+1 -1
View File
@@ -1,7 +1,7 @@
<form action="{path('/settings/proxy')}" method="POST">
{foreach $proxy_elements as $proxy_element}
<div class="field">
<label class="label">{$proxy_element}</label>
<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>
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "pablouser1/proxitok",
"description": "An alternative frontend for TikTok",
"version": "1.4.1.2",
"version": "2.0.0",
"license": "AGPL-3.0-or-later",
"type": "project",
"homepage": "https://github.com/pablouser1/ProxiTok",
Generated
+69 -30
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": "275eed5df34976f1f82201b8aef4bddd",
"content-hash": "f4a37c76a2ece697a18abfae8dcb6e9f",
"packages": [
{
"name": "bramus/router",
@@ -121,16 +121,16 @@
},
{
"name": "latte/latte",
"version": "v2.10.8",
"version": "v2.11.0",
"source": {
"type": "git",
"url": "https://github.com/nette/latte.git",
"reference": "596b28bf098ebb852732d60b00538139a009c4db"
"reference": "a815687bfadaf3af51ae99f92edb4ea310c43426"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/latte/zipball/596b28bf098ebb852732d60b00538139a009c4db",
"reference": "596b28bf098ebb852732d60b00538139a009c4db",
"url": "https://api.github.com/repos/nette/latte/zipball/a815687bfadaf3af51ae99f92edb4ea310c43426",
"reference": "a815687bfadaf3af51ae99f92edb4ea310c43426",
"shasum": ""
},
"require": {
@@ -161,7 +161,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.10-dev"
"dev-master": "2.11-dev"
}
},
"autoload": {
@@ -199,9 +199,9 @@
],
"support": {
"issues": "https://github.com/nette/latte/issues",
"source": "https://github.com/nette/latte/tree/v2.10.8"
"source": "https://github.com/nette/latte/tree/v2.11.0"
},
"time": "2022-01-04T14:13:28+00:00"
"time": "2022-02-22T18:39:58+00:00"
},
{
"name": "mibe/feedwriter",
@@ -304,6 +304,45 @@
},
"time": "2016-11-19T20:47:44+00:00"
},
{
"name": "pablouser1/tikscraper",
"version": "v1.2.7.4",
"source": {
"type": "git",
"url": "https://github.com/pablouser1/TikScraperPHP.git",
"reference": "f42df2e947cfe9afc31a46737a48e720f161ef1c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pablouser1/TikScraperPHP/zipball/f42df2e947cfe9afc31a46737a48e720f161ef1c",
"reference": "f42df2e947cfe9afc31a46737a48e720f161ef1c",
"shasum": ""
},
"require": {
"php": ">=7.3|^8.0"
},
"type": "library",
"autoload": {
"psr-4": {
"TikScraper\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Pablo Ferreiro"
}
],
"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"
},
"time": "2022-03-11T21:42:09+00:00"
},
{
"name": "phpoption/phpoption",
"version": "1.8.1",
@@ -381,12 +420,12 @@
"source": {
"type": "git",
"url": "https://github.com/pablouser1/TikTok-API-PHP.git",
"reference": "c6667d70757fd5405ee1b51eaa6602d8758d2abd"
"reference": "60c00cfd321888b80eba93b6214c8c4003e98e57"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pablouser1/TikTok-API-PHP/zipball/c6667d70757fd5405ee1b51eaa6602d8758d2abd",
"reference": "c6667d70757fd5405ee1b51eaa6602d8758d2abd",
"url": "https://api.github.com/repos/pablouser1/TikTok-API-PHP/zipball/60c00cfd321888b80eba93b6214c8c4003e98e57",
"reference": "60c00cfd321888b80eba93b6214c8c4003e98e57",
"shasum": ""
},
"type": "library",
@@ -425,11 +464,11 @@
"issues": "https://github.com/ssovit/TikTok-API-PHP/issues",
"email": "sovit.tamrakar@gmail.com"
},
"time": "2022-02-06T13:17:16+00:00"
"time": "2022-02-07T22:43:53+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.24.0",
"version": "v1.25.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
@@ -461,12 +500,12 @@
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
},
"files": [
"bootstrap.php"
]
],
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -491,7 +530,7 @@
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.24.0"
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0"
},
"funding": [
{
@@ -511,7 +550,7 @@
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.24.0",
"version": "v1.25.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
@@ -543,12 +582,12 @@
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
},
"files": [
"bootstrap.php"
]
],
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -574,7 +613,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.24.0"
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0"
},
"funding": [
{
@@ -594,16 +633,16 @@
},
{
"name": "symfony/polyfill-php80",
"version": "v1.24.0",
"version": "v1.25.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "57b712b08eddb97c762a8caa32c84e037892d2e9"
"reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/57b712b08eddb97c762a8caa32c84e037892d2e9",
"reference": "57b712b08eddb97c762a8caa32c84e037892d2e9",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c",
"reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c",
"shasum": ""
},
"require": {
@@ -657,7 +696,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.24.0"
"source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0"
},
"funding": [
{
@@ -673,7 +712,7 @@
"type": "tidelift"
}
],
"time": "2021-09-13T13:58:33+00:00"
"time": "2022-03-04T08:16:47+00:00"
},
{
"name": "vlucas/phpdotenv",
+7
View File
@@ -0,0 +1,7 @@
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]
# Disable index view
Options -Indexes
+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();
+125
View File
@@ -0,0 +1,125 @@
var opened_video_id = null
const video = document.getElementById('video')
const item_title = document.getElementById('item_title')
const audio = document.getElementById('audio')
const audio_title = document.getElementById('audio_title')
const modal = document.getElementById('modal')
const download_watermark = document.getElementById('download_watermark')
const download_nowatermark = document.getElementById('download_nowatermark')
const share_input = document.getElementById('share_input')
const getVideoDataById = id => {
const el = document.getElementById(id)
if (el) {
opened_video_id = id
return el.dataset
}
return false
}
const isModalActive = () => modal.classList.contains('is-active')
const toggleButton = (id, force) => document.getElementById(id).toggleAttribute('disabled', force)
// -- MODAL -- //
const swapData = ({ video_url, desc, video_download_watermark, video_download_nowatermark, video_share_url, music_title, music_url }) => {
video.src = video_url
item_title.innerText = desc
download_watermark.href = video_download_watermark
download_nowatermark.href = video_download_nowatermark
share_input.value = video_share_url
audio_title.innerText = music_title
audio.src = music_url
}
const showModal = id => {
const dataset = getVideoDataById(id)
if (dataset) {
swapData(dataset)
modal.classList.toggle('is-active')
video.play()
}
}
const hideModel = () => {
video.pause()
audio.pause()
video.currentTime = 0
modal.classList.toggle('is-active')
toggleButton('back-button', false)
toggleButton('next-button', false)
history.pushState('', document.title, window.location.pathname + window.location.search)
}
const getPrevOrNext = forward => {
if (opened_video_id) {
const el = document.getElementById(opened_video_id)
if (el) {
if (forward) {
return el.nextElementSibling
}
return el.previousElementSibling
}
}
return null
}
const moveVideo = forward => {
// Re-enable buttons
toggleButton('back-button', false)
toggleButton('next-button', false)
const new_el = getPrevOrNext(forward)
if (new_el) {
opened_video_id = new_el.id
swapData(new_el.dataset)
} else {
// Max reached, disable buttons depending on direction
if (forward) {
toggleButton('next-button', true)
} else {
toggleButton('back-button', true)
}
}
}
// EVENTS //
const openVideo = video_id => {
if (isModalActive()) {
const dataset = getVideoDataById(video_id)
if (dataset) {
swapData(dataset)
}
} else {
showModal(video_id)
}
}
const swapImages = e => {
const div = e.target
const img = div.children[0]
const gif = div.children[1]
if (!gif.src) {
gif.src = gif.dataset.src
}
img.classList.toggle('hidden')
gif.classList.toggle('hidden')
}
const copyShare = () => {
share_input.select();
navigator.clipboard.writeText(share_input.value);
alert('Copied!')
}
document.getElementById('modal-background').addEventListener('click', hideModel)
document.getElementById('modal-close').addEventListener('click', hideModel)
document.getElementById('back-button').addEventListener('click', () => moveVideo(false))
document.getElementById('next-button').addEventListener('click', () => moveVideo(true))
// Image hover
const images = document.getElementsByClassName("clickable-img")
for (let i = 0; i < images.length; i++) {
images[i].addEventListener('mouseenter', swapImages, false)
images[i].addEventListener('mouseout', swapImages, false)
}
+5 -1
View File
@@ -12,8 +12,12 @@ figure {
display: none;
}
/* Make gifs take all available space */
.clickable-img {
cursor: pointer;
}
.clickable-img img {
width: 100%;
height: 100%;
pointer-events: none;
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+8
View File
@@ -1,4 +1,5 @@
<?php
/** @var \Bramus\Router\Router $router */
use App\Helpers\Misc;
use App\Models\BaseTemplate;
use App\Models\HomeTemplate;
@@ -13,7 +14,13 @@ $router->get('/about', function () {
$latte->render(Misc::getView('about'), new BaseTemplate('About'));
});
$router->get('/verify', function () {
$latte = Misc::latte();
$latte->render(Misc::getView('verify'), new BaseTemplate('Verify'));
});
$router->get('/stream', 'ProxyController@stream');
$router->get('/download', 'ProxyController@download');
$router->get('/redirect', 'RedirectController@redirect');
$router->mount('/trending', function () use ($router) {
@@ -39,6 +46,7 @@ $router->get('/music/([^/]+)', 'MusicController@get');
$router->mount('/settings', function () use ($router) {
$router->get('/', 'SettingsController@index');
$router->post('/proxy', 'SettingsController@proxy');
$router->post('/api', 'SettingsController@api');
});
$router->get('/discover', 'DiscoverController@get');
-137
View File
@@ -1,137 +0,0 @@
const video = document.getElementById('video')
const item_title = document.getElementById('item_title')
const audio = document.getElementById('audio')
const audio_title = document.getElementById('audio_title')
const modal = document.getElementById('modal')
const download_button = document.getElementById('download_button')
// -- HELPERS -- //
const getHash = () => window.location.hash.substring(1)
const getVideoDataById = (id) => {
const el = document.getElementById(id)
if (el) {
return el.dataset
}
return false
}
const isModalActive = () => modal.classList.contains('is-active')
const toggleButton = (id, force) => document.getElementById(id) ? document.getElementById(id).toggleAttribute('disabled', force) : alert('That button does not exist')
// -- MODAL -- //
const swapData = ({ video_url, desc, video_download, music_title, music_url }) => {
video.src = video_url
item_title.innerText = desc
download_button.href = video_download
audio_title.innerText = music_title
audio.src = music_url
}
const showModal = (id) => {
const dataset = getVideoDataById(id)
if (dataset) {
swapData(dataset)
modal.classList.toggle('is-active')
video.play()
}
}
const hideModel = () => {
video.pause()
audio.pause()
video.currentTime = 0
modal.classList.toggle('is-active')
toggleButton('back-button', false)
toggleButton('next-button', false)
history.pushState('', document.title, window.location.pathname + window.location.search)
}
const getPrevOrNext = (forward) => {
const hash = getHash()
if (hash) {
const el = document.getElementById(hash)
if (el) {
if (forward) {
return el.parentElement.nextElementSibling ? el.parentElement.nextElementSibling.children[0] : null
}
return el.parentElement.previousElementSibling ? el.parentElement.previousElementSibling.children[0] : null
}
}
return null
}
const moveVideo = (forward) => {
// Re-enable buttons
toggleButton('back-button', false)
toggleButton('next-button', false)
const new_el = getPrevOrNext(forward)
if (new_el) {
window.location.hash = new_el.id
} else {
// Max reached, disable buttons depending on direction
if (forward) {
toggleButton('next-button', true)
} else {
toggleButton('back-button', true)
}
}
}
// EVENTS //
const hashChange = () => {
if (window.location.hash) {
const hash = getHash()
if (hash) {
// Check first if the modal is already active
if (isModalActive()) {
// If it is, get hash video id and swap data
const dataset = getVideoDataById(hash)
if (dataset) {
swapData(dataset)
video.play()
}
} else {
showModal(hash)
}
}
}
}
const swapImages = (e, mode) => {
let img = null
let gif = null
if (mode === 'gif') {
const a = e.target
img = a.children[0]
gif = a.children[1]
} else if (mode === 'img') {
gif = e.target
img = e.target.parentElement.children[0]
}
if (img && gif) {
if (!gif.src) {
gif.src = gif.dataset.src
}
img.classList.toggle('hidden')
gif.classList.toggle('hidden')
}
}
document.getElementById('modal-background').addEventListener('click', hideModel)
document.getElementById('modal-close').addEventListener('click', hideModel)
document.getElementById('back-button').addEventListener('click', () => moveVideo(false))
document.getElementById('next-button').addEventListener('click', () => moveVideo(true))
window.addEventListener('hashchange', hashChange, false)
// Image hover
const images = document.getElementsByClassName("clickable-img")
for (let i = 0; i < images.length; i++) {
images[i].addEventListener('mouseenter', e => swapImages(e, 'gif'), false)
images[i].addEventListener('mouseout', e => swapImages(e, 'img'), false)
}
hashChange()
+11
View File
@@ -0,0 +1,11 @@
This facilities making the bulma.min.css and fontawesome.min.css files, install all dependencies using:
```
yarn install
```
And then use:
```
yarn bulma && yarn fa
```
This compiles bulma.scss and fontawesome.scss to a css file and helps lowering the size of the css that will be sent to the user.
+1
View File
@@ -25,6 +25,7 @@ $bulmaswatch-import-font: false;
// Components
@import "./node_modules/bulma/sass/components/card.sass";
@import "./node_modules/bulma/sass/components/dropdown.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";
+2
View File
@@ -0,0 +1,2 @@
@import "./node_modules/@fortawesome/fontawesome-free/scss/fontawesome.scss";
@import "./node_modules/@fortawesome/fontawesome-free/scss/solid.scss";
+14
View File
@@ -0,0 +1,14 @@
{
"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"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^6.0.0",
"bulma": "^0.9.3",
"bulmaswatch": "^0.8.1",
"sass": "^1.46.0"
}
}
+8 -3
View File
@@ -2,6 +2,11 @@
# yarn lockfile v1
"@fortawesome/fontawesome-free@^6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.0.0.tgz#6f3bd8e42997c7d536a1246877ed8bcd4f005a54"
integrity sha512-6LB4PYBST1Rx40klypw1SmSDArjFOcfBf2LeX9Zg5EKJT2eXiyiJq+CyBYKeXyK0sXS2FsCJWSPr/luyhuvh0Q==
anymatch@~3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
@@ -113,9 +118,9 @@ readdirp@~3.6.0:
picomatch "^2.2.1"
sass@^1.46.0:
version "1.49.7"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.49.7.tgz#22a86a50552b9b11f71404dfad1b9ff44c6b0c49"
integrity sha512-13dml55EMIR2rS4d/RDHHP0sXMY3+30e1TKsyXaSz3iLWVoDWEoboY8WzJd5JMnxrRHffKO3wq2mpJ0jxRJiEQ==
version "1.49.9"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.49.9.tgz#b15a189ecb0ca9e24634bae5d1ebc191809712f9"
integrity sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
-1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-11
View File
@@ -1,11 +0,0 @@
This facilities making the bulma.min.css file, install all dependencies using:
```
yarn install
```
And then use:
```
yarn bulma
```
This compiles bulma.scss to a css file and helps lowering the size of the css that will be sent to the user.
-12
View File
@@ -1,12 +0,0 @@
{
"name": "proxitok-scss",
"private": true,
"scripts": {
"bulma": "sass --style=compressed bulma.scss ../bulma.min.css"
},
"dependencies": {
"bulma": "^0.9.3",
"bulmaswatch": "^0.8.1",
"sass": "^1.46.0"
}
}
+5 -1
View File
@@ -6,7 +6,11 @@
{/block}
{block content}
<p class="title">Why?</p>
<p class="title">About this instance</p>
<p>Forcing Legacy mode: {isset($_ENV['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>
There are already
<a rel="nofollow" href="https://penetrum.com/tiktok/Penetrum_TikTok_Security_Analysis_whitepaper.pdf">multiple</a>
+3
View File
@@ -5,5 +5,8 @@
<p class="subtitle">HTTP Code: {$error->http_code}</p>
{if $error->tiktok_code}
<p class="subtitle">API error code {$error->tiktok_code} ({$error->tiktok_msg})</p>
{if $error->tiktok_code === 10000}
<a href="{path('/verify')}">What does this mean?</a>
{/if}
{/if}
{/block}
+3
View File
@@ -8,4 +8,7 @@
<!-- Proxy settings -->
<p class="title">Proxy</p>
{include '../components/settings/proxy.latte'}
<hr />
<p class="title">Api</p>
{include '../components/settings/api.latte'}
{/block}
+38
View File
@@ -0,0 +1,38 @@
{layout '../layouts/default.latte'}
{block header}
<p class="title">VERIFY_CODE</p>
<p class="subtitle">What can I do?</p>
{/block}
{block content}
<p>TL;DR You probably have to search for another instance or wait some hours (or even better, have your own self-hosted instance)</p>
<p>TikTok has some mecanisms in order to stop scraping on their latest api (/api/...):</p>
<hr />
<div class="block">
<p class="is-size-4">Rate limits</p>
<p>
This is pretty standard stuff,
it blocks ips temporarly if you send too many requests to an endpoint and unbans you after some hours.
</p>
</div>
<div class="block">
<p class="is-size-4">IP Blacklist</p>
<p>
It is a list with IPs from popular Hosters (AWS, Heroku...). This IPs are banned <i>(I think) permanently</i> and
can barely make requests.
</p>
<p>
This makes hosting a public instance really hard. Even if you use a hosting platform that is not banned by TikTok, it is only a matter of time
that it inevitably gets banned.
</p>
<p class="is-size-5">(For webmasters) How can I know if my server is not blacklisted?</p>
<p>Run this command on your instance:</p>
<code>
curl \
--url 'https://www.tiktok.com/@tiktok/?lang=en' \
--header 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36'
</code>
<p>If you don't get an empty response or a captcha, you are good to go</p>
</div>
{/block}
+27
View File
@@ -1,14 +1,40 @@
{layout '../layouts/hero.latte'}
{block content}
{do $item = $feed->items[0]}
<div class="columns is-centered is-vcentered">
<div class="column">
<<<<<<< HEAD
<video controls poster="{path('/stream?url=' . urlencode($item->video->originCover))}">
<source src="{path('/stream?url=' . urlencode($item->video->playAddr))}" type="video/mp4" />
=======
<video controls poster="{path('/stream?url=' . urlencode($item->items[0]->video->originCover))}">
<source src="{path('/stream?url=' . urlencode($item->items[0]->video->playAddr))}" type="video/mp4" />
>>>>>>> master
</video>
</div>
<div class="column has-text-centered">
<div class="box">
<<<<<<< HEAD
<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>
<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=' . $item->author->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>
<p>{$item->music->title}</p>
<audio src="{path('/stream?url=' . urlencode($item->music->playUrl))}" controls preload="none"></audio>
=======
<p class="title">Video by <a href="{path('/@'.$item->info->detail->user->uniqueId)}">{$item->info->detail->user->uniqueId}</a></p>
<p class="subtitle">{$item->items[0]->desc}</p>
<p>Played {number($item->info->detail->stats->playCount)} times</p>
@@ -17,6 +43,7 @@
<a href="{path('/stream?url=' . urlencode($item->items[0]->video->playAddr) . '&download=1')}" class="button is-info">Download video</a>
<p>{$item->items[0]->music->title}</p>
<audio src="{path('/stream?url=' . urlencode($item->items[0]->music->playUrl))}" controls preload="none"></audio>
>>>>>>> master
</div>
</div>
</div>