Using new wrapper

This commit is contained in:
Pablo Ferreiro 2022-02-13 22:21:08 +01:00
parent 19e2c32c77
commit a3ebb08d26
No known key found for this signature in database
GPG key ID: 41FBCE65B779FA24
22 changed files with 141 additions and 154 deletions

View file

@ -1,9 +1,9 @@
# 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
# SIGNER_URL="https://tiktok-sign.herokuapp.com/signature" # External signing service
# 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)
# Redis cache, used on Helpers\CacheEngines\RedisCache (CHOOSE ONE) # Redis cache, used on Helpers\CacheEngines\RedisCache
# REDIS_HOST=localhost # Host or path to unix socket # REDIS_HOST=localhost # Host or path to unix socket
# REDIS_PORT=6379 # Set to -1 to use unix socket # REDIS_PORT=6379 # Set to -1 to use unix socket
# REDIS_PASSWORD=example # Leave commented for no password # REDIS_PASSWORD=example # Leave commented for no password

View file

@ -60,7 +60,7 @@ location /proxitok/.env {
* i18n * i18n
## Credits ## Credits
* [TikTok-API-PHP](https://github.com/ssovit/TikTok-API-PHP) (Currently using my personal fork) * [TikScraper](https://github.com/pablouser1/TikScraperPHP)
* [Latte](https://github.com/nette/latte) * [Latte](https://github.com/nette/latte)
* [bramus/router](https://github.com/bramus/router) * [bramus/router](https://github.com/bramus/router)
* [PHP dotenv](https://github.com/vlucas/phpdotenv) * [PHP dotenv](https://github.com/vlucas/phpdotenv)

View file

@ -1,33 +1,31 @@
<?php <?php
namespace App\Cache; namespace App\Cache;
use App\Helpers\Misc;
class JSONCache { class JSONCache {
private string $cache_path = __DIR__ . '/../../cache/api'; private string $cache_path = '';
function __construct() { function __construct() {
if (isset($_ENV['API_CACHE_JSON']) && !empty($_ENV['API_CACHE_JSON'])) { $this->cache_path = Misc::env('API_CACHE_JSON', __DIR__ . '/../../cache/api');
$this->cache_path = $_ENV['API_CACHE_JSON'];
}
}
public function get(string $cache_key): object|false {
$filename = $this->cache_path . '/' . $cache_key . '.json';
if (is_file($filename)) {
$time = time();
$json_string = file_get_contents($filename);
$element = json_decode($json_string);
if ($time < $element->expires) {
return $element->data;
}
// Remove file if expired
unlink($filename);
}
return false;
} }
public function set(string $cache_key, mixed $data, $timeout = 3600) { public function get(string $cache_key): ?object {
file_put_contents($this->cache_path . '/' . $cache_key . '.json', json_encode([ $filename = $this->cache_path . '/' . $cache_key . '.json';
'data' => $data, if (is_file($filename)) {
'expires' => time() + $timeout $json_string = file_get_contents($filename);
])); $element = json_decode($json_string);
return $element;
}
return null;
}
public function exists(string $cache_key): bool {
$filename = $this->cache_path . '/' . $cache_key . '.json';
return is_file($filename);
}
public function set(string $cache_key, string $data, $timeout = 3600) {
file_put_contents($this->cache_path . '/' . $cache_key . '.json', $data);
} }
} }

View file

@ -27,6 +27,10 @@ class RedisCache {
return null; return null;
} }
public function exists(string $cache_key): bool {
return $this->client->exists($cache_key);
}
public function set(string $cache_key, array $data, $timeout = 3600) { public function set(string $cache_key, array $data, $timeout = 3600) {
$this->client->set($cache_key, json_encode($data), $timeout); $this->client->set($cache_key, json_encode($data), $timeout);
} }

View file

@ -3,7 +3,7 @@ namespace App\Controllers;
use App\Helpers\ErrorHandler; use App\Helpers\ErrorHandler;
use App\Helpers\Misc; use App\Helpers\Misc;
use App\Models\FeedTemplate; use App\Models\DiscoverTemplate;
class DiscoverController { class DiscoverController {
static public function get() { static public function get() {
@ -11,7 +11,7 @@ class DiscoverController {
$feed = $api->getDiscover(); $feed = $api->getDiscover();
if ($feed->meta->success) { if ($feed->meta->success) {
$latte = Misc::latte(); $latte = Misc::latte();
$latte->render(Misc::getView('discover'), new FeedTemplate('Discover', $feed)); $latte->render(Misc::getView('discover'), new DiscoverTemplate($feed));
} else { } else {
ErrorHandler::show($feed->meta); ErrorHandler::show($feed->meta);
} }

View file

@ -40,25 +40,24 @@ class ProxyController {
static public function stream() { static public function stream() {
if (isset($_GET['download'])) { if (isset($_GET['download'])) {
$downloader = new \TikScraper\Download();
$watermark = isset($_GET['watermark']); $watermark = isset($_GET['watermark']);
if ($watermark) { if ($watermark) {
self::checkUrl(); self::checkUrl();
$filename = self::getFileName(); $filename = self::getFileName();
$downloader = new \Sovit\TikTok\Download();
$downloader->url($_GET['url'], $filename, true); $downloader->url($_GET['url'], $filename, true);
} else { } else {
if (!isset($_GET['id'])) { if (!isset($_GET['id'])) {
die('You need to send an ID!'); die('You need to send an ID!');
} }
$filename = self::getFileName(); $filename = self::getFileName();
$downloader = new \Sovit\TikTok\Download();
$downloader->url($_GET['id'], $filename, false); $downloader->url($_GET['id'], $filename, false);
} }
} else { } else {
self::checkUrl(); self::checkUrl();
$url = $_GET['url']; $url = $_GET['url'];
$streamer = new \Sovit\TikTok\Stream(); $streamer = new \TikScraper\Stream();
$streamer->stream($url); $streamer->url($url);
} }
} }
} }

View file

@ -10,7 +10,7 @@ class TagController {
static public function get(string $name) { static public function get(string $name) {
$cursor = Misc::getCursor(); $cursor = Misc::getCursor();
$api = Misc::api(); $api = Misc::api();
$feed = $api->getChallengeFeed($name, $cursor); $feed = $api->getHashtagFeed($name, $cursor);
if ($feed->meta->success) { if ($feed->meta->success) {
$latte = Misc::latte(); $latte = Misc::latte();
$latte->render(Misc::getView('tag'), new FeedTemplate('Tag', $feed)); $latte->render(Misc::getView('tag'), new FeedTemplate('Tag', $feed));
@ -21,9 +21,9 @@ class TagController {
static public function rss(string $name) { static public function rss(string $name) {
$api = Misc::api(); $api = Misc::api();
$feed = $api->getChallengeFeed($name); $feed = $api->getHashtagFeed($name);
if ($feed->meta->success) { if ($feed->meta->success) {
$feed = RSS::build("/tag/{$name}", "{$name} Tag", $feed->info->detail->challenge->desc, $feed->items); $feed = RSS::build("/tag/{$name}", "{$name} Tag", $feed->info->detail->desc, $feed->items);
// Setup headers // Setup headers
RSS::setHeaders('tag.rss'); RSS::setHeaders('tag.rss');
echo $feed; echo $feed;

View file

@ -9,8 +9,9 @@ use App\Helpers\RSS;
class TrendingController { class TrendingController {
static public function get() { static public function get() {
$cursor = Misc::getCursor(); $cursor = Misc::getCursor();
$page = $_GET['page'] ?? 0;
$api = Misc::api(); $api = Misc::api();
$feed = $api->getTrendingFeed($cursor); $feed = $api->getTrending($cursor, $page);
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));
@ -21,7 +22,7 @@ class TrendingController {
static public function rss() { static public function rss() {
$api = Misc::api(); $api = Misc::api();
$feed = $api->getTrendingFeed(); $feed = $api->getTrending();
if ($feed->meta->success) { if ($feed->meta->success) {
$feed = RSS::build('/trending', 'Trending', 'Tiktok trending', $feed->items); $feed = RSS::build('/trending', 'Trending', 'Tiktok trending', $feed->items);
// Setup headers // Setup headers

View file

@ -12,12 +12,12 @@ 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(400);
echo 'Private account detected! Not supported'; echo 'Private account detected! Not supported';
} }
$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->nickname, $feed));
} else { } else {
ErrorHandler::show($feed->meta); ErrorHandler::show($feed->meta);
} }
@ -27,7 +27,7 @@ class UserController {
$api = Misc::api(); $api = Misc::api();
$feed = $api->getUserFeed($username); $feed = $api->getUserFeed($username);
if ($feed->meta->success) { if ($feed->meta->success) {
$feed = RSS::build('/@'.$username, $feed->info->detail->user->nickname, $feed->info->detail->user->signature, $feed->items); $feed = RSS::build('/@'.$username, $feed->info->detail->nickname, $feed->info->detail->signature, $feed->items);
// Setup headers // Setup headers
RSS::setHeaders('user.rss'); RSS::setHeaders('user.rss');
echo $feed; echo $feed;

View file

@ -3,7 +3,7 @@ namespace App\Controllers;
use App\Helpers\ErrorHandler; use App\Helpers\ErrorHandler;
use App\Helpers\Misc; use App\Helpers\Misc;
use App\Models\ItemTemplate; use App\Models\FeedTemplate;
class VideoController { class VideoController {
static public function get(string $video_id) { static public function get(string $video_id) {
@ -11,7 +11,7 @@ class VideoController {
$item = $api->getVideoByID($video_id); $item = $api->getVideoByID($video_id);
if ($item->meta->success) { if ($item->meta->success) {
$latte = Misc::latte(); $latte = Misc::latte();
$latte->render(Misc::getView('video'), new ItemTemplate($item->info->detail->user->nickname, $item)); $latte->render(Misc::getView('video'), new FeedTemplate($item->info->detail->nickname, $item));
} else { } else {
ErrorHandler::show($item->meta); ErrorHandler::show($item->meta);
} }

View file

@ -27,8 +27,10 @@ class Misc {
/** /**
* Setup of TikTok Api wrapper * Setup of TikTok Api wrapper
*/ */
static public function api(): \Sovit\TikTok\Api { static public function api(): \TikScraper\Api {
$options = []; $options = [
'remote_signer' => self::env('SIGNER_URL', 'http://localhost:8080/signature')
];
$cacheEngine = false; $cacheEngine = false;
// Proxy config // Proxy config
foreach(Cookies::PROXY as $proxy_element) { foreach(Cookies::PROXY as $proxy_element) {
@ -55,13 +57,13 @@ class Misc {
} else { } else {
$host = $_ENV['REDIS_HOST']; $host = $_ENV['REDIS_HOST'];
$port = (int) $_ENV['REDIS_PORT']; $port = (int) $_ENV['REDIS_PORT'];
$password = isset($_ENV['REDIS_PASSWORD']) ? $_ENV['REDIS_PASSWORD'] : null; $password = $_ENV['REDIS_PASSWORD'] ?? null;
} }
$cacheEngine = new RedisCache($host, $port, $password); $cacheEngine = new RedisCache($host, $port, $password);
break; break;
} }
} }
$api = new \Sovit\TikTok\Api($options, $cacheEngine); $api = new \TikScraper\Api($options, $cacheEngine);
return $api; return $api;
} }

View file

@ -0,0 +1,16 @@
<?php
namespace App\Models;
use TikScraper\Models\Discover;
/**
* Base for templates with a feed
*/
class DiscoverTemplate extends BaseTemplate {
public Discover $feed;
function __construct(Discover $feed) {
parent::__construct('Discover');
$this->feed = $feed;
}
}

View file

@ -1,13 +1,15 @@
<?php <?php
namespace App\Models; namespace App\Models;
use TikScraper\Models\Feed;
/** /**
* Base for templates with a feed * Base for templates with a feed
*/ */
class FeedTemplate extends BaseTemplate { class FeedTemplate extends BaseTemplate {
public object $feed; public Feed $feed;
function __construct(string $title, object $feed) { function __construct(string $title, Feed $feed) {
parent::__construct($title); parent::__construct($title);
$this->feed = $feed; $this->feed = $feed;
} }

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;
}
}

View file

@ -17,7 +17,7 @@
</div> </div>
{/foreach} {/foreach}
</div> </div>
<div n:ifset="$feed->info" class="buttons"> <div class="buttons">
{if isset($_GET['cursor']) && $_GET['cursor'] != 0 } {if isset($_GET['cursor']) && $_GET['cursor'] != 0 }
<a class="button is-danger" href="?">First</a> <a class="button is-danger" href="?">First</a>
<button class="button is-danger" onclick="history.back()">Back</button> <button class="button is-danger" onclick="history.back()">Back</button>

View file

@ -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": "1.5.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",
@ -11,16 +11,10 @@
"homepage": "https://github.com/pablouser1" "homepage": "https://github.com/pablouser1"
} }
], ],
"repositories": [
{
"type": "vcs",
"url": "https://github.com/pablouser1/TikTok-API-PHP"
}
],
"require": { "require": {
"ext-redis": "^5.3.2", "ext-redis": "^5.3.2",
"ext-mbstring": "*", "ext-mbstring": "*",
"ssovit/tiktok-api": "dev-rework", "pablouser1/tikscraper": "^1.0",
"latte/latte": "^2.10", "latte/latte": "^2.10",
"vlucas/phpdotenv": "^5.4", "vlucas/phpdotenv": "^5.4",
"bramus/router": "^1.6", "bramus/router": "^1.6",

105
composer.lock generated
View file

@ -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": "e829a003ddc8a31048406dfd3ec20815", "content-hash": "f5e451a254a58bf7d512421237641801",
"packages": [ "packages": [
{ {
"name": "bramus/router", "name": "bramus/router",
@ -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.1",
"source": {
"type": "git",
"url": "https://github.com/pablouser1/TikScraperPHP.git",
"reference": "2022b2c2d661383d09c4727091df7fae345844a5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pablouser1/TikScraperPHP/zipball/2022b2c2d661383d09c4727091df7fae345844a5",
"reference": "2022b2c2d661383d09c4727091df7fae345844a5",
"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.1"
},
"time": "2022-02-13T20:28:38+00:00"
},
{ {
"name": "phpoption/phpoption", "name": "phpoption/phpoption",
"version": "1.8.1", "version": "1.8.1",
@ -375,58 +414,6 @@
], ],
"time": "2021-12-04T23:24:31+00:00" "time": "2021-12-04T23:24:31+00:00"
}, },
{
"name": "ssovit/tiktok-api",
"version": "dev-rework",
"source": {
"type": "git",
"url": "https://github.com/pablouser1/TikTok-API-PHP.git",
"reference": "60c00cfd321888b80eba93b6214c8c4003e98e57"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pablouser1/TikTok-API-PHP/zipball/60c00cfd321888b80eba93b6214c8c4003e98e57",
"reference": "60c00cfd321888b80eba93b6214c8c4003e98e57",
"shasum": ""
},
"type": "library",
"autoload": {
"psr-4": {
"Sovit\\": "lib"
}
},
"archive": {
"exclude": [
"/example",
"/.github",
"/.editorconfig"
]
},
"license": [
"MIT"
],
"authors": [
{
"name": "Sovit Tamrakar",
"email": "sovit.tamrakar@gmail.com",
"homepage": "https://github.com/ssovit/",
"role": "Developer"
}
],
"description": "Unofficial TikTok API for PHP",
"homepage": "https://github.com/ssovit/TikTok-API-PHP",
"keywords": [
"tiktok",
"tiktok-api",
"tiktok-scraper"
],
"support": {
"source": "https://github.com/ssovit/TikTok-API-PHP",
"issues": "https://github.com/ssovit/TikTok-API-PHP/issues",
"email": "sovit.tamrakar@gmail.com"
},
"time": "2022-02-07T22:43:53+00:00"
},
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
"version": "v1.24.0", "version": "v1.24.0",
@ -543,12 +530,12 @@
} }
}, },
"autoload": { "autoload": {
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
},
"files": [ "files": [
"bootstrap.php" "bootstrap.php"
] ],
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
}
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
"license": [ "license": [
@ -759,9 +746,7 @@
"packages-dev": [], "packages-dev": [],
"aliases": [], "aliases": [],
"minimum-stability": "stable", "minimum-stability": "stable",
"stability-flags": { "stability-flags": [],
"ssovit/tiktok-api": 20
},
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {

View file

@ -20,7 +20,7 @@
<p> <p>
This project wouldn't be possible without the help of the following projects: This project wouldn't be possible without the help of the following projects:
<ul> <ul>
<li><a rel="nofollow" href="https://github.com/ssovit/TikTok-API-PHP">TikTok-API-PHP</a></li> <li><a rel="nofollow" href="https://github.com/pablouser1/TikScraperPHP">TikScraper</a></li>
<li><a rel="nofollow" href="https://github.com/nette/latte">Latte</a></li> <li><a rel="nofollow" href="https://github.com/nette/latte">Latte</a></li>
<li><a rel="nofollow" href="https://github.com/bramus/router">bramus/router</a></li> <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/vlucas/phpdotenv">PHP dotenv</a></li>

View file

@ -1,9 +1,9 @@
{layout '../layouts/default.latte'} {layout '../layouts/default.latte'}
{block header} {block header}
<p class="title">{$feed->info->detail->music->title}</p> <p class="title">{$feed->info->detail->title}</p>
<p class="subtitle">{$feed->info->detail->music->desc}</p> <p class="subtitle">{$feed->info->detail->desc}</p>
<p>Videos: {number($feed->info->detail->stats->videoCount)}</p> <p>Videos: {number($feed->info->stats->videoCount)}</p>
{/block} {/block}
{block content} {block content}

View file

@ -1,10 +1,10 @@
{layout '../layouts/default.latte'} {layout '../layouts/default.latte'}
{block header} {block header}
<p class="title">{$feed->info->detail->challenge->title}</p> <p class="title">{$feed->info->detail->title}</p>
<p class="subtitle">{$feed->info->detail->challenge->desc}</p> <p class="subtitle">{$feed->info->detail->desc}</p>
<p>Videos: {number($feed->info->detail->stats->videoCount)} / Views: {number($feed->info->detail->stats->viewCount)}</p> <p>Videos: {number($feed->info->stats->videoCount)} / Views: {number($feed->info->stats->viewCount)}</p>
<a href="{path('tag/' . $feed->info->detail->challenge->title . '/rss')}">RSS</a> <a href="{path('tag/' . $feed->info->detail->title . '/rss')}">RSS</a>
{/block} {/block}
{block content} {block content}

View file

@ -2,13 +2,13 @@
{block header} {block header}
<figure class="figure is-96x96"> <figure class="figure is-96x96">
<img src="{path('/stream?url=' . urlencode($feed->info->detail->user->avatarThumb))}" /> <img src="{path('/stream?url=' . urlencode($feed->info->detail->avatarThumb))}" />
</figure> </figure>
<p class="title">{$feed->info->detail->user->uniqueId}'s profile</p> <p class="title">{$feed->info->detail->uniqueId}'s profile</p>
<p class="subtitle">{$feed->info->detail->user->signature}</p> <p class="subtitle">{$feed->info->detail->signature}</p>
<p>Following: {number($feed->info->detail->stats->followingCount)} / Followers: {number($feed->info->detail->stats->followerCount)}</p> <p>Following: {number($feed->info->stats->followingCount)} / Followers: {number($feed->info->stats->followerCount)}</p>
<p>Hearts: {number($feed->info->detail->stats->heartCount)} / Videos: {$feed->info->detail->stats->videoCount}</p> <p>Hearts: {number($feed->info->stats->heartCount)} / Videos: {$feed->info->stats->videoCount}</p>
<a href="{path('/@' . $feed->info->detail->user->uniqueId . '/rss')}">RSS</a> <a href="{path('/@' . $feed->info->detail->uniqueId . '/rss')}">RSS</a>
{/block} {/block}
{block content} {block content}

View file

@ -3,20 +3,20 @@
{block content} {block content}
<div class="columns is-centered is-vcentered"> <div class="columns is-centered is-vcentered">
<div class="column"> <div class="column">
<video controls poster="{path('/stream?url=' . urlencode($item->items[0]->video->originCover))}"> <video controls poster="{path('/stream?url=' . urlencode($feed->items[0]->video->originCover))}">
<source src="{path('/stream?url=' . urlencode($item->items[0]->video->playAddr))}" type="video/mp4" /> <source src="{path('/stream?url=' . urlencode($feed->items[0]->video->playAddr))}" type="video/mp4" />
</video> </video>
</div> </div>
<div class="column has-text-centered"> <div class="column has-text-centered">
<div class="box"> <div class="box">
<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('/@'.$feed->info->detail->uniqueId)}">{$feed->info->detail->uniqueId}</a></p>
<p class="subtitle">{$item->items[0]->desc}</p> <p class="subtitle">{$feed->items[0]->desc}</p>
<p>Played {number($item->info->detail->stats->playCount)} times</p> <p>Played {number($feed->info->stats->playCount)} times</p>
<p>Shared {number($item->info->detail->stats->shareCount)} times / {number($item->info->detail->stats->commentCount)} comments</p> <p>Shared {number($feed->info->stats->shareCount)} times / {number($feed->info->stats->commentCount)} comments</p>
<hr /> <hr />
<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($feed->items[0]->video->playAddr) . '&download=1')}" class="button is-info">Download video</a>
<p>{$item->items[0]->music->title}</p> <p>{$feed->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($feed->items[0]->music->playUrl))}" controls preload="none"></audio>
</div> </div>
</div> </div>
</div> </div>