Merge pull request #18 from pablouser1/new-api
New Wrapper using TikTok's new Api
This commit is contained in:
commit
7011867fd8
10
.env.example
10
.env.example
|
@ -1,5 +1,15 @@
|
||||||
# APP_URL="http://localhost:8000" # Full url path, PLEASE REPLACE TO YOUR OWN ONE
|
# 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
|
# 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)
|
# API_CACHE=redis # Cache engine for TikTok Api, (more info on README)
|
||||||
|
|
||||||
|
|
13
.htaccess
13
.htaccess
|
@ -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>
|
|
2
Procfile
2
Procfile
|
@ -1 +1 @@
|
||||||
web: vendor/bin/heroku-php-apache2
|
web: vendor/bin/heroku-php-apache2 public/
|
||||||
|
|
|
@ -36,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:
|
Then you can run it using for example the PHP Development Server with:
|
||||||
```bash
|
```bash
|
||||||
php -S localhost:8080
|
php -S localhost:8080 -t public
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
|
@ -39,26 +39,25 @@ class ProxyController {
|
||||||
}
|
}
|
||||||
|
|
||||||
static public function stream() {
|
static public function stream() {
|
||||||
if (isset($_GET['download'])) {
|
self::checkUrl();
|
||||||
$watermark = isset($_GET['watermark']);
|
$url = $_GET['url'];
|
||||||
if ($watermark) {
|
$streamer = new \TikScraper\Stream();
|
||||||
self::checkUrl();
|
$streamer->url($url);
|
||||||
$filename = self::getFileName();
|
}
|
||||||
$downloader = new \Sovit\TikTok\Download();
|
|
||||||
$downloader->url($_GET['url'], $filename, true);
|
static public function download() {
|
||||||
} else {
|
$downloader = new \TikScraper\Download();
|
||||||
if (!isset($_GET['id'])) {
|
$watermark = isset($_GET['watermark']);
|
||||||
die('You need to send an ID!');
|
if ($watermark) {
|
||||||
}
|
|
||||||
$filename = self::getFileName();
|
|
||||||
$downloader = new \Sovit\TikTok\Download();
|
|
||||||
$downloader->url($_GET['id'], $filename, false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self::checkUrl();
|
self::checkUrl();
|
||||||
$url = $_GET['url'];
|
$filename = self::getFileName();
|
||||||
$streamer = new \Sovit\TikTok\Stream();
|
$downloader->url($_GET['url'], $filename, true);
|
||||||
$streamer->stream($url);
|
} else {
|
||||||
|
if (!isset($_GET['id'])) {
|
||||||
|
die('You need to send an ID!');
|
||||||
|
}
|
||||||
|
$filename = self::getFileName();
|
||||||
|
$downloader->url($_GET['id'], $filename, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ use App\Models\SettingsTemplate;
|
||||||
class SettingsController {
|
class SettingsController {
|
||||||
static public function index() {
|
static public function index() {
|
||||||
$latte = Misc::latte();
|
$latte = Misc::latte();
|
||||||
$latte->render(Misc::getView('settings'), new SettingsTemplate());
|
$latte->render(Misc::getView('settings'), new SettingsTemplate);
|
||||||
}
|
}
|
||||||
|
|
||||||
static public function proxy() {
|
static public function proxy() {
|
||||||
|
@ -20,4 +20,10 @@ class SettingsController {
|
||||||
$url = Misc::url('/settings');
|
$url = Misc::url('/settings');
|
||||||
header("Location: {$url}");
|
header("Location: {$url}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static public function api() {
|
||||||
|
$_POST['legacy'] ?? Cookies::set('api-legacy', '1');
|
||||||
|
$url = Misc::url('/settings');
|
||||||
|
header("Location: {$url}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,16 @@ use App\Helpers\RSS;
|
||||||
|
|
||||||
class TrendingController {
|
class TrendingController {
|
||||||
static public function get() {
|
static public function get() {
|
||||||
$cursor = Misc::getCursor();
|
$cursor = Misc::getTtwid();
|
||||||
$api = Misc::api();
|
$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) {
|
if ($feed->meta->success) {
|
||||||
$latte = Misc::latte();
|
$latte = Misc::latte();
|
||||||
$latte->render(Misc::getView('trending'), new FeedTemplate('Trending', $feed));
|
$latte->render(Misc::getView('trending'), new FeedTemplate('Trending', $feed));
|
||||||
|
|
|
@ -12,9 +12,10 @@ class UserController {
|
||||||
$api = Misc::api();
|
$api = Misc::api();
|
||||||
$feed = $api->getUserFeed($username, $cursor);
|
$feed = $api->getUserFeed($username, $cursor);
|
||||||
if ($feed->meta->success) {
|
if ($feed->meta->success) {
|
||||||
if ($feed->info->detail->user->privateAccount) {
|
if ($feed->info->detail->privateAccount) {
|
||||||
http_response_code(400);
|
http_response_code(403);
|
||||||
echo 'Private account detected! Not supported';
|
echo 'Private account detected! Not supported';
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
$latte = Misc::latte();
|
$latte = Misc::latte();
|
||||||
$latte->render(Misc::getView('user'), new FeedTemplate($feed->info->detail->user->nickname, $feed));
|
$latte->render(Misc::getView('user'), new FeedTemplate($feed->info->detail->user->nickname, $feed));
|
||||||
|
|
|
@ -2,13 +2,13 @@
|
||||||
namespace App\Helpers;
|
namespace App\Helpers;
|
||||||
|
|
||||||
class Cookies {
|
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])) {
|
if (isset($_COOKIE[$name]) && !empty($_COOKIE[$name])) {
|
||||||
return $_COOKIE[$name];
|
return $_COOKIE[$name];
|
||||||
}
|
}
|
||||||
return '';
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static public function exists(string $name): bool {
|
static public function exists(string $name): bool {
|
||||||
|
|
|
@ -13,8 +13,8 @@ class Misc {
|
||||||
return self::env('APP_URL', '') . $endpoint;
|
return self::env('APP_URL', '') . $endpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
static public function env(string $key, string $default_value): string {
|
static public function env(string $key, $default_value) {
|
||||||
return isset($_ENV[$key]) && !empty($_ENV[$key]) ? $_ENV[$key] : $default_value;
|
return $_ENV[$key] ?? $default_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,17 +26,28 @@ class Misc {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup of TikTok Api wrapper
|
* Setup of TikTok Api wrapper
|
||||||
|
* @return \TikScraper\Api|\TikScraper\Legacy
|
||||||
*/
|
*/
|
||||||
static public function api(): \Sovit\TikTok\Api {
|
static public function api() {
|
||||||
$options = [];
|
$options = [
|
||||||
$cacheEngine = false;
|
'remote_signer' => self::env('SIGNER_URL', 'http://localhost:8080/signature'),
|
||||||
// Proxy config
|
'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) {
|
foreach(Cookies::PROXY as $proxy_element) {
|
||||||
if (isset($_COOKIE[$proxy_element])) {
|
if (isset($_COOKIE[$proxy_element])) {
|
||||||
$options['proxy'][$proxy_element] = $_COOKIE[$proxy_element];
|
$options['proxy'][$proxy_element] = $_COOKIE[$proxy_element];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Cache config
|
// Cache config
|
||||||
|
$cacheEngine = false;
|
||||||
if (isset($_ENV['API_CACHE'])) {
|
if (isset($_ENV['API_CACHE'])) {
|
||||||
switch ($_ENV['API_CACHE']) {
|
switch ($_ENV['API_CACHE']) {
|
||||||
case 'json':
|
case 'json':
|
||||||
|
@ -61,8 +72,12 @@ class Misc {
|
||||||
break;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,8 +6,8 @@
|
||||||
{do $share_url = 'https://tiktok.com/@' . $item->author->uniqueId . '/video/' . $item->id}
|
{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)"
|
<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_url="{path('/stream?url=' . urlencode($item->video->playAddr))}"
|
||||||
data-video_download_watermark="{path('/stream?url=' . urlencode($item->video->playAddr) . '&download=1&id=' . $item->id . '&user=' . $item->author->uniqueId) . '&watermark='}"
|
data-video_download_watermark="{path('/download?url=' . urlencode($item->video->playAddr) . '&id=' . $item->id . '&user=' . $item->author->uniqueId) . '&watermark='}"
|
||||||
data-video_download_nowatermark="{path('/stream?download=1&id=' . $item->id . '&user=' . $item->author->uniqueId)}"
|
data-video_download_nowatermark="{path('/download?id=' . $item->id . '&user=' . $item->author->uniqueId)}"
|
||||||
data-video_share_url="{$share_url}"
|
data-video_share_url="{$share_url}"
|
||||||
data-desc="{$item->desc}"
|
data-desc="{$item->desc}"
|
||||||
data-music_title="{$item->music->title}"
|
data-music_title="{$item->music->title}"
|
||||||
|
@ -47,9 +47,16 @@
|
||||||
<button class="button is-primary" onclick="copyShare()">Copy</button>
|
<button class="button is-primary" onclick="copyShare()">Copy</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons is-centered">
|
<div id="download_dropdown" class="dropdown is-hoverable">
|
||||||
<a target="_blank" id="download_watermark" class="button is-info">Download with watermark</a>
|
<div class="dropdown-trigger">
|
||||||
<a target="_blank" id="download_nowatermark" class="button is-info">Download without watermark</a>
|
<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>
|
</div>
|
||||||
<p id="audio_title"></p>
|
<p id="audio_title"></p>
|
||||||
<audio id="audio" controls preload="none"></audio>
|
<audio id="audio" controls preload="none"></audio>
|
||||||
|
|
|
@ -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>
|
|
|
@ -5,6 +5,7 @@
|
||||||
<meta property="og:title" content="ProxiTok" />
|
<meta property="og:title" content="ProxiTok" />
|
||||||
<meta property="og:description" content="Alternative frontend for TikTok" />
|
<meta property="og:description" content="Alternative frontend for TikTok" />
|
||||||
<meta property="og:type" content="website" />
|
<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>
|
<title>{$title} - ProxiTok</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
12
components/settings/api.latte
Normal file
12
components/settings/api.latte
Normal 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,7 +1,7 @@
|
||||||
<form action="{path('/settings/proxy')}" method="POST">
|
<form action="{path('/settings/proxy')}" method="POST">
|
||||||
{foreach $proxy_elements as $proxy_element}
|
{foreach $proxy_elements as $proxy_element}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">{$proxy_element}</label>
|
<label class="label">{$proxy_element|firstUpper}</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input name="{$proxy_element}" class="input" value="{isset($_COOKIE[$proxy_element]) ? $_COOKIE[$proxy_element] : ''}" required />
|
<input name="{$proxy_element}" class="input" value="{isset($_COOKIE[$proxy_element]) ? $_COOKIE[$proxy_element] : ''}" required />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "pablouser1/proxitok",
|
"name": "pablouser1/proxitok",
|
||||||
"description": "An alternative frontend for TikTok",
|
"description": "An alternative frontend for TikTok",
|
||||||
"version": "1.4.1.4",
|
"version": "2.0.0",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"homepage": "https://github.com/pablouser1/ProxiTok",
|
"homepage": "https://github.com/pablouser1/ProxiTok",
|
||||||
|
|
83
composer.lock
generated
83
composer.lock
generated
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "80d871b2eb1a9e28de6f38990492ab3c",
|
"content-hash": "f4a37c76a2ece697a18abfae8dcb6e9f",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "bramus/router",
|
"name": "bramus/router",
|
||||||
|
@ -121,16 +121,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "latte/latte",
|
"name": "latte/latte",
|
||||||
"version": "v2.10.8",
|
"version": "v2.11.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/nette/latte.git",
|
"url": "https://github.com/nette/latte.git",
|
||||||
"reference": "596b28bf098ebb852732d60b00538139a009c4db"
|
"reference": "a815687bfadaf3af51ae99f92edb4ea310c43426"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/nette/latte/zipball/596b28bf098ebb852732d60b00538139a009c4db",
|
"url": "https://api.github.com/repos/nette/latte/zipball/a815687bfadaf3af51ae99f92edb4ea310c43426",
|
||||||
"reference": "596b28bf098ebb852732d60b00538139a009c4db",
|
"reference": "a815687bfadaf3af51ae99f92edb4ea310c43426",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -161,7 +161,7 @@
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "2.10-dev"
|
"dev-master": "2.11-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
@ -199,9 +199,9 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/nette/latte/issues",
|
"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",
|
"name": "mibe/feedwriter",
|
||||||
|
@ -304,6 +304,45 @@
|
||||||
},
|
},
|
||||||
"time": "2016-11-19T20:47:44+00:00"
|
"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",
|
"name": "phpoption/phpoption",
|
||||||
"version": "1.8.1",
|
"version": "1.8.1",
|
||||||
|
@ -429,7 +468,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-ctype",
|
"name": "symfony/polyfill-ctype",
|
||||||
"version": "v1.24.0",
|
"version": "v1.25.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||||
|
@ -461,12 +500,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
|
||||||
"Symfony\\Polyfill\\Ctype\\": ""
|
|
||||||
},
|
|
||||||
"files": [
|
"files": [
|
||||||
"bootstrap.php"
|
"bootstrap.php"
|
||||||
]
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Polyfill\\Ctype\\": ""
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
"license": [
|
"license": [
|
||||||
|
@ -491,7 +530,7 @@
|
||||||
"portable"
|
"portable"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.24.0"
|
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -511,7 +550,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-mbstring",
|
"name": "symfony/polyfill-mbstring",
|
||||||
"version": "v1.24.0",
|
"version": "v1.25.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||||
|
@ -574,7 +613,7 @@
|
||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.24.0"
|
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -594,16 +633,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-php80",
|
"name": "symfony/polyfill-php80",
|
||||||
"version": "v1.24.0",
|
"version": "v1.25.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||||
"reference": "57b712b08eddb97c762a8caa32c84e037892d2e9"
|
"reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/57b712b08eddb97c762a8caa32c84e037892d2e9",
|
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c",
|
||||||
"reference": "57b712b08eddb97c762a8caa32c84e037892d2e9",
|
"reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -657,7 +696,7 @@
|
||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.24.0"
|
"source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -673,7 +712,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2021-09-13T13:58:33+00:00"
|
"time": "2022-03-04T08:16:47+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "vlucas/phpdotenv",
|
"name": "vlucas/phpdotenv",
|
||||||
|
|
7
public/.htaccess
Normal file
7
public/.htaccess
Normal 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
|
|
@ -1,14 +1,14 @@
|
||||||
<?php
|
<?php
|
||||||
require __DIR__ . "/vendor/autoload.php";
|
require __DIR__ . "/../vendor/autoload.php";
|
||||||
|
|
||||||
// LOAD DOTENV
|
// LOAD DOTENV
|
||||||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
|
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');
|
||||||
$dotenv->safeLoad();
|
$dotenv->safeLoad();
|
||||||
|
|
||||||
// ROUTER
|
// ROUTER
|
||||||
$router = new Bramus\Router\Router();
|
$router = new Bramus\Router\Router();
|
||||||
$router->setNamespace('\App\Controllers');
|
$router->setNamespace('\App\Controllers');
|
||||||
|
|
||||||
require __DIR__ . '/routes.php';
|
require __DIR__ . '/../routes.php';
|
||||||
|
|
||||||
$router->run();
|
$router->run();
|
1
public/styles/vendor/bulma.min.css
vendored
Normal file
1
public/styles/vendor/bulma.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/styles/vendor/bulma.min.css.map
vendored
Normal file
1
public/styles/vendor/bulma.min.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
/** @var \Bramus\Router\Router $router */
|
||||||
use App\Helpers\Misc;
|
use App\Helpers\Misc;
|
||||||
use App\Models\BaseTemplate;
|
use App\Models\BaseTemplate;
|
||||||
use App\Models\HomeTemplate;
|
use App\Models\HomeTemplate;
|
||||||
|
@ -13,7 +14,13 @@ $router->get('/about', function () {
|
||||||
$latte->render(Misc::getView('about'), new BaseTemplate('About'));
|
$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('/stream', 'ProxyController@stream');
|
||||||
|
$router->get('/download', 'ProxyController@download');
|
||||||
$router->get('/redirect', 'RedirectController@redirect');
|
$router->get('/redirect', 'RedirectController@redirect');
|
||||||
|
|
||||||
$router->mount('/trending', function () use ($router) {
|
$router->mount('/trending', function () use ($router) {
|
||||||
|
@ -39,6 +46,7 @@ $router->get('/music/([^/]+)', 'MusicController@get');
|
||||||
$router->mount('/settings', function () use ($router) {
|
$router->mount('/settings', function () use ($router) {
|
||||||
$router->get('/', 'SettingsController@index');
|
$router->get('/', 'SettingsController@index');
|
||||||
$router->post('/proxy', 'SettingsController@proxy');
|
$router->post('/proxy', 'SettingsController@proxy');
|
||||||
|
$router->post('/api', 'SettingsController@api');
|
||||||
});
|
});
|
||||||
|
|
||||||
$router->get('/discover', 'DiscoverController@get');
|
$router->get('/discover', 'DiscoverController@get');
|
||||||
|
|
11
scss/README.md
Normal file
11
scss/README.md
Normal 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
styles/scss/bulma.scss → scss/bulma.scss
vendored
1
styles/scss/bulma.scss → scss/bulma.scss
vendored
|
@ -25,6 +25,7 @@ $bulmaswatch-import-font: false;
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
@import "./node_modules/bulma/sass/components/card.sass";
|
@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/modal.sass";
|
||||||
@import "./node_modules/bulma/sass/components/navbar.sass";
|
@import "./node_modules/bulma/sass/components/navbar.sass";
|
||||||
@import "./node_modules/bulma/sass/components/media.sass";
|
@import "./node_modules/bulma/sass/components/media.sass";
|
2
scss/fontawesome.scss
vendored
Normal file
2
scss/fontawesome.scss
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
@import "./node_modules/@fortawesome/fontawesome-free/scss/fontawesome.scss";
|
||||||
|
@import "./node_modules/@fortawesome/fontawesome-free/scss/solid.scss";
|
14
scss/package.json
Normal file
14
scss/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,11 @@
|
||||||
# yarn lockfile v1
|
# 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:
|
anymatch@~3.1.2:
|
||||||
version "3.1.2"
|
version "3.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
|
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
|
||||||
|
@ -113,9 +118,9 @@ readdirp@~3.6.0:
|
||||||
picomatch "^2.2.1"
|
picomatch "^2.2.1"
|
||||||
|
|
||||||
sass@^1.46.0:
|
sass@^1.46.0:
|
||||||
version "1.49.7"
|
version "1.49.9"
|
||||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.49.7.tgz#22a86a50552b9b11f71404dfad1b9ff44c6b0c49"
|
resolved "https://registry.yarnpkg.com/sass/-/sass-1.49.9.tgz#b15a189ecb0ca9e24634bae5d1ebc191809712f9"
|
||||||
integrity sha512-13dml55EMIR2rS4d/RDHHP0sXMY3+30e1TKsyXaSz3iLWVoDWEoboY8WzJd5JMnxrRHffKO3wq2mpJ0jxRJiEQ==
|
integrity sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==
|
||||||
dependencies:
|
dependencies:
|
||||||
chokidar ">=3.0.0 <4.0.0"
|
chokidar ">=3.0.0 <4.0.0"
|
||||||
immutable "^4.0.0"
|
immutable "^4.0.0"
|
1
styles/bulma.min.css
vendored
1
styles/bulma.min.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -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.
|
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,7 +6,11 @@
|
||||||
{/block}
|
{/block}
|
||||||
|
|
||||||
{block content}
|
{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>
|
<p>
|
||||||
There are already
|
There are already
|
||||||
<a rel="nofollow" href="https://penetrum.com/tiktok/Penetrum_TikTok_Security_Analysis_whitepaper.pdf">multiple</a>
|
<a rel="nofollow" href="https://penetrum.com/tiktok/Penetrum_TikTok_Security_Analysis_whitepaper.pdf">multiple</a>
|
||||||
|
|
|
@ -5,5 +5,8 @@
|
||||||
<p class="subtitle">HTTP Code: {$error->http_code}</p>
|
<p class="subtitle">HTTP Code: {$error->http_code}</p>
|
||||||
{if $error->tiktok_code}
|
{if $error->tiktok_code}
|
||||||
<p class="subtitle">API error code {$error->tiktok_code} ({$error->tiktok_msg})</p>
|
<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}
|
{/if}
|
||||||
{/block}
|
{/block}
|
||||||
|
|
|
@ -8,4 +8,7 @@
|
||||||
<!-- Proxy settings -->
|
<!-- Proxy settings -->
|
||||||
<p class="title">Proxy</p>
|
<p class="title">Proxy</p>
|
||||||
{include '../components/settings/proxy.latte'}
|
{include '../components/settings/proxy.latte'}
|
||||||
|
<hr />
|
||||||
|
<p class="title">Api</p>
|
||||||
|
{include '../components/settings/api.latte'}
|
||||||
{/block}
|
{/block}
|
||||||
|
|
38
views/verify.latte
Normal file
38
views/verify.latte
Normal 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}
|
|
@ -1,14 +1,40 @@
|
||||||
{layout '../layouts/hero.latte'}
|
{layout '../layouts/hero.latte'}
|
||||||
|
|
||||||
{block content}
|
{block content}
|
||||||
|
{do $item = $feed->items[0]}
|
||||||
<div class="columns is-centered is-vcentered">
|
<div class="columns is-centered is-vcentered">
|
||||||
<div class="column">
|
<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))}">
|
<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" />
|
<source src="{path('/stream?url=' . urlencode($item->items[0]->video->playAddr))}" type="video/mp4" />
|
||||||
|
>>>>>>> master
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
<div class="column has-text-centered">
|
<div class="column has-text-centered">
|
||||||
<div class="box">
|
<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="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 class="subtitle">{$item->items[0]->desc}</p>
|
||||||
<p>Played {number($item->info->detail->stats->playCount)} times</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>
|
<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>
|
<p>{$item->items[0]->music->title}</p>
|
||||||
<audio src="{path('/stream?url=' . urlencode($item->items[0]->music->playUrl))}" controls preload="none"></audio>
|
<audio src="{path('/stream?url=' . urlencode($item->items[0]->music->playUrl))}" controls preload="none"></audio>
|
||||||
|
>>>>>>> master
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue