Patching system, video by id

This commit is contained in:
Pablo Ferreiro 2022-01-08 16:03:57 +01:00
parent 38fa75402b
commit c6b0f1e812
No known key found for this signature in database
GPG key ID: 41FBCE65B779FA24
19 changed files with 305 additions and 60 deletions

View file

@ -6,6 +6,7 @@ Use Tiktok using an alternative frontend, inspired by Nitter.
* See user's feed * See user's feed
* See trending * See trending
* See tags * See tags
* See video by id
* Create a following list, which you can later use to see all the feeds from those users * Create a following list, which you can later use to see all the feeds from those users
## Installation ## Installation
@ -44,11 +45,14 @@ location /tiktok-viewer/.env {
``` ```
## Known issues ## Known issues
* Right now there is an error when trying to fetch the desired user, there is already a pull request not merged yet fixing this issue on the TikTokApi repo, you can check it out [here](https://github.com/ssovit/TikTok-API-PHP/pull/43). **This is automatically patched after running composer install** * Fetching a user fails, there is already a pull request not merged yet fixing this issue on the TikTokApi repo, you can check it out [here](https://github.com/ssovit/TikTok-API-PHP/pull/43).
* Fetching a video by id or by url fails
**These issues are automatically patched after running composer install**
## TODO ## TODO
* Allow searching for just one video using the ID
* Add a NoJS version / Make the whole program without required JS * Add a NoJS version / Make the whole program without required JS
* Better error handling
* Make video on /video fit screen and don't overflow
## Credits ## Credits
* [TikTok-API-PHP](https://github.com/ssovit/TikTok-API-PHP) * [TikTok-API-PHP](https://github.com/ssovit/TikTok-API-PHP)

View file

@ -1,24 +1,33 @@
{ {
"name": "pablouser1/tiktok-viewer", "name": "pablouser1/tiktok-viewer",
"description": "An alternative frontend for TikTok", "description": "An alternative frontend for TikTok",
"version": "1.0.0", "version": "1.1.0",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"require": { "require": {
"php": "^7.3|^8.0",
"ext-curl": "*", "ext-curl": "*",
"ssovit/tiktok-api": "^2.0", "ssovit/tiktok-api": "^2.0",
"steampixel/simple-php-router": "^0.7.0", "steampixel/simple-php-router": "^0.7.0",
"latte/latte": "^2.10", "latte/latte": "^2.10",
"vlucas/phpdotenv": "^5.4" "vlucas/phpdotenv": "^5.4",
"cweagans/composer-patches": "^1.7"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Helpers\\": "helpers/" "Helpers\\": "helpers/"
} }
}, },
"scripts": { "config": {
"post-install-cmd": [ "allow-plugins": {
"php fix_api.php" "cweagans/composer-patches": true
] }
},
"extra": {
"patches": {
"ssovit/tiktok-api": [
"patches/0001-Fixed-getUser.patch",
"patches/0002-Added-support-for-username-as-well-as-user-id.patch",
"patches/0003-Fixed-getVideoByUrl.patch"
]
}
} }
} }

107
composer.lock generated
View file

@ -4,8 +4,56 @@
"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": "e064040cff1f97fef9172828540a1aac", "content-hash": "f82588da7787761ecda16112b8c1d1b1",
"packages": [ "packages": [
{
"name": "cweagans/composer-patches",
"version": "1.7.1",
"source": {
"type": "git",
"url": "https://github.com/cweagans/composer-patches.git",
"reference": "9888dcc74993c030b75f3dd548bb5e20cdbd740c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cweagans/composer-patches/zipball/9888dcc74993c030b75f3dd548bb5e20cdbd740c",
"reference": "9888dcc74993c030b75f3dd548bb5e20cdbd740c",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0 || ^2.0",
"php": ">=5.3.0"
},
"require-dev": {
"composer/composer": "~1.0 || ~2.0",
"phpunit/phpunit": "~4.6"
},
"type": "composer-plugin",
"extra": {
"class": "cweagans\\Composer\\Patches"
},
"autoload": {
"psr-4": {
"cweagans\\Composer\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Cameron Eagans",
"email": "me@cweagans.net"
}
],
"description": "Provides a way to patch Composer packages.",
"support": {
"issues": "https://github.com/cweagans/composer-patches/issues",
"source": "https://github.com/cweagans/composer-patches/tree/1.7.1"
},
"time": "2021-06-08T15:12:46+00:00"
},
{ {
"name": "graham-campbell/result-type", "name": "graham-campbell/result-type",
"version": "v1.0.4", "version": "v1.0.4",
@ -70,16 +118,16 @@
}, },
{ {
"name": "latte/latte", "name": "latte/latte",
"version": "v2.10.7", "version": "v2.10.8",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nette/latte.git", "url": "https://github.com/nette/latte.git",
"reference": "a69d0b9598652438b5754ae5c1abc217d5003d98" "reference": "596b28bf098ebb852732d60b00538139a009c4db"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nette/latte/zipball/a69d0b9598652438b5754ae5c1abc217d5003d98", "url": "https://api.github.com/repos/nette/latte/zipball/596b28bf098ebb852732d60b00538139a009c4db",
"reference": "a69d0b9598652438b5754ae5c1abc217d5003d98", "reference": "596b28bf098ebb852732d60b00538139a009c4db",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -148,9 +196,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.7" "source": "https://github.com/nette/latte/tree/v2.10.8"
}, },
"time": "2021-12-21T11:22:49+00:00" "time": "2022-01-04T14:13:28+00:00"
}, },
{ {
"name": "phpoption/phpoption", "name": "phpoption/phpoption",
@ -314,21 +362,24 @@
}, },
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
"version": "v1.23.0", "version": "v1.24.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git", "url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" "reference": "30885182c981ab175d4d034db0f6f469898070ab"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab",
"reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", "reference": "30885182c981ab175d4d034db0f6f469898070ab",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=7.1" "php": ">=7.1"
}, },
"provide": {
"ext-ctype": "*"
},
"suggest": { "suggest": {
"ext-ctype": "For best performance" "ext-ctype": "For best performance"
}, },
@ -373,7 +424,7 @@
"portable" "portable"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" "source": "https://github.com/symfony/polyfill-ctype/tree/v1.24.0"
}, },
"funding": [ "funding": [
{ {
@ -389,25 +440,28 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-02-19T12:13:01+00:00" "time": "2021-10-20T20:35:02+00:00"
}, },
{ {
"name": "symfony/polyfill-mbstring", "name": "symfony/polyfill-mbstring",
"version": "v1.23.1", "version": "v1.24.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git", "url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6" "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9174a3d80210dca8daa7f31fec659150bbeabfc6", "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825",
"reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6", "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=7.1" "php": ">=7.1"
}, },
"provide": {
"ext-mbstring": "*"
},
"suggest": { "suggest": {
"ext-mbstring": "For best performance" "ext-mbstring": "For best performance"
}, },
@ -453,7 +507,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.1" "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.24.0"
}, },
"funding": [ "funding": [
{ {
@ -469,20 +523,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-05-27T12:26:48+00:00" "time": "2021-11-30T18:21:41+00:00"
}, },
{ {
"name": "symfony/polyfill-php80", "name": "symfony/polyfill-php80",
"version": "v1.23.1", "version": "v1.24.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php80.git", "url": "https://github.com/symfony/polyfill-php80.git",
"reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be" "reference": "57b712b08eddb97c762a8caa32c84e037892d2e9"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/1100343ed1a92e3a38f9ae122fc0eb21602547be", "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/57b712b08eddb97c762a8caa32c84e037892d2e9",
"reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be", "reference": "57b712b08eddb97c762a8caa32c84e037892d2e9",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -536,7 +590,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.23.1" "source": "https://github.com/symfony/polyfill-php80/tree/v1.24.0"
}, },
"funding": [ "funding": [
{ {
@ -552,7 +606,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-07-28T13:41:28+00:00" "time": "2021-09-13T13:58:33+00:00"
}, },
{ {
"name": "vlucas/phpdotenv", "name": "vlucas/phpdotenv",
@ -642,9 +696,8 @@
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {
"php": "^7.3|^8.0",
"ext-curl": "*" "ext-curl": "*"
}, },
"platform-dev": [], "platform-dev": [],
"plugin-api-version": "2.0.0" "plugin-api-version": "2.2.0"
} }

View file

@ -1,7 +0,0 @@
<?php
if (!file_exists(__DIR__ . '/.new_api')) {
$new_api = file_get_contents('https://raw.githubusercontent.com/attend-dunce/TikTok-API-PHP/master/lib/TikTok/Api.php');
file_put_contents(__DIR__ . '/vendor/ssovit/tiktok-api/lib/TikTok/Api.php', $new_api);
file_put_contents(__DIR__ . '/.new_api', 'DO NOT DELETE! This file proves that you have patched the API');
echo('Patched API!');
}

View file

@ -0,0 +1,40 @@
From 7c52f9291fbd15d912fa78d0301939e3aa9deec4 Mon Sep 17 00:00:00 2001
From: attend-dunce <96889312+attend-dunce@users.noreply.github.com>
Date: Thu, 30 Dec 2021 21:35:14 +0100
Subject: [PATCH 1/3] Fixed `getUser`
---
lib/TikTok/Api.php | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/lib/TikTok/Api.php b/lib/TikTok/Api.php
index e605eef..4a35f61 100644
--- a/lib/TikTok/Api.php
+++ b/lib/TikTok/Api.php
@@ -385,16 +385,14 @@ if (!\class_exists('\Sovit\TikTok\Api')) {
}
}
$username = urlencode($username);
- $result = $this->remote_call("https://www.tiktok.com/@{$username}?lang=en", false);
- if (preg_match('/<script id="__NEXT_DATA__"([^>]+)>([^<]+)<\/script>/', $result, $matches)) {
- $result = json_decode($matches[2], false);
- if (isset($result->props->pageProps->userInfo)) {
- $result = $result->props->pageProps->userInfo;
- if ($this->cacheEnabled) {
- $this->cacheEngine->set($cacheKey, $result, $this->_config['cache-timeout']);
- }
- return $result;
+ $result = $this->remote_call("https://www.tiktok.com/api/user/detail/?userId={$username}", false);
+ $result = json_decode($result, false);
+ if (isset($result->userInfo)) {
+ $result = $result->userInfo;
+ if ($this->cacheEnabled) {
+ $this->cacheEngine->set($cacheKey, $result, $this->_config['cache-timeout']);
}
+ return $result;
}
return $this->failure();
}
--
2.34.1

View file

@ -0,0 +1,26 @@
From c948bda5d6f30929b448fe78a0920739d5792d4b Mon Sep 17 00:00:00 2001
From: attend-dunce <96889312+attend-dunce@users.noreply.github.com>
Date: Fri, 31 Dec 2021 00:55:23 +0100
Subject: [PATCH 2/3] Added support for username as well as user id
---
lib/TikTok/Api.php | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/TikTok/Api.php b/lib/TikTok/Api.php
index 4a35f61..8fd42ae 100644
--- a/lib/TikTok/Api.php
+++ b/lib/TikTok/Api.php
@@ -385,7 +385,8 @@ if (!\class_exists('\Sovit\TikTok\Api')) {
}
}
$username = urlencode($username);
- $result = $this->remote_call("https://www.tiktok.com/api/user/detail/?userId={$username}", false);
+ $param = is_numeric($username) ? "userId" : "uniqueId";
+ $result = $this->remote_call("https://www.tiktok.com/api/user/detail/?{$param}={$username}", false);
$result = json_decode($result, false);
if (isset($result->userInfo)) {
$result = $result->userInfo;
--
2.34.1

View file

@ -0,0 +1,50 @@
From c23e8dbf0bd722799cf19182142d4b177f8fd933 Mon Sep 17 00:00:00 2001
From: Pablo Ferreiro <pferreiromero@gmail.com>
Date: Sat, 8 Jan 2022 15:47:21 +0100
Subject: [PATCH 3/3] Fixed getVideoByUrl
---
lib/TikTok/Api.php | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/lib/TikTok/Api.php b/lib/TikTok/Api.php
index 8fd42ae..f800555 100644
--- a/lib/TikTok/Api.php
+++ b/lib/TikTok/Api.php
@@ -483,20 +483,27 @@ if (!\class_exists('\Sovit\TikTok\Api')) {
throw new \Exception("Invalid VIDEO URL");
}
$result = $this->remote_call($url, false);
- $result = Helper::string_between($result, '{"props":{"initialProps":{', "</script>");
+ $result = Helper::string_between($result, "window['SIGI_STATE']=", ";window['SIGI_RETRY']=");
if (!empty($result)) {
- $jsonData = json_decode('{"props":{"initialProps":{' . $result);
- if (isset($jsonData->props->pageProps->itemInfo->itemStruct)) {
+ $jsonData = json_decode($result);
+ if (isset($jsonData->ItemModule, $jsonData->ItemList, $jsonData->UserModule)) {
+ $id = $jsonData->ItemList->video->keyword;
+ $item = $jsonData->ItemModule->{$id};
+ $username = $item->author;
$result = (object) [
'statusCode' => 0,
'info' => (object) [
'type' => 'video',
- 'detail' => $url,
+ 'detail' => (object) [
+ "url" => $url,
+ "user" => $jsonData->UserModule->users->{$username},
+ "stats" => $item->stats
+ ],
],
- "items" => [$jsonData->props->pageProps->itemInfo->itemStruct],
+ "items" => [$item],
"hasMore" => false,
"minCursor" => '0',
- "maxCursor" => ' 0',
+ "maxCursor" => '0'
];
if ($this->cacheEnabled) {
$this->cacheEngine->set($cacheKey, $result, $this->_config['cache-timeout']);
--
2.34.1

View file

@ -1,18 +1,19 @@
<?php <?php
use Steampixel\Route; use Steampixel\Route;
const VALID_TIKTOK_DOMAINS = [
"tiktokcdn.com", "tiktokcdn-us.com", "tiktok.com"
];
/** /**
* Check if an url has a valid domain * Check if an url has a valid domain
* @param string $url URL you want to check * @param string $url URL you want to check
* @return bool * @return bool
*/ */
function isValidDomain(string $url): bool { function isValidDomain(string $url): bool {
$valid_domains = [
"tiktokcdn.com", "tiktokcdn-us.com", "tiktok.com"
];
$host = parse_url($url, PHP_URL_HOST); $host = parse_url($url, PHP_URL_HOST);
$host_split = explode('.', $host); $host_split = explode('.', $host);
return count($host_split) === 3 && in_array($host_split[1] . '.' . $host_split[2], $valid_domains); return count($host_split) === 3 && in_array($host_split[1] . '.' . $host_split[2], VALID_TIKTOK_DOMAINS);
} }
Route::add('/stream', function () { Route::add('/stream', function () {

View file

@ -10,6 +10,11 @@ Route::add('/', function () {
$latte->render(Misc::getView('home')); $latte->render(Misc::getView('home'));
}); });
Route::add('/about', function () {
$latte = Misc::latte();
$latte->render(Misc::getView('about'));
});
Route::add("/trending", function () { Route::add("/trending", function () {
$cursor = 0; $cursor = 0;
if (isset($_GET['cursor']) && is_numeric($_GET['cursor'])) { if (isset($_GET['cursor']) && is_numeric($_GET['cursor'])) {
@ -44,6 +49,17 @@ Route::add("/@([^/]+)", function (string $username) {
} }
}); });
Route::add('/video/(\d+)', function (string $video_id) {
$latte = Misc::latte();
$api = Misc::api();
$item = $api->getVideoByID($video_id);
if ($item) {
$latte->render(Misc::getView('video'), ['item' => $item]);
} else {
return 'ERROR!';
}
});
Route::add('/tag/(\w+)', function (string $name) { Route::add('/tag/(\w+)', function (string $name) {
$cursor = 0; $cursor = 0;
if (isset($_GET['cursor']) && is_numeric($_GET['cursor'])) { if (isset($_GET['cursor']) && is_numeric($_GET['cursor'])) {

View file

@ -21,10 +21,8 @@ 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') const toggleButton = (id, force) => document.getElementById(id) ? document.getElementById(id).toggleAttribute('disabled', force) : alert('That button does not exist')
// -- MODAL -- // // -- MODAL -- //
const swapData = ({ video_url, video_width, video_height, desc, video_download, music_title, music_url }) => { const swapData = ({ video_url, desc, video_download, music_title, music_url }) => {
video.src = video_url video.src = video_url
video.width = video_width
video.height = video_height
item_title.innerText = desc item_title.innerText = desc
download_button.href = video_download download_button.href = video_download
audio_title.innerText = music_title audio_title.innerText = music_title

View file

@ -1,16 +1,24 @@
const goToUser = (e) => { const goToUser = e => {
e.preventDefault() e.preventDefault()
const formData = new FormData(e.target) const formData = new FormData(e.target)
const username = formData.get('username') const username = formData.get('username')
window.location.href = `./@${username}` window.location.href = `./@${username}`
} }
const goToTag = (e) => { const goToTag = e => {
e.preventDefault() e.preventDefault()
const formData = new FormData(e.target) const formData = new FormData(e.target)
const tag = formData.get('tag') const tag = formData.get('tag')
window.location.href = `./tag/${tag}` window.location.href = `./tag/${tag}`
} }
const goToVideo = e => {
e.preventDefault()
const formData = new FormData(e.target)
const video_id = formData.get('video_id')
window.location.href = `./video/${video_id}`
}
document.getElementById('username_form').addEventListener('submit', goToUser, false) document.getElementById('username_form').addEventListener('submit', goToUser, false)
document.getElementById('tag_form').addEventListener('submit', goToTag, false) document.getElementById('tag_form').addEventListener('submit', goToTag, false)
document.getElementById('video_form').addEventListener('submit', goToVideo, false)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -10,6 +10,7 @@
// Elements // Elements
@import "./node_modules/bulma/sass/elements/button.sass"; @import "./node_modules/bulma/sass/elements/button.sass";
@import "./node_modules/bulma/sass/elements/box.sass";
@import "./node_modules/bulma/sass/elements/container.sass"; @import "./node_modules/bulma/sass/elements/container.sass";
@import "./node_modules/bulma/sass/elements/other.sass"; @import "./node_modules/bulma/sass/elements/other.sass";
@import "./node_modules/bulma/sass/elements/tag.sass"; @import "./node_modules/bulma/sass/elements/tag.sass";
@ -17,7 +18,7 @@
// Form // Form
@import "./node_modules/bulma/sass/form/shared.sass"; @import "./node_modules/bulma/sass/form/shared.sass";
@import "./node_modules/bulma/sass/form/checkbox_radio.sass"; @import "./node_modules/bulma/sass/form/checkbox-radio.sass";
@import "./node_modules/bulma/sass/form/tools.sass"; @import "./node_modules/bulma/sass/form/tools.sass";
// Components // Components

View file

@ -113,9 +113,9 @@ readdirp@~3.6.0:
picomatch "^2.2.1" picomatch "^2.2.1"
sass@^1.46.0: sass@^1.46.0:
version "1.46.0" version "1.47.0"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.46.0.tgz#923117049525236026a7ede69715580eb0fac751" resolved "https://registry.yarnpkg.com/sass/-/sass-1.47.0.tgz#c22dd0eed2e4a991430dae0b03c8e694bc41c2b4"
integrity sha512-Z4BYTgioAOlMmo4LU3Ky2txR8KR0GRPLXxO38kklaYxgo7qMTgy+mpNN4eKsrXDTFlwS5vdruvazG4cihxHRVQ== integrity sha512-GtXwvwgD7/6MLUZPnlA5/8cdRgC9SzT5kAnnJMRmEZQFRE3J56Foswig4NyyyQGsnmNvg6EUM/FP0Pe9Y2zywQ==
dependencies: dependencies:
chokidar ">=3.0.0 <4.0.0" chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0" immutable "^4.0.0"

View file

@ -7,8 +7,6 @@
data-video_url="{path('stream?url=' . urlencode($item->video->playAddr))}" data-video_url="{path('stream?url=' . urlencode($item->video->playAddr))}"
data-video_download="{path('stream?url=' . urlencode($item->video->playAddr) . '&download=1')}" data-video_download="{path('stream?url=' . urlencode($item->video->playAddr) . '&download=1')}"
data-desc="{$item->desc}" data-desc="{$item->desc}"
data-video_width="{$item->video->width}"
data-video_height="{$item->video->height}"
data-music_title="{$item->music->title}" data-music_title="{$item->music->title}"
data-music_url="{path('stream?url=' . urlencode($item->music->playUrl))}"> data-music_url="{path('stream?url=' . urlencode($item->music->playUrl))}">
<img loading="lazy" src="{path('stream?url=' . urlencode($item->video->originCover))}" /> <img loading="lazy" src="{path('stream?url=' . urlencode($item->video->originCover))}" />

View file

@ -12,6 +12,7 @@
<a href="{path('')}" class="navbar-item">Home</a> <a href="{path('')}" class="navbar-item">Home</a>
<a href="{path('following')}" class="navbar-item">Following</a> <a href="{path('following')}" class="navbar-item">Following</a>
<a href="{path('settings')}" class="navbar-item">Settings</a> <a href="{path('settings')}" class="navbar-item">Settings</a>
<a href="{path('about')}" class="navbar-item">About</a>
</div> </div>
</div> </div>
</nav> </nav>

View file

@ -23,6 +23,18 @@
</div> </div>
</form> </form>
<hr /> <hr />
<p>Search video by id:</p>
<form id="video_form">
<div class="field has-addons has-addons-centered">
<div class="control">
<input name="video_id" class="input" type="text" placeholder="Type video ID" required />
</div>
<div class="control">
<button class="button is-success" type="submit">Go</button>
</div>
</div>
</form>
<hr />
<p>Search tag:</p> <p>Search tag:</p>
<form id="tag_form"> <form id="tag_form">
<div class="field has-addons has-addons-centered"> <div class="field has-addons has-addons-centered">

35
views/video.latte Normal file
View file

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
{include 'components/head.latte', title: $item->info->detail->user->nickname}
<body>
<section class="hero is-fullheight">
<div class="hero-head">
{include 'components/navbar.latte'}
</div>
<div class="hero-body">
<div class="columns">
<div class="column has-text-centered">
<video autoplay controls src="{path('stream?url=' . urlencode($item->items[0]->video->playAddr))}"></video>
</div>
<div class="column is-one-quarter">
<div class="box container">
<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>
<p>Shared {number($item->info->detail->stats->shareCount)} times / {number($item->info->detail->stats->commentCount)} comments</p>
<hr />
<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>
</div>
</div>
</div>
</div>
<div class="hero-foot">
{include 'components/footer.latte'}
</div>
</section>
</body>
</html>