Compare commits

...

111 Commits

Author SHA1 Message Date
Pablo Ferreiro ec611478e3 implemented following (AKA discover) & fyp using new endpoints
Docker / build (push) Has been cancelled
Create Tag / build (push) Has been cancelled
2024-08-28 14:34:02 +02:00
Pablo Ferreiro 96aa2803b2 bump scraper
Docker / build (push) Has been cancelled
Create Tag / build (push) Has been cancelled
2024-08-21 19:10:32 +02:00
Pablo Ferreiro 9f63fecc18 fixed null value on download 2024-08-21 13:30:56 +02:00
Pablo Ferreiro a27d6ca767 bump scraper 2024-08-21 13:24:40 +02:00
Pablo Ferreiro 2aab1bfe24 bump scraper 2024-08-21 13:09:06 +02:00
Pablo Ferreiro bc9ff0100e region may not show 2024-08-21 03:57:26 +02:00
Pablo Ferreiro c4f3a80c3e fixed error constructor 2024-08-21 03:30:52 +02:00
Pablo Ferreiro 3b441c2683 fixed undefined addr 2024-08-21 03:28:34 +02:00
Pablo Ferreiro 03002ba835 removed redis persist storage 2024-08-21 03:26:11 +02:00
Pablo Ferreiro 76d4a581f3 init redis and no need to expose chromedriver 2024-08-21 03:15:33 +02:00
Pablo Ferreiro 36d9902c73 bump 2024-08-21 03:03:13 +02:00
Pablo Ferreiro 98405545e7 controls using cursor 2024-08-21 02:35:42 +02:00
Pablo Ferreiro 9ac169d480 download fix 2024-08-21 02:05:38 +02:00
Pablo Ferreiro b63a0b2755 official instance now in austria 2024-08-21 01:39:46 +02:00
Pablo Ferreiro 1427a94810 Merge pull request #223 from yedayak/lite-short-url
Follow lite.tiktok.com, same as v[mt].tiktok.com
2024-08-21 00:35:21 +02:00
Pablo Ferreiro 547f8460b3 Merge pull request #205 from drizzt/podman-compatibility
Podman compatibility
2024-08-21 00:00:47 +02:00
Pablo Ferreiro 4610a89810 Merge branch 'master' into podman-compatibility 2024-08-21 00:00:28 +02:00
Pablo Ferreiro 70161ec0d6 Merge pull request #209 from jamesbrown8532/instances-list-json
Add instances list in JSON format
2024-08-20 23:55:00 +02:00
Pablo Ferreiro 2592f2087c bump scraper 2024-08-20 23:50:21 +02:00
Pablo Ferreiro a033682dfb breaking changes warning 2024-08-20 23:37:21 +02:00
Pablo Ferreiro 01c39fbc0e cosign bump 2024-08-20 23:29:29 +02:00
Pablo Ferreiro 086c206920 removed discover template 2024-08-20 23:27:42 +02:00
Pablo Ferreiro 672e752841 Merge pull request #224 from pablouser1/scraper_rewrite
v2.5.0.0: "Hopefully fixing the mess" edition (BREAKING CHANGES!)
2024-08-20 23:08:43 +02:00
Pablo Ferreiro 591bb2c726 fixed download issue 2024-08-20 22:49:24 +02:00
Pablo Ferreiro 4e49badfc4 bump 2024-08-20 22:40:25 +02:00
Pablo Ferreiro 5001bf0e91 docker comments 2024-08-20 22:33:43 +02:00
Pablo Ferreiro 71931971f7 removed trending and discover 2024-08-20 22:28:58 +02:00
Pablo Ferreiro f2e3475cb9 bump tikscraper 2024-08-20 22:28:21 +02:00
Pablo Ferreiro 9536d9e78a bump latte 2024-08-20 21:08:05 +02:00
Pablo Ferreiro 86fdeff56e removed discover and trending from home 2024-08-20 21:05:06 +02:00
Pablo Ferreiro bbbe6c2dba docker 2024-08-20 21:02:34 +02:00
Pablo Ferreiro f1f69533a8 reflect scraper changes 2024-08-20 20:13:43 +02:00
Yedaya Katsman 3eebeb0369 Follow lite.tiktok.com, same as v[mt].tiktok.com
Fixes https://github.com/pablouser1/ProxiTok/issues/222
2024-08-18 22:59:09 +03:00
Pablo Ferreiro 107ca2f9e2 cache change 2024-08-14 20:01:17 +02:00
jamesbrown8532 f203dca5a2 Fix typo in instances.json 2024-02-18 12:56:03 +00:00
jamesbrown8532 db1de45ee1 Remove duplicate entry 2024-02-18 01:41:45 +00:00
jamesbrown8532 fe80102212 Put Tor instance link in correct entry 2024-02-18 01:41:28 +00:00
jamesbrown8532 a99b8ebd54 Mention instances.json in README.md 2024-02-18 01:25:11 +00:00
jamesbrown8532 d33e9d119a Add instances list in JSON format 2024-02-18 01:23:41 +00:00
Pablo Ferreiro 358c6ad08f bump scraper 2024-02-13 18:46:17 +01:00
Timothy Redaelli 7c51b63660 Add io.containers.autoupdate to tell podman to auto update containers
See https://docs.podman.io/en/latest/markdown/podman-auto-update.1.html
for more informations
2024-01-25 18:48:21 +01:00
Timothy Redaelli 13e686bb5a Add the full image: path in docker-compose.yml 2024-01-25 18:35:58 +01:00
Timothy Redaelli 250bbedc1d Remove persistent storage from redis in docker-compose.yml
This is needed to restore podman-compose compatibility, since it doesn't
support uid and gid options in tmpfs, moreover it's quite silly to have
persistent storage on a tmpfs since /data is deleted when the container
is stopped.

Fixes: 48eccbc5ec ("harden docker-compose")
Cc: admin@nullnet.services
2024-01-25 18:35:58 +01:00
Pablo Ferreiro dd377a27be bump 2024-01-25 13:20:52 +01:00
Pablo Ferreiro 79454aa9e0 Merge pull request #187 from rystaf/user-avatar-rss
rss feed: include user avatar, use nickname for title and username for item creator.
2023-11-29 16:57:55 +00:00
Pablo Ferreiro 9f1e2c44e4 Merge pull request #191 from taivlam/patch-1
Fix typo in bug report template
2023-11-28 15:18:33 +00:00
taivlam 846ab1ccae Fix typo in bug_report.md
Missing "n" in the word "environment"
2023-10-30 18:11:04 -04:00
Ryan Stafford dfe22f6a18 use user nickname for rss title, and username for item creator 2023-10-10 14:42:30 -04:00
Ryan Stafford b85b1f4148 conform image tag to atom spec 2023-10-10 14:31:28 -04:00
Ryan Stafford a573ac1990 Include user avatar in rss feed 2023-10-10 13:47:53 -04:00
Pablo Ferreiro a3b4143ab1 Show debug info when TikTok gives error 2023-10-07 00:17:11 +02:00
Pablo Ferreiro 3ba64be202 avoid crash when using user id instead of username 2023-09-29 13:52:04 +02:00
Pablo Ferreiro 80f3ca4ebb Improved description extras 2023-09-29 13:31:31 +02:00
Pablo Ferreiro 200a6fc1f8 Some UI changes 2023-09-29 13:29:20 +02:00
Pablo Ferreiro c546ed406c hotfix 2023-09-27 14:17:28 +02:00
Pablo Ferreiro 7c333b1658 wrapper 2.4, changed PROXY config, docker php82 2023-09-27 13:24:27 +02:00
Pablo Ferreiro 9d9207b05e shut down signer (way) faster 2023-07-26 22:21:06 +02:00
Pablo Ferreiro 923d42c6c1 auto yes 2023-07-26 21:56:42 +02:00
Pablo Ferreiro 5ba084dae1 bump cosign and pin php-nginx version 2023-07-26 21:51:32 +02:00
Pablo Ferreiro 4f906ef801 new docker image 2023-07-26 21:41:37 +02:00
Pablo Ferreiro 15fdd41cad small changes to UI 2023-07-04 16:39:53 +02:00
Pablo Ferreiro 34623c8e43 hotfix 2023-06-12 18:57:05 +02:00
Pablo Ferreiro 629da5f357 Fixed more crashing issues 2023-06-12 15:20:51 +02:00
Pablo Ferreiro 4a59dab75c Docker updated to PHP8.2 2023-06-12 15:20:42 +02:00
Pablo Ferreiro 8c769d89d4 Fixed isAd crash 2023-06-12 15:08:02 +02:00
Pablo Ferreiro 19a2066fc2 changed keyword order 2023-04-27 20:23:23 +02:00
Pablo Ferreiro e43580f3f9 RSS TextExtra and Gallery support 2023-04-27 20:22:55 +02:00
Pablo Ferreiro b5d034437f fix multiple carousel in one page 2023-04-08 12:03:33 +02:00
Pablo Ferreiro 4e191922fe accept both desktop and mobile user agents scraper 2023-04-05 17:09:22 +02:00
Pablo Ferreiro d09830cc89 bump scraper 2023-04-03 16:24:51 +02:00
Pablo Ferreiro 3556fef036 commerce fix 2023-04-03 14:37:04 +02:00
Pablo Ferreiro 7e65da7fe9 fixes to ttag 2023-04-02 20:37:36 +02:00
Pablo Ferreiro 3bd6c553f4 bump 2023-04-02 20:18:10 +02:00
Pablo Ferreiro aadfeccab1 Added some details 2023-04-02 20:14:48 +02:00
Pablo Ferreiro 4188fcf064 Initial URLify of vid desc and profile 2023-04-02 19:55:04 +02:00
Pablo Ferreiro 9364a2e212 hotfix 2023-04-01 18:45:10 +02:00
Pablo Ferreiro e5d416f696 pictures using carousel 2023-04-01 17:09:12 +02:00
Pablo Ferreiro 98a9ec2b65 hotfix 2023-04-01 16:35:17 +02:00
Pablo Ferreiro 33c723d786 Pictures support 2023-04-01 15:48:27 +02:00
Pablo Ferreiro faee8d58f2 bump 2023-03-29 11:23:14 +02:00
Pablo Ferreiro 75e13bc7d2 Merge pull request #140 from NoPlagiarism/www_shortened
Fix https:// + www in redirections (fixes #139)
2023-03-29 09:05:36 +00:00
Pablo Ferreiro 08145ee90b Merge pull request #135 from kuanhulio/master
harden docker-compose
2023-03-29 09:04:10 +00:00
NoPlagiarism 6194324452 Fix https:// + www in redirections (#139) 2023-03-29 07:39:36 +05:00
Nullnet Services Administrator 48eccbc5ec harden docker-compose 2023-03-18 21:10:24 -05:00
Pablo Ferreiro cac97ff05e Initial comments support 2023-03-06 21:20:14 +01:00
Pablo Ferreiro eeaa8cdf1c Random User Agent generator 2023-03-05 13:18:34 +01:00
Pablo Ferreiro ecba96eaf4 Merge pull request #131 from chrisnovakovic/video-title
Set page title to video description in video view
2023-02-28 11:40:30 +00:00
Chris Novakovic 013feeb679 Set page title to video description in video view
If the upstream video has a description set, use it as the page title in
place of "Video" in the video view. This helps to disambiguate video
pages in the browser history.
2023-02-27 23:11:36 +00:00
Pablo Ferreiro bf86e0d36a Merge pull request #125 from samip5/configureable_user_agent
Ability to pass custom user agent to the scraper and ignore .git on Docker
2023-02-19 11:08:22 +00:00
Skyler Mäntysaari 72ea5d43fd Addressing PR comments about not using hardcoded string. 2023-02-17 22:32:36 +02:00
Skyler Mäntysaari 288f87504c Add .git to dockerignore
Otherwise bad things end up in container.
2023-02-16 02:26:25 +02:00
Skyler Mäntysaari 3d5890d821 Ability to pass own user agent to the scraper. 2023-02-08 12:59:17 +02:00
Pablo Ferreiro 5dea2468f8 Better handling of private accounts 2023-02-01 15:22:53 +01:00
Pablo Ferreiro e8ec0ab293 bump 2023-01-28 17:32:07 +01:00
Pablo Ferreiro 78fe44070c Fixed Trending and normalized Discover 2023-01-26 19:21:14 +01:00
Pablo Ferreiro 7d2f6e8fd6 bump scraper 2023-01-26 17:20:38 +01:00
Pablo Ferreiro 32d49954f9 hotfix 2023-01-25 21:10:47 +01:00
Pablo Ferreiro 5c770def41 better ui 2023-01-25 21:06:59 +01:00
Pablo Ferreiro d273b35e53 Added apcu caching method and bump 2023-01-25 15:56:23 +01:00
Pablo Ferreiro faf1dcee51 bump, video showing up 2023-01-25 15:18:46 +01:00
Pablo Ferreiro 89d9ad2f9c Mobile-friendly 2023-01-25 15:00:52 +01:00
Pablo Ferreiro 7ddf6f7738 bump cosign 2023-01-25 14:10:35 +01:00
Pablo Ferreiro 059bf7208a bump 2023-01-25 13:59:59 +01:00
Pablo Ferreiro d5858c39fd Fixed undefined vars 2022-12-27 16:58:03 +01:00
Pablo Ferreiro 76be343c13 Merge pull request #114 from TheFrenchGhosty/patch-1
Update the credits, deprecate my image
2022-12-27 14:37:27 +00:00
TheFrenchGhosty a7b0edf8fe Update the credits, deprecate my image 2022-12-21 17:37:23 +01:00
Pablo Ferreiro 73e9d7a1cd Dynamically generated manifest 2022-11-27 14:26:35 +01:00
Pablo Ferreiro 0224cd25eb Added url_tag 2022-11-27 00:05:11 +01:00
Pablo Ferreiro 215f984fe4 Properly implemented OG 2022-11-26 23:51:45 +01:00
Pablo Ferreiro ee6e8b0593 Revert "Added better og support"
This reverts commit fa3f1e0a7c.
2022-11-26 23:36:20 +01:00
Pablo Ferreiro fa3f1e0a7c Added better og support 2022-11-26 23:28:59 +01:00
74 changed files with 3110 additions and 637 deletions
+5 -7
View File
@@ -1,8 +1,6 @@
node_modules
/.env
/.vscode
/vendor
/cache/latte/*
!/cache/latte/.gitkeep
/cache/api/*
!/cache/api/.gitkeep
.env
.git
.vscode
vendor
cache
+1 -1
View File
@@ -22,7 +22,7 @@ indent_style = space
indent_size = 4
[**.latte]
indent_style = tab
indent_style = space
indent_size = 2
[**.html]
+8 -10
View File
@@ -2,21 +2,19 @@
# LATTE_CACHE=/tmp/proxitok_api # Path for Latte cache, leave commented for ./cache/latte
# API CONFIG
# API_SIGNER="remote"
# API_SIGNER_URL="http://localhost:8080"
# API_TEST_ENDPOINTS=true # Uncomment for usage of testing TikTok endpoints, may help sometimes
# API_CHROMEDRIVER=http://localhost:4444
# API_VERIFYFP=verify_...
# API_DEVICE_ID=586094...
# API_CACHE=redis
# Redis cache, used on Helpers\CacheEngines\RedisCache (CHOOSE ONE)
# Redis cache, used on App\Cache\RedisCache (CHOOSE ONE)
# REDIS_HOST=localhost # Host or path to unix socket
# REDIS_PORT=6379 # Set to -1 to use unix socket
# REDIS_PASSWORD=example # Leave commented for no password
# JSON cache, used on Helpers\CacheEngines\JSONCache
# JSON cache, used on App\Cache\JSONCache
# API_CACHE_JSON=/tmp/proxitok_api # Path for JSON API Cache, leave commented for ./cache/api
# Proxy Config
# PROXY_HOST=http://EXAMPLE_IP # Host to be used as proxy, make sure to type a valid protocol. Available: http:// | https:// | socks4:// | socks5://
# PROXY_PORT=8080
# PROXY_USERNAME=username
# PROXY_PASSWORD=password
# PROXY="https://user:password@host:port" # Host to be used as proxy, make sure to type a valid protocol. Available: http:// | https:// | socks4:// | socks5://
# USER_AGENT="Mozilla/5.0 (Android 12; Mobile; rv:109.0) Gecko/109.0 Firefox/109.0"
+2 -2
View File
@@ -3,7 +3,7 @@ name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: pablouser1
assignees: ''
---
@@ -25,7 +25,7 @@ A clear and concise description of what you expected to happen.
**Logs**
If applicable, add PHP logs to help explain the issue.
**Enviroment (please complete the following information):**
**Environment (please complete the following information):**
- Deployment method (if possible): [e.g docker, heroku, manual...]
- Frontend version: [e.g. 2.2.0.0]
+5 -6
View File
@@ -42,9 +42,9 @@ jobs:
# https://github.com/sigstore/cosign-installer
- name: Install cosign
if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@main
uses: sigstore/cosign-installer@v3.6.0
with:
cosign-release: 'v1.13.1'
cosign-release: 'v2.4.0'
# Workaround: https://github.com/docker/build-push-action/issues/461
@@ -96,7 +96,6 @@ jobs:
- name: Sign the published Docker image
if: ${{ github.event_name != 'pull_request' }}
env:
COSIGN_EXPERIMENTAL: "true"
# This step uses the identity token to provision an ephemeral certificate
# against the sigstore community Fulcio instance.
run: cosign sign ${{ steps.meta.outputs.tags }}@${{ steps.build-and-push.outputs.digest }}
DIGEST: ${{ steps.build-and-push.outputs.digest }}
TAGS: ${{ steps.meta.outputs.tags }}
run: cosign sign --yes "${TAGS}@${DIGEST}"
+18 -21
View File
@@ -1,25 +1,22 @@
FROM php:8.1-apache
WORKDIR /var/www/html
FROM trafex/php-nginx:3.6.0
# Add composer
COPY --from=composer /usr/bin/composer /usr/bin/composer
RUN apt update -y && apt upgrade -y \
&& apt install -y --no-install-recommends libzip-dev \
&& pecl install redis zip \
&& docker-php-ext-enable redis zip \
&& a2enmod rewrite headers \
&& mkdir /cache \
&& chown -R www-data:www-data /cache \
&& rm -rf /var/www/html/*
# Copy project to /var/www/html
COPY . .
# Copy config
COPY ./misc/setup/docker/php.ini /etc/php83/conf.d/settings.ini
# Run composer and clean
RUN composer update --no-cache \
&& composer install --no-interaction --optimize-autoloader --no-dev --no-cache \
&& apt autoclean -y \
&& apt autoremove -y \
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf /usr/bin/composer
USER root
# Create cache folder
RUN mkdir /cache && chown -R nobody:nogroup /cache
# Install deps
RUN apk add --no-cache php83-redis php83-zip php83-tokenizer
EXPOSE 80
CMD apachectl -D FOREGROUND
USER nobody
# Copy source
COPY --chown=nobody . /var/www/html
# Dependencies
RUN composer install --no-interaction --optimize-autoloader --no-dev --no-cache
EXPOSE 8080
+10 -3
View File
@@ -1,14 +1,16 @@
# ProxiTok
Use Tiktok with an alternative frontend, inspired by Nitter.
## WARNING
THE LATEST VERSION **v2.5.0.0** INCLUDES BREAKING CHANGES, CHECK THE [PULL REQUEST](https://github.com/pablouser1/ProxiTok/pull/224) FOR MORE INFO
## Features
* Privacy: All requests made to TikTok are server-side, so you will never connect to their servers
* See user's feed
* See trending and discovery tab
* See tags
* See video by id
* Themes
* RSS Feed for user, trending and tag (just add /rss to the url)
* RSS Feed for user and tag (just add /rss to the url)
## Self-hosting
Please check [this](https://github.com/pablouser1/ProxiTok/wiki/Self-hosting) wiki article for info on how to self-host your own instance
@@ -16,6 +18,8 @@ Please check [this](https://github.com/pablouser1/ProxiTok/wiki/Self-hosting) wi
## Public instances
[This](https://github.com/pablouser1/ProxiTok/wiki/Public-instances) wiki article contains a list with all the known public instances.
Instances list in JSON format can be found in [instances.json](instances.json) file.
## Extensions
If you want to automatically redirect Tiktok links to ProxiTok you can use:
* [Libredirect](https://github.com/libredirect/libredirect)
@@ -33,6 +37,7 @@ Apply to: Main window (address bar)
```
## TODO / Known issues
* Fix Heroku, still using old signer method
* Replace placeholder favicon
* Make video on /video fit screen and don't overflow
* Fix embed styling
@@ -40,7 +45,9 @@ Apply to: Main window (address bar)
* Add custom amount of videos per page
## Credits
[@TheFrenchGhosty](https://github.com/TheFrenchGhosty): Initial Dockerfile and fixes to a usable state. You can check his Docker image [here](https://github.com/PussTheCat-org/docker-proxitok-quay) on Github or [here](https://quay.io/repository/pussthecatorg/proxitok) on Quay
* [TheFrenchGhosty](https://thefrenchghosty.me) ([Github](https://github.com/TheFrenchGhosty)): Initial Dockerfile and fixes to a usable state.
* [Jennifer Wjertzoch](https://wjertzochjennifer.medium.com): Carousel CSS Implementation
### External libraries
* [TikScraperPHP](https://github.com/pablouser1/TikScraperPHP)
* [Latte](https://github.com/nette/latte)
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace App\Cache;
use TikScraper\Interfaces\ICache;
class ApcuCache implements ICache {
function __construct() {
if (!(extension_loaded('apcu') && apcu_enabled())) {
throw new \Exception('APCu not enabled');
}
}
public function get(string $cache_key): ?object {
$data = apcu_fetch($cache_key);
return $data !== false ? json_decode($data) : null;
}
public function exists(string $cache_key): bool {
return apcu_exists($cache_key);
}
public function set(string $cache_key, string $data, int $timeout = 3600): void {
apcu_store($cache_key, $data, $timeout);
}
}
+3 -3
View File
@@ -1,9 +1,9 @@
<?php
namespace App\Cache;
use TikScraper\Interfaces\CacheInterface;
use TikScraper\Interfaces\ICache;
class JSONCache implements CacheInterface {
class JSONCache implements ICache {
private string $cache_path = __DIR__ . '/../../cache/api';
function __construct() {
@@ -26,7 +26,7 @@ class JSONCache implements CacheInterface {
return is_file($filename);
}
public function set(string $cache_key, string $data, $timeout = 3600) {
public function set(string $cache_key, string $data, int $timeout = 3600) {
file_put_contents($this->cache_path . '/' . $cache_key . '.json', $data);
}
}
+3 -3
View File
@@ -1,9 +1,9 @@
<?php
namespace App\Cache;
use TikScraper\Interfaces\CacheInterface;
use TikScraper\Interfaces\ICache;
class RedisCache implements CacheInterface {
class RedisCache implements ICache {
private \Redis $client;
function __construct(string $host, int $port, ?string $password) {
$this->client = new \Redis();
@@ -30,7 +30,7 @@ class RedisCache implements CacheInterface {
return $this->client->exists($cache_key);
}
public function set(string $cache_key, string $data, $timeout = 3600) {
public function set(string $cache_key, string $data, int $timeout = 3600) {
$this->client->set($cache_key, $data, $timeout);
}
}
+1
View File
@@ -3,5 +3,6 @@ namespace App\Constants;
abstract class CacheMethods {
const JSON = 'json';
const APCU = 'apcu';
const REDIS = 'redis';
}
+7
View File
@@ -0,0 +1,7 @@
<?php
namespace App\Constants;
abstract class TextExtras {
const USER = 0;
const HASHTAG = 1;
}
-18
View File
@@ -1,18 +0,0 @@
<?php
namespace App\Controllers;
use App\Helpers\ErrorHandler;
use App\Helpers\Wrappers;
use App\Models\FeedTemplate;
class DiscoverController {
static public function get() {
$api = Wrappers::api();
$feed = $api->discover();
if ($feed->meta->success) {
Wrappers::latte('discover', new FeedTemplate('Discover', $feed));
} else {
ErrorHandler::showMeta($feed->meta);
}
}
}
+4 -3
View File
@@ -6,13 +6,14 @@ use App\Helpers\Wrappers;
use App\Models\VideoTemplate;
class EmbedController {
static public function v2(int $id) {
public static function v2(int $id) {
$api = Wrappers::api();
$video = $api->video($id);
$video->feed();
if ($video->ok()) {
$data = $video->getFull();
Wrappers::latte('video', new VideoTemplate($data->feed->items[0], $data->info->detail, true));
$item = $video->getFeed()->items[0];
$info = $video->getInfo();
Wrappers::latte('video', new VideoTemplate($item, $info, true));
} else {
ErrorHandler::showMeta($video->error());
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace App\Controllers;
use App\Helpers\ErrorHandler;
use App\Helpers\Misc;
use App\Helpers\Wrappers;
use App\Models\FeedTemplate;
class FollowingController {
public static function get() {
$cursor = Misc::getCursor();
$api = Wrappers::api();
$following = $api->following();
$following->feed($cursor);
if ($following->ok()) {
$feed = $following->getFeed();
Wrappers::latte('following', new FeedTemplate('Following / Discover', $feed));
} else {
ErrorHandler::showMeta($following->error());
}
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace App\Controllers;
use App\Helpers\ErrorHandler;
use App\Helpers\Misc;
use App\Helpers\Wrappers;
use App\Models\FeedTemplate;
use App\Models\RSSTemplate;
class ForYouController {
public static function get() {
$api = Wrappers::api();
$fyp = $api->foryou();
$fyp->feed();
if ($fyp->ok()) {
$feed = $fyp->getFeed();
Wrappers::latte('foryou', new FeedTemplate('For you', $feed));
} else {
ErrorHandler::showMeta($fyp->error());
}
}
public static function rss() {
$api = Wrappers::api();
$fyp = $api->foryou();
$fyp->feed();
if ($fyp->ok()) {
$feed = $fyp->getFeed();
Misc::rss('For you');
Wrappers::latte('rss', new RSSTemplate('For you', 'For you Page', '', Misc::url('/foryou'), $feed->items));
}
}
}
+4 -3
View File
@@ -7,15 +7,16 @@ use App\Helpers\Wrappers;
use App\Models\FullTemplate;
class MusicController {
static public function get(string $music_id) {
public static function get(string $music_id) {
$cursor = Misc::getCursor();
$api = Wrappers::api();
$music = $api->music($music_id);
$music->feed($cursor);
if ($music->ok()) {
$data = $music->getFull();
Wrappers::latte('music', new FullTemplate('Music', $data));
$info = $music->getInfo();
$feed = $music->getFeed();
Wrappers::latte('music', new FullTemplate('Music', $info, $feed));
} else {
ErrorHandler::showMeta($music->error());
}
+32 -29
View File
@@ -2,27 +2,53 @@
namespace App\Controllers;
use App\Helpers\Cookies;
use TikScraper\Helpers\Converter;
use App\Helpers\Misc;
class ProxyController {
const VALID_TIKTOK_DOMAINS = [
"tiktokcdn.com", "tiktokcdn-us.com", "tiktok.com"
];
static private function isValidDomain(string $url) {
public static function stream() {
self::checkUrl();
$url = $_GET['url'];
$options = Misc::getScraperOptions();
$streamer = new \TikScraper\Stream($options);
$streamer->url($url);
}
public static function download() {
self::checkUrl();
$method = Cookies::downloader();
$downloader = new \TikScraper\Download($method, Misc::getScraperOptions());
// Params
$id = $_GET['id'] ?? '';
$watermark = isset($_GET['watermark']);
$url = $_GET['url'];
$user = $_GET['user'] ?? '';
// Filename
$filename = self::getFilename($id, $user);
// Running
$downloader->url($url, $filename, $watermark);
}
static private function isValidDomain(string $url): bool {
$valid = false;
$host = parse_url($url, PHP_URL_HOST);
$host_split = explode('.', $host);
$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);
$valid = 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);
$valid = in_array($host_split[1] . '.' . $host_split[2], self::VALID_TIKTOK_DOMAINS);
}
return false;
return $valid;
}
static private function checkUrl() {
static private function checkUrl(): void {
if (!isset($_GET['url'])) {
die('You need to send a URL');
}
@@ -37,27 +63,4 @@ class ProxyController {
$filename = 'tiktok-video-' . $id . '-' . $user;
return $filename;
}
static public function stream() {
self::checkUrl();
$url = $_GET['url'];
$streamer = new \TikScraper\Stream();
$streamer->url($url);
}
static public function download() {
self::checkUrl();
$method = Cookies::downloader();
$downloader = new \TikScraper\Download($method);
// Params
$id = $_GET['id'] ?? '';
$watermark = isset($_GET['watermark']);
$url = $_GET['url'];
$user = $_GET['user'] ?? '';
// Filename
$filename = self::getFilename($id, $user);
// Running
$downloader->url($url, $filename, $watermark);
}
}
+21 -2
View File
@@ -2,12 +2,13 @@
namespace App\Controllers;
use App\Helpers\ErrorHandler;
use App\Helpers\Misc;
use App\Helpers\UrlBuilder;
/**
* Used to be compatible with HTML forms
*/
class RedirectController {
static public function redirect() {
public static function search() {
$endpoint = '/';
if (isset($_GET['type'], $_GET['term'])) {
$term = trim($_GET['term']);
@@ -48,11 +49,29 @@ class RedirectController {
header("Location: {$url}");
}
public static function download() {
if (!(isset($_GET['videoId'], $_GET['authorUsername'], $_GET['playAddr']))) {
ErrorHandler::showText(400, 'Request incomplete');
return;
}
$watermark = isset($_GET['watermark']) && $_GET['watermark'] === 'yes' ? true : false;
$url = '';
if ($watermark) {
$url = UrlBuilder::download($_GET['playAddr'], $_GET['authorUsername'], $_GET['videoId'], true);
} else {
$url = UrlBuilder::download(UrlBuilder::video_external($_GET['authorUsername'], $_GET['videoId']), $_GET['authorUsername'], $_GET['videoId'], false);
}
header("Location: {$url}");
}
/**
* to_endpoint maps a TikTok URL into a ProxiTok-compatible endpoint URL.
*/
static private function to_endpoint(string $url): string {
if (preg_match('%^(?:https?://|www\.)?(?:vm\.|vt\.)?tiktok\.com/(?:t/)?([A-Za-z0-9]+)%', $url, $m)) {
if (preg_match('%^(?:https?://)?(?:www\.)?(?:vm\.|vt\.|lite\.)?tiktok\.com/(?:t/)?([A-Za-z0-9]+)%', $url, $m)) {
// Short video URL
return '/@placeholder/video/' . $m[1];
} elseif (preg_match('%^https://www\.tiktok\.com/(.+)%', $url, $m)) {
+3 -8
View File
@@ -7,11 +7,11 @@ use App\Helpers\Wrappers;
use App\Models\SettingsTemplate;
class SettingsController {
static public function index() {
public static function index() {
Wrappers::latte('settings', new SettingsTemplate());
}
static public function general() {
public static function general() {
if (isset($_POST['theme'])) {
$theme = $_POST['theme'];
Cookies::set('theme', $theme);
@@ -19,13 +19,8 @@ class SettingsController {
self::redirect();
}
static public function api() {
public static function api() {
// TODO, ADD COUNT
if (isset($_POST['api-test_endpoints'])) {
$test_endpoints = $_POST['api-test_endpoints'];
Cookies::set('api-test_endpoints', $test_endpoints);
}
if (isset($_POST['api-downloader'])) {
$downloader = $_POST['api-downloader'];
Cookies::set("api-downloader", $downloader);
+6 -5
View File
@@ -9,27 +9,28 @@ use App\Models\FullTemplate;
use App\Models\RSSTemplate;
class TagController {
static public function get(string $name) {
public static function get(string $name) {
$cursor = Misc::getCursor();
$api = Wrappers::api();
$hashtag = $api->hashtag($name);
$hashtag->feed($cursor);
if ($hashtag->ok()) {
$data = $hashtag->getFull();
Wrappers::latte('tag', new FullTemplate($data->info->detail->title, $data));
$info = $hashtag->getInfo();
$feed = $hashtag->getFeed();
Wrappers::latte('tag', new FullTemplate($info->detail->title, $info, $feed));
} else {
ErrorHandler::showMeta($hashtag->error());
}
}
static public function rss(string $name) {
public static function rss(string $name) {
$api = Wrappers::api();
$hashtag = $api->hashtag($name);
$hashtag->feed();
if ($hashtag->ok()) {
$data = $hashtag->getFull();
Misc::rss($name);
Wrappers::latte('rss', new RSSTemplate($name, $data->info->detail->desc, UrlBuilder::tag($name), $data->feed->items));
Wrappers::latte('rss', new RSSTemplate($name, $data->info->detail->desc, '', UrlBuilder::tag($name), $data->feed->items));
}
}
}
-37
View File
@@ -1,37 +0,0 @@
<?php
namespace App\Controllers;
use App\Helpers\Misc;
use App\Models\FeedTemplate;
use App\Helpers\ErrorHandler;
use App\Helpers\Wrappers;
use App\Models\RSSTemplate;
class TrendingController {
static public function get() {
$api = Wrappers::api();
$cursor = Misc::getTtwid();
$trending = $api->trending();
$trending->feed($cursor);
$feed = $trending->getFeed();
if ($feed && $feed->meta->success) {
Wrappers::latte('trending', new FeedTemplate('Trending', $feed));
} else {
ErrorHandler::showMeta($trending->error());
}
}
static public function rss() {
$api = Wrappers::api();
$trending = $api->trending();
$trending->feed();
$feed = $trending->getFeed();
if ($feed && $feed->meta->success) {
Misc::rss('Trending');
Wrappers::latte('rss', new RSSTemplate('Trending', 'Trending on TikTok', Misc::url('/trending'), $feed->items));
}
}
}
+10 -12
View File
@@ -10,43 +10,41 @@ use App\Models\RSSTemplate;
use App\Models\VideoTemplate;
class UserController {
static public function get(string $username) {
public static function get(string $username) {
$cursor = Misc::getCursor();
$api = Wrappers::api();
$user = $api->user($username);
$user->feed($cursor);
if ($user->ok()) {
$data = $user->getFull();
if ($data->info->detail->privateAccount) {
ErrorHandler::showText(401, "Private account detected! Not supported");
return;
}
Wrappers::latte('user', new FullTemplate($data->info->detail->nickname, $data));
$info = $user->getInfo();
$feed = $user->getFeed();
Wrappers::latte('user', new FullTemplate($info->detail->nickname, $info, $feed));
} else {
ErrorHandler::showMeta($user->error());
}
}
static public function video(string $username, string $video_id) {
public static function video(string $username, string $video_id) {
$api = Wrappers::api();
$video = $api->video($video_id);
$video->feed();
if ($video->ok()) {
$data = $video->getFull();
Wrappers::latte('video', new VideoTemplate($data->feed->items[0], $data->info->detail));
$item = $video->getFeed()->items[0];
$info = $video->getInfo();
Wrappers::latte('video', new VideoTemplate($item, $info));
} else {
ErrorHandler::showMeta($video->error());
}
}
static public function rss(string $username) {
public static function rss(string $username) {
$api = Wrappers::api();
$user = $api->user($username);
$user->feed();
if ($user->ok()) {
$data = $user->getFull();
Misc::rss($username);
Wrappers::latte('rss', new RSSTemplate($username, $data->info->detail->signature, UrlBuilder::user($username), $data->feed->items));
Wrappers::latte('rss', new RSSTemplate($data->info->detail->nickname, $data->info->detail->signature, UrlBuilder::stream($data->info->detail->avatarLarger), UrlBuilder::user($username), $data->feed->items));
}
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
namespace App\Controllers;
class VideoController {
static public function get(string $video_id) {
public static function get(string $video_id) {
UserController::video('placeholder', $video_id);
}
}
+6 -6
View File
@@ -4,14 +4,14 @@ namespace App\Helpers;
use App\Constants\Themes;
class Cookies {
static public function get(string $name, string $default_value = ''): string {
public static function get(string $name, string $default_value = ''): string {
if (isset($_COOKIE[$name]) && !empty($_COOKIE[$name])) {
return $_COOKIE[$name];
}
return $default_value;
}
static public function theme(): string {
public static function theme(): string {
$theme = self::get('theme');
$ref = new \ReflectionClass(Themes::class);
$themes = $ref->getConstants();
@@ -21,20 +21,20 @@ class Cookies {
return 'default';
}
static public function downloader(): string {
public static function downloader(): string {
$downloader = self::get('api-downloader', 'default');
return $downloader;
}
static public function exists(string $name): bool {
public static function exists(string $name): bool {
return isset($_COOKIE[$name]);
}
static public function check(string $name, string $value): bool {
public static function check(string $name, string $value): bool {
return self::exists($name) && $_COOKIE[$name] === $value;
}
static public function set(string $name, string $value) {
public static function set(string $name, string $value) {
setcookie($name, $value, time()+60*60*24*30, '/', '', isset($_SERVER['HTTPS']), true);
}
};
+5 -5
View File
@@ -5,13 +5,13 @@ use App\Models\ErrorTemplate;
use TikScraper\Models\Meta;
class ErrorHandler {
static public function showMeta(Meta $meta) {
http_response_code($meta->http_code);
Wrappers::latte('error', new ErrorTemplate($meta->http_code, $meta->tiktok_msg, $meta->tiktok_code));
public static function showMeta(Meta $meta) {
http_response_code($meta->httpCode);
Wrappers::latte('error', new ErrorTemplate($meta->httpCode, $meta->proxitokMsg, $meta->proxitokCode, $meta->response));
}
static public function showText(int $code, string $msg) {
public static function showText(int $code, string $msg) {
http_response_code($code);
Wrappers::latte('error', new ErrorTemplate($code, $msg));
Wrappers::latte('error', new ErrorTemplate($code, $msg, null, null));
}
}
+34 -6
View File
@@ -2,33 +2,61 @@
namespace App\Helpers;
class Misc {
static public function getCursor(): int {
public static function getCursor(): int {
return isset($_GET['cursor']) && is_numeric($_GET['cursor']) ? (int) $_GET['cursor'] : 0;
}
static public function getTtwid(): string {
public static function getTtwid(): string {
return isset($_GET['cursor']) ? $_GET['cursor'] : '';
}
static public function url(string $endpoint = ''): string {
public static function url(string $endpoint = ''): string {
return self::env('APP_URL', '') . $endpoint;
}
static public function env(string $key, $default_value) {
public static function env(string $key, $default_value) {
return $_ENV[$key] ?? $default_value;
}
/**
* Returns absolute path for view
*/
static public function getView(string $template): string {
public static function getView(string $template): string {
return __DIR__ . "/../../templates/views/{$template}.latte";
}
public static function getScraperOptions(): array {
$debug = self::env('APP_DEBUG', false);
$url = self::env('API_CHROMEDRIVER', 'http://localhost:4444');
$verifyFp = self::env('API_VERIFYFP', '');
$device_id = self::env('API_DEVICE_ID', '');
$ua = self::env('USER_AGENT', '');
$options = [
'debug' => $debug,
'browser' => [
'url' => $url,
'close_when_done' => false
],
'verify_fp' => $verifyFp,
'device_id' => $device_id,
'user_agent' => $ua
];
$proxy = Misc::env('PROXY', '');
if ($proxy !== '') {
$options['proxy'] = $proxy;
}
return $options;
}
/**
* Common method for rss feeds
*/
static public function rss(string $title) {
public static function rss(string $title) {
header('Content-Type: application/rss+xml');
header('Content-Disposition: attachment; filename="' . $title . '.rss' . '"');
}
}
+6 -6
View File
@@ -2,29 +2,29 @@
namespace App\Helpers;
class UrlBuilder {
static public function stream(string $url): string {
public static function stream(string $url): string {
return Misc::url('/stream?url=' . urlencode($url));
}
static public function download(string $url, string $username, string $id, bool $watermark): string {
public static function download(string $url, string $username, string $id, bool $watermark): string {
$down_url = Misc::url('/download?url=' . urlencode($url) . '&id=' . $id . '&user=' . $username);
if ($watermark) $down_url .= '&watermark=1';
return $down_url;
}
static public function user(string $username): string {
public static function user(string $username): string {
return Misc::url('/@' . $username);
}
static public function tag(string $tag): string {
public static function tag(string $tag): string {
return Misc::url('/tag/' . $tag);
}
static public function video_internal(string $username, string $id): string {
public static function video_internal(string $username, string $id): string {
return Misc::url('/@' . $username . "/video/" . $id);
}
static public function video_external(string $username, string $id): string {
public static function video_external(string $username, string $id): string {
return "https://www.tiktok.com/@" . $username . "/video/" . $id;
}
}
+44 -41
View File
@@ -1,16 +1,18 @@
<?php
namespace App\Helpers;
use App\Cache\ApcuCache;
use App\Cache\JSONCache;
use App\Cache\RedisCache;
use App\Constants\CacheMethods;
use App\Constants\TextExtras;
use App\Models\BaseTemplate;
class Wrappers {
/**
* Setup of Latte template engine
*/
static public function latte(string $template, BaseTemplate $base) {
public static function latte(string $template, BaseTemplate $base) {
$latte = new \Latte\Engine;
$cache_path = Misc::env('LATTE_CACHE', __DIR__ . '/../../cache/latte');
$latte->setTempDirectory($cache_path);
@@ -22,7 +24,7 @@ class Wrappers {
});
// Static assets
$latte->addFunction('static', function (string $type, string $file, bool $isVendor = false): string {
$latte->addFunction('assets', function (string $type, string $file, bool $isVendor = false): string {
$endpoint = '';
switch ($type) {
case 'js':
@@ -32,7 +34,7 @@ class Wrappers {
$endpoint .= '/styles';
break;
default:
throw new \Exception('Invalid static asset type');
throw new \Exception('Invalid asset type');
}
if ($isVendor) $endpoint .= '/vendor';
@@ -76,6 +78,9 @@ class Wrappers {
$latte->addFunction('url_user', function (string $username): string {
return UrlBuilder::user($username);
});
$latte->addFunction('url_tag', function (string $tag): string {
return UrlBuilder::tag($tag);
});
$latte->addFunction('url_video_internal', function (string $username, string $id): string {
return UrlBuilder::video_internal($username, $id);
});
@@ -86,48 +91,43 @@ class Wrappers {
return UrlBuilder::download($url, $username, $id, $watermark);
});
$latte->addFunction('bool_to_str', function (bool $cond): string {
return $cond ? 'yes' : 'no';
});
// Add URLs to video descriptions
$latte->addFunction('render_desc', function (string $desc, array $textExtras = []): string {
$sanitizedDesc = htmlspecialchars($desc);
$out = $sanitizedDesc;
foreach ($textExtras as $extra) {
$url = '';
// We do -1 to also take the # or @
$text = mb_substr($desc, $extra->start - 1, $extra->end - $extra->start, 'UTF-8');
switch ($extra->type) {
// User URL
case TextExtras::USER:
$url = UrlBuilder::user(htmlspecialchars($extra->userUniqueId));
break;
// Hashtag URL
case TextExtras::HASHTAG:
$url = UrlBuilder::tag(htmlspecialchars($extra->hashtagName));
break;
}
// We do \b to avoid non-strict matches ('#hi' would match with '#hii' and we don't want that)
$out = preg_replace("/$text\b/", "<a href=\"$url\">$text</a>", $out);
}
return $out;
});
$latte->render(Misc::getView($template), $base);
}
/**
* Setup of TikTok Api wrapper
*/
static public function api(): \TikScraper\Api {
$method = Misc::env('API_SIGNER', '');
$url = Misc::env('API_SIGNER_URL', '');
if (!$method) {
// Legacy support
$browser_url = Misc::env('API_BROWSER_URL', '');
if ($url) {
$method = 'remote';
} elseif ($browser_url) {
$url = $browser_url;
$method = 'browser';
}
}
$options = [
'use_test_endpoints' => Misc::env('API_TEST_ENDPOINTS', false) || isset($_COOKIE['api-test_endpoints']) && $_COOKIE['api-test_endpoints'] === 'yes',
'signer' => [
'method' => $method,
'url' => $url,
'close_when_done' => false
]
];
// -- PROXY CONFIG -- //
$proxy_host = Misc::env('PROXY_HOST', '');
$proxy_port = Misc::env('PROXY_PORT', '');
if ($proxy_host && $proxy_port) {
$options['proxy'] = [
'host' => $proxy_host,
'port' => $proxy_port,
'username' => Misc::env('PROXY_USERNAME', null),
'password' => Misc::env('PROXY_PASSWORD', null)
];
}
public static function api(): \TikScraper\Api {
$options = Misc::getScraperOptions();
// Cache config
$cacheEngine = null;
if (isset($_ENV['API_CACHE'])) {
@@ -135,6 +135,9 @@ class Wrappers {
case CacheMethods::JSON:
$cacheEngine = new JSONCache();
break;
case CacheMethods::APCU:
$cacheEngine = new ApcuCache();
break;
case CacheMethods::REDIS:
if (!(isset($_ENV['REDIS_URL']) || isset($_ENV['REDIS_HOST'], $_ENV['REDIS_PORT']))) {
throw new \Exception('You need to set REDIS_URL or REDIS_HOST and REDIS_PORT to use Redis Cache!');
@@ -147,8 +150,8 @@ class Wrappers {
$password = $url['pass'] ?? null;
} else {
$host = $_ENV['REDIS_HOST'];
$port = (int) $_ENV['REDIS_PORT'];
$password = isset($_ENV['REDIS_PASSWORD']) ? $_ENV['REDIS_PASSWORD'] : null;
$port = intval($_ENV['REDIS_PORT']);
$password = $_ENV['REDIS_PASSWORD'] ?? null;
}
$cacheEngine = new RedisCache($host, $port, $password);
break;
+5 -1
View File
@@ -1,16 +1,20 @@
<?php
namespace App\Models;
use TikScraper\Models\Response;
class ErrorTemplate extends BaseTemplate {
public int $http_code = 502;
public ?int $tiktok_code = -1;
public string $msg = '';
public ?Response $response = null;
function __construct(int $http_code, string $msg, ?int $tiktok_code = null) {
function __construct(int $http_code, string $msg, ?int $tiktok_code, ?Response $response) {
parent::__construct('Error');
$this->http_code = $http_code;
$this->msg = $msg;
$this->tiktok_code = $tiktok_code;
$this->response = $response;
}
}
+5 -5
View File
@@ -1,16 +1,16 @@
<?php
namespace App\Models;
use TikScraper\Models\Feed;
/**
* Base for templates with a feed
*/
class FeedTemplate extends BaseTemplate {
public object $data;
public Feed $feed;
function __construct(string $title, object $feed) {
function __construct(string $title, Feed $feed) {
parent::__construct($title);
$this->data = (object) [
'feed' => $feed
];
$this->feed = $feed;
}
}
+7 -4
View File
@@ -1,16 +1,19 @@
<?php
namespace App\Models;
use TikScraper\Models\Full;
use TikScraper\Models\Feed;
use TikScraper\Models\Info;
/**
* Base for templates with both info and feed
*/
class FullTemplate extends BaseTemplate {
public Full $data;
public Info $info;
public Feed $feed;
function __construct(string $title, Full $data) {
function __construct(string $title, Info $info, Feed $feed) {
parent::__construct($title);
$this->data = $data;
$this->info = $info;
$this->feed = $feed;
}
}
+3 -1
View File
@@ -3,12 +3,14 @@ namespace App\Models;
class RSSTemplate extends BaseTemplate {
public string $desc;
public string $image;
public string $link;
public array $items;
function __construct(string $title, string $desc, string $link, array $items) {
function __construct(string $title, string $desc, string $image, string $link, array $items) {
parent::__construct($title);
$this->desc = $desc;
$this->image = $image;
$this->link = $link;
$this->items = $items;
}
-2
View File
@@ -11,7 +11,6 @@ use TikScraper\Constants\DownloadMethods;
class SettingsTemplate extends BaseTemplate {
public array $downloaders = [];
public array $themes = [];
public bool $isTestEndpoints = false;
public string $currentDownloader;
public string $currentTheme;
public bool $isServiceWorker = false;
@@ -25,7 +24,6 @@ class SettingsTemplate extends BaseTemplate {
$ref = new \ReflectionClass(Themes::class);
$this->themes = $ref->getConstants();
// Cookies data
$this->isTestEndpoints = Cookies::check('api-test_endpoints', 'yes');
$this->currentDownloader = Cookies::downloader();
$this->currentTheme = Cookies::theme();
$this->isServiceWorker = Cookies::check('misc-sw', 'yes');
+5 -3
View File
@@ -1,18 +1,20 @@
<?php
namespace App\Models;
use TikScraper\Models\Info;
/**
* Base for templates with a feed
*/
class VideoTemplate extends BaseTemplate {
public object $item;
public object $detail;
public Info $info;
public string $layout = 'hero';
function __construct(object $item, object $detail, bool $isEmbed = false) {
function __construct(object $item, Info $info, bool $isEmbed = false) {
parent::__construct('Video');
$this->item = $item;
$this->detail = $detail;
$this->info = $info;
if ($isEmbed) {
$this->layout = 'embed';
} else {
+9 -7
View File
@@ -1,7 +1,7 @@
{
"name": "pablouser1/proxitok",
"description": "An alternative frontend for TikTok",
"version": "2.4.3.3",
"version": "2.5.0.0",
"license": "AGPL-3.0-or-later",
"type": "project",
"authors": [
@@ -14,17 +14,19 @@
"config": {
"optimize-autoloader": true,
"platform": {
"php": "7.4"
"php": "8.1"
}
},
"require": {
"php": ">=7.4|^8.0",
"ext-redis": "^5.3.2",
"php": "^8.1",
"ext-mbstring": "*",
"latte/latte": "^2.11",
"latte/latte": "^3.0",
"bramus/router": "^1.6",
"josegonzalez/dotenv": "dev-master",
"pablouser1/tikscraper": "^2.3"
"pablouser1/tikscraper": "^2.6",
"josegonzalez/dotenv": "^4.0"
},
"suggest": {
"ext-redis": "For using redis as a caching engine"
},
"autoload": {
"psr-4": {
Generated
+643 -168
View File
File diff suppressed because it is too large Load Diff
+45 -11
View File
@@ -1,30 +1,64 @@
version: '3'
services:
web:
container_name: proxitok-web
image: ghcr.io/pablouser1/proxitok:master
ports:
- 8080:80
- 8080:8080
environment:
- LATTE_CACHE=/cache
- API_CACHE=redis
- REDIS_HOST=proxitok-redis
- REDIS_PORT=6379
- API_SIGNER=remote
- API_SIGNER_URL=http://proxitok-signer:8080/signature
- API_CHROMEDRIVER=http://proxitok-chromedriver:9515
# - API_VERIFYFP=verify_...
# - API_DEVICE_ID=4586...
labels:
- "io.containers.autoupdate=registry"
volumes:
- proxitok-cache:/cache
depends_on:
- redis
- signer
- chromedriver
networks:
- proxitok
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- CHOWN
- SETGID
- SETUID
# Redis, used for caching
redis:
container_name: proxitok-redis
image: redis:7-alpine
command: redis-server --save 60 1 --loglevel warning
image: docker.io/library/redis:7.4-alpine
restart: unless-stopped
signer:
container_name: proxitok-signer
image: ghcr.io/pablouser1/signtok:master
labels:
- "io.containers.autoupdate=registry"
networks:
- proxitok
init: true
user: nobody
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
# Chromedriver instance, used for sending requests to TikTok
chromedriver:
container_name: proxitok-chromedriver
image: zenika/alpine-chrome:with-chromedriver
init: true
labels:
- "io.containers.autoupdate=registry"
shm_size: 1g
networks:
- proxitok
security_opt:
- seccomp:./misc/setup/docker/chrome.json
volumes:
proxitok-cache:
networks:
proxitok:
+120
View File
@@ -0,0 +1,120 @@
[
{
"clearnet": "https://proxitok.pabloferreiro.es/",
"cdn": false,
"country": "AT"
},
{
"clearnet": "https://proxitok.pussthecat.org/",
"cdn": false,
"country": "DE"
},
{
"clearnet": "https://proxitok.esmailelbob.xyz/",
"tor": "http://proxitok.esmail5pdn24shtvieloeedh7ehz3nrwcdivnfhfcedl7gf4kwddhkqd.onion/",
"cdn": false,
"country": "CA"
},
{
"clearnet": "https://proxitok.privacydev.net/",
"tor": "http://proxitok.g4c3eya4clenolymqbpgwz3q3tawoxw56yhzk4vugqrl6dtu3ejvhjid.onion/",
"cdn": false,
"country": "FR"
},
{
"clearnet": "https://tok.artemislena.eu/",
"tor": "http://tok.lpoaj7z2zkajuhgnlltpeqh3zyq7wk2iyeggqaduhgxhyajtdt2j7wad.onion/",
"cdn": false,
"country": "DE"
},
{
"clearnet": "https://tok.adminforge.de/",
"cdn": false,
"country": "DE"
},
{
"clearnet": "https://tik.hostux.net/",
"cdn": false,
"country": "FR"
},
{
"clearnet": "https://tt.vern.cc/",
"tor": "http://tt.vernccvbvyi5qhfzyqengccj7lkove6bjot2xhh5kajhwvidqafczrad.onion/",
"i2p": "http://vern2gwxbtxzhb3rgchv4kbhlqppijhjqg7rmgvp4c5bxihxcm3a.b32.i2p/",
"cdn": false,
"country": "US"
},
{
"clearnet": "https://cringe.whatever.social/",
"cdn": false,
"country": "US"
},
{
"clearnet": "https://proxitok.lunar.icu/",
"cdn": true,
"country": "DE"
},
{
"clearnet": "https://proxitok.privacy.com.de/",
"cdn": false,
"country": "DE"
},
{
"clearnet": "https://cringe.whateveritworks.org/",
"cdn": true,
"country": "DE"
},
{
"clearnet": "https://cringe.seitan-ayoub.lol/",
"cdn": false,
"country": "DE"
},
{
"clearnet": "https://proxitok.kyun.li/",
"cdn": false,
"country": "RO"
},
{
"clearnet": "https://cringe.datura.network/",
"tor": "http://cringe.daturab6drmkhyeia4ch5gvfc2f3wgo6bhjrv3pz6n7kxmvoznlkq4yd.onion/",
"cdn": false,
"country": "DE"
},
{
"clearnet": "https://tt.opnxng.com/",
"cdn": false,
"country": "SG"
},
{
"clearnet": "https://proxitok.tinfoil-hat.net/",
"cdn": false,
"country": "DE"
},
{
"clearnet": "https://tiktok.wpme.pl/",
"cdn": true,
"country": "DE"
},
{
"clearnet": "https://proxitok.r4fo.com/",
"tor": "http://proxitok.r4focoma7gu2zdwwcjjad47ysxt634lg73sxmdbkdozanwqslho5ohyd.onion/",
"cdn": true,
"country": "NL"
},
{
"clearnet": "https://proxitok.belloworld.it/",
"cdn": false,
"country": "DE"
},
{
"clearnet": "https://tok.habedieeh.re/",
"tor": "http://tok.habeehrhadazsw3izbrbilqajalfyqqln54mrja3iwpqxgcuxnus7eid.onion/",
"cdn": false,
"country": "CA"
},
{
"tor": "http://pt.skunky7dhv7nohsoalpwe3sxfz3fbkad7r3wk632riye25vqm3meqead.onion/",
"i2p": "http://hm4d5t5jeycvraam3ptvmhlegfttjmxpxgl4lou2hibde7gzfsvq.b32.i2p/",
"cdn": false
}
]
+1
View File
@@ -28,6 +28,7 @@ $bulmaswatch-import-font: false;
@import "./node_modules/bulma/sass/form/tools.sass";
// Components
@import "./node_modules/bulma/sass/components/breadcrumb.sass";
@import "./node_modules/bulma/sass/components/card.sass";
@import "./node_modules/bulma/sass/components/dropdown.sass";
@import "./node_modules/bulma/sass/components/media.sass";
+2
View File
@@ -15,6 +15,8 @@
@import "./node_modules/css.gg/icons/scss/comment.scss";
@import "./node_modules/css.gg/icons/scss/share.scss";
@import "./node_modules/css.gg/icons/scss/software-download.scss";
@import "./node_modules/css.gg/icons/scss/check-o.scss";
@import "./node_modules/css.gg/icons/scss/tag.scss";
@import "./node_modules/css.gg/icons/scss/lock.scss";
@import "./node_modules/css.gg/icons/scss/lock-unlock.scss";
+12 -12
View File
@@ -48,9 +48,9 @@ bulmaswatch@^0.8.1:
fsevents "~2.3.2"
css.gg@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/css.gg/-/css.gg-2.0.0.tgz#1ba891092ceac58a2550540171c2d469a86b27b2"
integrity sha512-8qO59kSXLbewo7xaUM4eC8328MTdGmb8atpmw6PRzeDGsZrCkAVjJzKeFDIntagl1/aCee+NG1OQtIvWAjgG/A==
version "2.1.1"
resolved "https://registry.yarnpkg.com/css.gg/-/css.gg-2.1.1.tgz#62b2e242633c1cdf9de9840f2cf343ce03f6548e"
integrity sha512-OJrjd4+CdjrLvrVB+EFctAnk7eeM+zc8aKfVlO5BfNBgSqcC9wMHQTZ7ALcCiZGpE62kVVhwQ3Mvl1u6RkVFvQ==
fill-range@^7.0.1:
version "7.0.1"
@@ -60,9 +60,9 @@ fill-range@^7.0.1:
to-regex-range "^5.0.1"
fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
glob-parent@~5.1.2:
version "5.1.2"
@@ -72,9 +72,9 @@ glob-parent@~5.1.2:
is-glob "^4.0.1"
immutable@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.1.0.tgz#f795787f0db780183307b9eb2091fcac1f6fafef"
integrity sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==
version "4.3.4"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.4.tgz#2e07b33837b4bb7662f288c244d1ced1ef65a78f"
integrity sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==
is-binary-path@~2.1.0:
version "2.1.0"
@@ -118,9 +118,9 @@ readdirp@~3.6.0:
picomatch "^2.2.1"
sass@^1.46.0:
version "1.56.1"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.56.1.tgz#94d3910cd468fd075fa87f5bb17437a0b617d8a7"
integrity sha512-VpEyKpyBPCxE7qGDtOcdJ6fFbcpOM+Emu7uZLxVrkX8KVU/Dp5UF7WLvzqRuUhB6mqqQt1xffLoG+AndxTZrCQ==
version "1.68.0"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.68.0.tgz#0034b0cc9a50248b7d1702ac166fd25990023669"
integrity sha512-Lmj9lM/fef0nQswm1J2HJcEsBUba4wgNx2fea6yJHODREoMFnwRpZydBnX/RjyXw2REIwdkbqE4hrTo4qfDBUA==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
variables_order="EGPCS"
[Date]
date.timezone="UTC"
expose_php=Off
+40 -10
View File
@@ -2,6 +2,7 @@
/** @var \Bramus\Router\Router $router */
use App\Helpers\ErrorHandler;
use App\Helpers\Misc;
use App\Helpers\Wrappers;
use App\Models\BaseTemplate;
@@ -21,14 +22,39 @@ $router->get('/verify', function () {
Wrappers::latte('verify', new BaseTemplate('verify'));
});
$router->get('/manifest', function () {
header('Content-Type: application/json');
$data = [
"name" => "ProxiTok",
"short_name" => "ProxiTok",
"description" => "Use TikTok with a privacy-friendly alternative frontend",
"lang" => "en-US",
"theme_color" => "#4040ff",
"background_color" => "#ffffff",
"display" => "standalone",
"orientation" => "portrait-primary",
"icons" => [
[
"src" => Misc::url('/android-chrome-192x192.png'),
"sizes" => "192x192",
"type" => "image/png"
],
[
"src" => Misc::url('/android-chrome-512x512.png'),
"sizes" => "512x512",
"type" => "image/png"
]
],
"start_url" => Misc::url('/'),
"scope" => Misc::url('/')
];
echo json_encode($data, JSON_PRETTY_PRINT);
});
$router->get('/stream', 'ProxyController@stream');
$router->get('/download', 'ProxyController@download');
$router->get('/redirect', 'RedirectController@redirect');
$router->mount('/trending', function () use ($router) {
$router->get('/', 'TrendingController@get');
$router->get('/rss', 'TrendingController@rss');
});
$router->get('/redirect/search', 'RedirectController@search');
$router->get('/redirect/download', 'RedirectController@download');
$router->mount('/@([^/]+)', function () use ($router) {
$router->get('/', 'UserController@get');
@@ -36,8 +62,6 @@ $router->mount('/@([^/]+)', function () use ($router) {
$router->get('/rss', 'UserController@rss');
});
// Deprecated, please use /@USERNAME/video/VIDEO_ID instead
$router->get('/video/(\w+)', 'VideoController@get');
// Workaround that allows /t endpoints
$router->get('/t/([^/]+)', 'VideoController@get');
@@ -48,6 +72,14 @@ $router->mount('/tag/([^/]+)', function () use ($router) {
$router->get('/music/([^/]+)', 'MusicController@get');
// For you
$router->mount('/foryou', function () use ($router) {
$router->get('/', 'ForYouController@get');
$router->get('/rss', 'ForYouController@rss');
});
$router->get('/following', 'FollowingController@get');
// -- Settings -- //
$router->mount('/settings', function () use ($router) {
$router->get('/', 'SettingsController@index');
@@ -56,7 +88,5 @@ $router->mount('/settings', function () use ($router) {
$router->post('/misc', 'SettingsController@misc');
});
$router->get('/discover', 'DiscoverController@get');
// -- EMBED -- //
$router->get('/embed/v2/(\d+)', 'EmbedController@v2');
-23
View File
@@ -1,23 +0,0 @@
{
"name": "ProxiTok",
"short_name": "ProxiTok",
"description": "Use TikTok with a privacy-friendly alternative frontend",
"orientation": "portrait-primary",
"icons": [
{
"src": "android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"id": "./",
"start_url": "./",
"theme_color": "#4040ff",
"background_color": "#ffffff",
"display": "standalone"
}
+68
View File
@@ -0,0 +1,68 @@
/*
CREDITS:
CSS Taken from: https://levelup.gitconnected.com/how-to-make-a-fully-accessible-css-only-carousel-40e8bd62032b
Author: Jennifer Wjertzoch (https://wjertzochjennifer.medium.com)
CodePen: https://codepen.io/jwjertzoch/pen/JjyGeRy
*/
.carousel {
margin: 0 auto;
overflow: hidden;
text-align: center;
}
.slides {
width: 100%;
display: flex;
overflow-x: scroll;
scrollbar-width: none;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
}
.slides::-webkit-scrollbar {
display: none;
}
.slides-item {
max-height: 75vh;
align-items: center;
display: flex;
flex-shrink: 0;
justify-content: center;
margin: 0 1rem;
position: relative;
scroll-snap-align: start;
transform: scale(1);
transform-origin: center center;
transition: transform .5s;
width: 100%;
}
.slides-item img {
max-height: 75vh;
}
.carousel__nav {
padding: 1.25rem .5rem;
}
.slider-nav {
align-items: center;
background-color: #ddd;
border-radius: 50%;
color: #000;
display: inline-flex;
height: 1.5rem;
justify-content: center;
padding: .5rem;
position: relative;
text-decoration: none;
width: 1.5rem;
}
.slider-nav:hover,
.slider-nav:active {
background-color: #000;
color: #fff;
}
+3
View File
@@ -0,0 +1,3 @@
.new-line {
white-space: pre-wrap;
}
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
{"version":3,"sourceRoot":"","sources":["../../misc/scss/node_modules/css.gg/icons/scss/home.scss","../../misc/scss/node_modules/css.gg/icons/scss/options.scss","../../misc/scss/node_modules/css.gg/icons/scss/info.scss","../../misc/scss/node_modules/css.gg/icons/scss/code-slash.scss","../../misc/scss/node_modules/css.gg/icons/scss/search.scss","../../misc/scss/node_modules/css.gg/icons/scss/feed.scss","../../misc/scss/node_modules/css.gg/icons/scss/eye.scss","../../misc/scss/node_modules/css.gg/icons/scss/heart.scss","../../misc/scss/node_modules/css.gg/icons/scss/comment.scss","../../misc/scss/node_modules/css.gg/icons/scss/share.scss","../../misc/scss/node_modules/css.gg/icons/scss/software-download.scss","../../misc/scss/node_modules/css.gg/icons/scss/lock.scss","../../misc/scss/node_modules/css.gg/icons/scss/lock-unlock.scss","../../misc/scss/node_modules/css.gg/icons/scss/music.scss"],"names":[],"mappings":"AAAA,SACE,yLACA,sBACA,kBACA,cACA,+BACA,WACA,YACA,iBACA,aACA,gBACA,4BACA,2BACA,6BACA,4BACA,mBAEA,gBACE,WACA,cACA,sBACA,kBAGF,iBACE,WACA,cACA,sBACA,kBACA,qBACA,sBACA,2BACA,wBACA,SACA,kBACA,WACA,YACA,OAGF,gBACE,UACA,YACA,iBACA,oBACA,4BACA,6BACA,gBACA,SACA,SCjDJ,YACI,sBACA,kBACA,cACA,+BACA,WACA,WACA,qCAEA,mBACE,WACA,cACA,sBACA,kBACA,UACA,WACA,iBACA,mBAGF,oBACE,WACA,cACA,sBACA,kBACA,UACA,WACA,iBACA,mBACA,SACA,UAGF,mBACE,YACA,WCnCN,SACI,sBACA,kBACA,cACA,+BACA,WACA,YACA,iBACA,mBAEA,iCACE,WACA,cACA,sBACA,kBACA,kBACA,UACA,wBACA,SAGF,gBACE,WACA,WAGF,iBACE,WACA,QC5BN,eACI,cACA,kBACA,sBACA,6CACA,UACA,YACA,wBAEA,sBACE,WACA,cACA,sBACA,kBACA,UACA,WACA,yBAGF,uBACE,WACA,cACA,sBACA,kBACA,UACA,WACA,yBACA,sBACA,qBACA,UACA,QAGF,sBACE,uBACA,wBACA,WACA,QCrCN,WACI,sBACA,kBACA,cACA,+BACA,WACA,YACA,iBACA,mBACA,iBACA,gBAEA,kBACE,WACA,cACA,sBACA,kBACA,kBACA,UACA,WACA,wBACA,yBACA,SACA,UCvBN,SACI,cACA,sBACA,wBACA,sBASA,iBACA,gBACA,kBACA,+BACA,UACA,WACA,kBAbA,iCACE,cACA,sBACA,wBACA,sBAWF,iCACE,WACA,kBACA,kBACA,WACA,WACA,QACA,SACA,WAGF,gBACE,UACA,QACA,WCnCN,QACI,kBACA,cACA,+BACA,WACA,YACA,iCACA,gCACA,gBACA,sBAEA,+BACE,WACA,cACA,oBACA,kBACA,sBAGF,eACE,QACA,8CACA,WACA,YAGF,gBACE,UACA,WACA,iBACA,WACA,SC/BN,UACI,iBACA,6BACA,8BACA,WACA,WACA,gBAeA,sBACA,kBACA,gHACA,cAhBA,iBACE,iBACA,6BACA,8BACA,WACA,WACA,gBACA,WACA,cACA,sBACA,kBAQF,kBACE,WACA,cACA,sBACA,kBAGF,iBACE,WACA,wBACA,QAGF,kBACE,WACA,YACA,sBACA,wBACA,UACA,QC7CN,YACI,sBACA,kBACA,cACA,+BACA,WACA,YACA,iBACA,gBACA,0CAEA,mBACE,WACA,cACA,sBACA,kBACA,UAGF,oBACE,WACA,cACA,sBACA,kBACA,UACA,iBACA,+BACA,+BACA,UACA,YACA,WAGF,mBACE,WACA,wBACA,qBACA,SACA,QCtCN,UACI,sBACA,kBACA,cACA,+BACA,UACA,WACA,wBACA,oBACA,kCAEA,iBACE,WACA,cACA,sBACA,kBACA,kBACA,WACA,WACA,wBACA,SAGF,kBACE,WACA,cACA,sBACA,kBACA,kBACA,WACA,WACA,wBACA,SACA,MACA,yBAGF,iBACE,SACA,wBCvCN,sBACI,sBACA,kBACA,cACA,+BACA,WACA,WACA,iBACA,aACA,8BACA,+BACA,eAEA,6BACE,WACA,cACA,sBACA,kBACA,UACA,WACA,sBACA,wBACA,yBACA,SACA,WAGF,8BACE,WACA,cACA,sBACA,kBACA,kBACA,UACA,YACA,wBACA,SACA,WCrCN,SACI,sBACA,kBACA,cACA,+BACA,WACA,YACA,iBACA,4BACA,2BACA,4BACA,iBAEA,gBACE,WACA,cACA,sBACA,kBACA,WACA,YACA,kBACA,+BACA,qBACA,UACA,QCxBN,gBACI,sBACA,kBACA,cACA,+BACA,WACA,WACA,iBACA,8BACA,6BACA,4BACA,iBACA,iBAEA,+CACE,WACA,cACA,sBACA,kBAGF,uBACE,+BACA,qBACA,WACA,YACA,kBACA,UACA,SAGF,wBACE,UACA,WACA,wBACA,UACA,QCpCN,UACI,cACA,sBACA,iBAQA,2BACA,4BACA,gBACA,gBACA,kBACA,6CACA,WACA,YAbA,mCACE,cACA,sBACA,iBAYF,mCACE,WACA,kBACA,UACA,WACA,QACA,UACA,uBACA,mBAGF,iBACE","file":"cssgg.min.css"}
{"version":3,"sourceRoot":"","sources":["../../misc/scss/node_modules/css.gg/icons/scss/home.scss","../../misc/scss/node_modules/css.gg/icons/scss/options.scss","../../misc/scss/node_modules/css.gg/icons/scss/info.scss","../../misc/scss/node_modules/css.gg/icons/scss/code-slash.scss","../../misc/scss/node_modules/css.gg/icons/scss/search.scss","../../misc/scss/node_modules/css.gg/icons/scss/feed.scss","../../misc/scss/node_modules/css.gg/icons/scss/eye.scss","../../misc/scss/node_modules/css.gg/icons/scss/heart.scss","../../misc/scss/node_modules/css.gg/icons/scss/comment.scss","../../misc/scss/node_modules/css.gg/icons/scss/share.scss","../../misc/scss/node_modules/css.gg/icons/scss/software-download.scss","../../misc/scss/node_modules/css.gg/icons/scss/check-o.scss","../../misc/scss/node_modules/css.gg/icons/scss/tag.scss","../../misc/scss/node_modules/css.gg/icons/scss/lock.scss","../../misc/scss/node_modules/css.gg/icons/scss/lock-unlock.scss","../../misc/scss/node_modules/css.gg/icons/scss/music.scss"],"names":[],"mappings":"AAAA,SACE,yLACA,sBACA,kBACA,cACA,+BACA,WACA,YACA,iBACA,aACA,gBACA,4BACA,2BACA,6BACA,4BACA,mBAEA,gBACE,WACA,cACA,sBACA,kBAGF,iBACE,WACA,cACA,sBACA,kBACA,qBACA,sBACA,2BACA,wBACA,SACA,kBACA,WACA,YACA,OAGF,gBACE,UACA,YACA,iBACA,oBACA,4BACA,6BACA,gBACA,SACA,SCjDJ,YACI,sBACA,kBACA,cACA,+BACA,WACA,WACA,qCAEA,mBACE,WACA,cACA,sBACA,kBACA,UACA,WACA,iBACA,mBAGF,oBACE,WACA,cACA,sBACA,kBACA,UACA,WACA,iBACA,mBACA,SACA,UAGF,mBACE,YACA,WCnCN,SACI,sBACA,kBACA,cACA,+BACA,WACA,YACA,iBACA,mBAEA,iCACE,WACA,cACA,sBACA,kBACA,kBACA,UACA,wBACA,SAGF,gBACE,WACA,WAGF,iBACE,WACA,QC5BN,eACI,cACA,kBACA,sBACA,6CACA,UACA,YACA,wBAEA,sBACE,WACA,cACA,sBACA,kBACA,UACA,WACA,yBAGF,uBACE,WACA,cACA,sBACA,kBACA,UACA,WACA,yBACA,sBACA,qBACA,UACA,QAGF,sBACE,uBACA,wBACA,WACA,QCrCN,WACI,sBACA,kBACA,cACA,+BACA,WACA,YACA,iBACA,mBACA,iBACA,gBAEA,kBACE,WACA,cACA,sBACA,kBACA,kBACA,UACA,WACA,wBACA,yBACA,SACA,UCvBN,SACI,cACA,sBACA,wBACA,sBASA,iBACA,gBACA,kBACA,+BACA,UACA,WACA,kBAbA,iCACE,cACA,sBACA,wBACA,sBAWF,iCACE,WACA,kBACA,kBACA,WACA,WACA,QACA,SACA,WAGF,gBACE,UACA,QACA,WCnCN,QACI,kBACA,cACA,+BACA,WACA,YACA,iCACA,gCACA,gBACA,sBAEA,+BACE,WACA,cACA,oBACA,kBACA,sBAGF,eACE,QACA,8CACA,WACA,YAGF,gBACE,UACA,WACA,iBACA,WACA,SC/BN,UACI,iBACA,6BACA,8BACA,WACA,WACA,gBAeA,sBACA,kBACA,gHACA,cAhBA,iBACE,iBACA,6BACA,8BACA,WACA,WACA,gBACA,WACA,cACA,sBACA,kBAQF,kBACE,WACA,cACA,sBACA,kBAGF,iBACE,WACA,wBACA,QAGF,kBACE,WACA,YACA,sBACA,wBACA,UACA,QC7CN,YACI,sBACA,kBACA,cACA,+BACA,WACA,YACA,iBACA,gBACA,0CAEA,mBACE,WACA,cACA,sBACA,kBACA,UAGF,oBACE,WACA,cACA,sBACA,kBACA,UACA,iBACA,+BACA,+BACA,UACA,YACA,WAGF,mBACE,WACA,wBACA,qBACA,SACA,QCtCN,UACI,sBACA,kBACA,cACA,+BACA,UACA,WACA,wBACA,oBACA,kCAEA,iBACE,WACA,cACA,sBACA,kBACA,kBACA,WACA,WACA,wBACA,SAGF,kBACE,WACA,cACA,sBACA,kBACA,kBACA,WACA,WACA,wBACA,SACA,MACA,yBAGF,iBACE,SACA,wBCvCN,sBACI,sBACA,kBACA,cACA,+BACA,WACA,WACA,iBACA,aACA,8BACA,+BACA,eAEA,6BACE,WACA,cACA,sBACA,kBACA,UACA,WACA,sBACA,wBACA,yBACA,SACA,WAGF,8BACE,WACA,cACA,sBACA,kBACA,kBACA,UACA,YACA,wBACA,SACA,WCrCN,YACI,sBACA,kBACA,cACA,+BACA,WACA,YACA,iBACA,oBAEA,mBACE,WACA,cACA,sBACA,kBACA,SACA,SACA,UACA,YACA,0BACA,yBACA,mBACA,6BACA,wBCvBN,QACI,cACA,iBAeA,eACA,2BACA,8BACA,sBACA,+BACA,kBACA,WACA,YApBA,gBACE,cACA,iBACA,WACA,kBACA,UACA,WACA,uBACA,oBACA,QACA,QAYF,eACE,WACA,cACA,sBACA,kBACA,WACA,YACA,wBACA,uBACA,+BACA,yBACA,MACA,WCtCN,SACI,sBACA,kBACA,cACA,+BACA,WACA,YACA,iBACA,4BACA,2BACA,4BACA,iBAEA,gBACE,WACA,cACA,sBACA,kBACA,WACA,YACA,kBACA,+BACA,qBACA,UACA,QCxBN,gBACI,sBACA,kBACA,cACA,+BACA,WACA,WACA,iBACA,8BACA,6BACA,4BACA,iBACA,iBAEA,+CACE,WACA,cACA,sBACA,kBAGF,uBACE,+BACA,qBACA,WACA,YACA,kBACA,UACA,SAGF,wBACE,UACA,WACA,wBACA,UACA,QCpCN,UACI,cACA,sBACA,iBAQA,2BACA,4BACA,gBACA,gBACA,kBACA,6CACA,WACA,YAbA,mCACE,cACA,sBACA,iBAYF,mCACE,WACA,kBACA,UACA,WACA,QACA,UACA,uBACA,mBAGF,iBACE","file":"cssgg.min.css"}
+4
View File
@@ -0,0 +1,4 @@
<details>
<summary>{$title}</summary>
{block content}{/block}
</details>
+23 -5
View File
@@ -5,15 +5,33 @@
<link rel="apple-touch-icon" sizes="180x180" href="{path('/apple-touch-icon.png')}">
<link rel="icon" type="image/png" sizes="32x32" href="{path('/favicon-32x32.png')}">
<link rel="icon" type="image/png" sizes="16x16" href="{path('/favicon-16x16.png')}">
<link rel="manifest" href="{path('/site.webmanifest')}">
<link rel="manifest" href="{path('/manifest')}">
{if isset($og, $og_content, $og_url)}
<!-- Using TikTok's meta config -->
<meta property="og:title" content="{$og->title}" />
<meta property="og:description" content="{$og->description}" />
<meta property="og:url" content="{$og_url}" />
<meta property="og:image" content="{url_stream($og_content)}" />
<meta property="twitter:title" content="{$og->title}" />
<meta property="twitter:description" content="{$og->description}" />
<meta property="twitter:image" content="{url_stream($og_content)}" />
{else}
<!-- Using predifined ProxiTok config -->
<meta property="og:title" content="{$title}" />
<meta property="og:description" content="Alternative frontend for TikTok" />
<meta property="twitter:title" content="{$title}" />
<meta property="twitter:description" content="Alternative frontend for TikTok" />
{/if}
<meta property="og:site_name" content="ProxiTok" />
<meta property="og:title" content="{$title}" />
<meta property="og:description" content="Alternative frontend for TikTok" />
<meta property="og:type" content="website" />
<meta property="twitter:card" content="summary" />
<meta property="twitter:site" content="ProxiTok" />
{if isset($has_rss)}
<link rel="alternate" type="application/rss+xml" title="{$title}" href="{$_SERVER['REQUEST_URI'] . '/rss'}" />
{/if}
<link rel="stylesheet" href="{static('css', 'cssgg.min.css', true)}">
<link rel="stylesheet" href="{static('css', 'bulma.min.css', true)}">
<link rel="stylesheet" href="{assets('css', 'cssgg.min.css', true)}">
<link rel="stylesheet" href="{assets('css', 'bulma.min.css', true)}">
<link rel="stylesheet" href="{assets('css', 'misc.css')}">
<link rel="stylesheet" href="{assets('css', 'carousel.css')}">
<title>{$title} - ProxiTok</title>
</head>
+1 -1
View File
@@ -26,4 +26,4 @@
</div>
</div>
</nav>
<script src="{static('js', 'navbar.js')}"></script>
<script src="{assets('js', 'navbar.js')}"></script>
-14
View File
@@ -1,19 +1,5 @@
{embed '../form.latte', path: '/settings/api', method: 'POST', submit: true}
{block fields}
<div class="field">
<label class="label">Use test endpoints</label>
<div class="control">
<label class="radio">
<input type="radio" name="api-test_endpoints" value="yes" n:attr="checked => $isTestEndpoints" />
<span>Yes</span>
</label>
<label class="radio">
<input type="radio" name="api-test_endpoints" value="no" n:attr="checked => !$isTestEndpoints" />
<span>No</span>
</label>
</div>
<p class="help">This <i>may</i> help bypass rate limits</p>
</div>
<div class="field">
<label class="label">Downloader</label>
<div class="control">
+5 -5
View File
@@ -1,8 +1,8 @@
<link rel="stylesheet" href="{static('css', 'themes/card.css')}">
<noscript>JavaScript is required for this section to work!</noscript>
<link rel="stylesheet" href="{assets('css', 'themes/card.css')}">
<section class="section">
<noscript>JavaScript is required for this section to work!</noscript>
<div class="columns is-multiline is-vcentered">
{foreach $data->feed->items as $item}
{foreach $feed->items as $item}
{do $share_url = url_video_external($item->author->uniqueId, $item->id)}
<div class="column is-one-quarter clickable-img" id="{$item->id}" onclick="openVideo(this.id)"
data-video_url="{url_stream($item->video->playAddr)}"
@@ -17,7 +17,7 @@
<img class="hidden" loading="lazy" data-src="{url_stream($item->video->dynamicCover)}" />
</div>
{/foreach}
{if empty($data->feed->items)}
{if empty($feed->items)}
<p class="title">No items sent by TikTok!</p>
{/if}
</div>
@@ -67,4 +67,4 @@
</footer>
</div>
</div>
<script src="{static('js', 'themes/card.js')}"></script>
<script src="{assets('js', 'themes/card.js')}"></script>
@@ -0,0 +1,2 @@
<p class="mb-1">{include '../../icon.latte', icon: 'music', text: $songTitle}</p>
<audio controls preload="none" src="{url_stream($url)}"></audio>
@@ -0,0 +1,27 @@
{if !isset($item->imagePost)}
<video controls n:attr="autoplay => $isAutoplay" poster="{url_stream($item->video->originCover)}" preload="none">
<source src="{url_stream($item->video->playAddr)}" type="video/mp4" />
</video>
{else}
{if isset($isSimple)}
{foreach $item->imagePost->images as $i => $image}
<img src="{url_stream($image->imageURL->urlList[0])}" />
{/foreach}
{else}
<section class="carousel" aria-label="carousel" Tabindex="0">
<div class="slides">
{foreach $item->imagePost->images as $i => $image}
<div class="slides-item" id="{$item->id}-image{$i + 1}" tabindex="0">
<img src="{url_stream($image->imageURL->urlList[0])}" />
</div>
{/foreach}
</div>
<div class="carousel__nav">
{for $i = 0; $i < count($item->imagePost->images); $i++}
<a class="slider-nav" href="#{$item->id}-image{$i + 1}">{$i + 1}</a>
{/for}
</div>
<div class="carousel__skip-message" id="skip-link" tabindex="0"></div>
</section>
{/if}
{/if}
@@ -1,7 +1,7 @@
<div n:ifset="$data->info" class="buttons">
<div class="buttons">
{* is_numeric is used to avoid having a back button with ttwid cursors *}
{if isset($_GET['cursor']) && is_numeric($_GET['cursor']) && $_GET['cursor'] != 0}
<a class="button is-danger" href="?cursor=0">First</a>
{/if}
<a n:attr="disabled => !$data->feed->hasMore" class="button is-success" href="?cursor={$data->feed->maxCursor}">Next</a>
<a n:attr="disabled => !$feed->hasMore" class="button is-success" href="?cursor={$feed->cursor}">Next</a>
</div>
@@ -1,13 +1,8 @@
<div class="dropdown is-hoverable">
<div class="dropdown-trigger">
<button class="button is-success" aria-haspopup="true" aria-controls="dropdown-menu">
{include '../../icon.latte', icon: 'software-download', text: 'Download'}
</button>
</div>
<div class="dropdown-menu" role="menu">
<div class="dropdown-content">
<a target="_blank" href="{url_download($playAddr, $uniqueId, $id, true)}" class="dropdown-item">Watermark</a>
<a target="_blank" href="{url_download(url_video_external($uniqueId, $id), $uniqueId, $id, false)}" class="dropdown-item">No watermark</a>
</div>
</div>
<div class="field is-grouped is-grouped-centered">
<p class="control">
<a target="_blank" href="{url_download($downloadAddr, $uniqueId, $id, true)}" class="button is-info">Watermark</a>
</p>
<p class="control">
<a target="_blank" href="{url_download($playAddr, $uniqueId, $id, false)}" class="button is-success">No watermark</a>
</p>
</div>
+11 -16
View File
@@ -1,17 +1,12 @@
<div class="dropdown is-hoverable">
<div class="dropdown-trigger">
<button class="button is-primary" aria-haspopup="true" aria-controls="dropdown-menu">
{include '../../icon.latte', icon: 'share', text: 'Share'}
</button>
</div>
<div class="dropdown-menu" role="menu">
<div class="dropdown-content">
<a href="{url_video_internal($uniqueId, $id)}" class="dropdown-item has-text-success">
{include '../../icon.latte', icon: 'lock', text: 'Instance link'}
</a>
<a href="{url_video_external($uniqueId, $id)}" class="dropdown-item has-text-warning">
{include '../../icon.latte', icon: 'lock-unlock', text: 'Original Link'}
</a>
</div>
</div>
<div class="field is-grouped is-grouped-centered">
<p class="control">
<a class="button is-success" href="{url_video_internal($uniqueId, $id)}">
{include '../../icon.latte', icon: 'lock', text: 'Instance'}
</a>
</p>
<p class="control">
<a class="button is-warning" href="{url_video_external($uniqueId, $id)}">
{include '../../icon.latte', icon: 'lock-unlock', text: 'Original'}
</a>
</p>
</div>
@@ -1,5 +1,8 @@
<div class="tags">
{foreach $challenges as $challenge}
<a class="tag is-rounded" href="{path('/tag/' . $challenge->title)}">#{$challenge->title}</a>
{/foreach}
</div>
<details>
<summary>{include '../../icon.latte', icon: 'tag', text: 'Tags used'}</summary>
<div class="tags">
{foreach $challenges as $challenge}
<a class="tag is-primary is-rounded" href="{path('/tag/' . $challenge->title)}">#{$challenge->title}</a>
{/foreach}
</div>
</details>
+27 -10
View File
@@ -1,5 +1,5 @@
<div class="container">
{foreach $data->feed->items as $item}
{foreach $feed->items as $item}
<article class="media">
<figure class="media-left">
<p class="image is-64x64">
@@ -8,30 +8,47 @@
</figure>
<div class="media-content">
<p>
<strong>{$item->author->nickname}</strong>
<strong>
{if $item->author->verified}
{include '../icon.latte', icon: 'check-o', text: $item->author->nickname}
{else}
{$item->author->nickname}
{/if}
</strong>
<small>
<a href="{url_user($item->author->uniqueId)}">@{$item->author->uniqueId}</a>
</small>
<small title="{date('M d, Y H:i:s e', $item->createTime)}">{date('M d, Y', $item->createTime)}</small>
</p>
{if !empty($item->challenges)}
<p>{include './common/tags.latte', challenges: $item->challenges}</p>
{include './common/tags.latte', challenges: $item->challenges}
{/if}
<p n:ifcontent>{$item->desc}</p>
<p class="mb-2 mt-2" n:ifcontent>{render_desc($item->desc, $item->textExtra ?? [])|noescape}</p>
{include './common/audio.latte', songTitle: $item->music->title, url: $item->music->playUrl}
{include './common/stats.latte', playCount: $item->stats->playCount, diggCount: $item->stats->diggCount, commentCount: $item->stats->commentCount, shareCount: $item->stats->shareCount}
<div class="has-text-centered">
<video width="{$item->video->width}" height="{$item->video->height}" controls preload="none" poster="{url_stream($item->video->originCover)}">
<source src="{url_stream($item->video->playAddr)}" type="video/mp4" />
</video>
{include './common/content.latte', item: $item, isAutoplay: false, isBig: false}
</div>
{if isset($item->video->playAddr) && $item->video->playAddr !== ""}
<!-- Download links, not shown if item is a gallery -->
<p class="has-text-centered"><b>Download video</b></p>
{include './common/download.latte', id: $item->id, uniqueId: $item->author->uniqueId, playAddr: $item->video->playAddr, downloadAddr: isset($item->video->downloadAddr) ? $item->video->downloadAddr : $item->video->playAddr}
{/if}
<p class="has-text-centered"><b>Share link</b></p>
{include './common/share.latte', uniqueId: $item->author->uniqueId, id: $item->id}
<div class="has-text-centered">
{include './common/share.latte', uniqueId: $item->author->uniqueId, id: $item->id}
{include './common/download.latte', playAddr: $item->video->playAddr, id: $item->id, uniqueId: $item->author->uniqueId}
{embed '../details.latte', title: "Details"}
{block content}
<p>Can duet: {bool_to_str(isset($item->duetEnabled) ? $item->duetEnabled : boolval($item->duetDisplay))}</p>
<p>Can stitch (add this video to other ones): {bool_to_str(isset($item->stitchEnabled) ? $item->stitchEnabled : boolval($item->stitchDisplay))}</p>
<p>Can be shared: {bool_to_str(isset($item->shareEnabled) ? $item->shareEnabled : boolval($item->shareDisplay))}
{/block}
{/embed}
</div>
</div>
</article>
{/foreach}
{if empty($data->feed->items)}
{if empty($feed->items)}
<p class="title">No items sent by TikTok!</p>
{/if}
</div>
-44
View File
@@ -1,44 +0,0 @@
{layout '../layouts/default.latte'}
{block header}
<p class="title">Discover</p>
{/block}
{block content}
{foreach $data->feed->items as $type => $items}
<p class="title">{$type|firstUpper}</p>
<div class="columns is-multiline is-vcentered">
{foreach $items as $item}
<div class="column is-one-quarter">
{embed '../components/card.latte'}
{block content}
<div class="media">
<!-- Show image if exists -->
{if !empty($item->cardItem->cover)}
<div class="media-left">
<figure class="image is-96x96">
<img loading="lazy" width="96" height="96" src="{path('/stream?url=' . urlencode($item->cardItem->cover))}" />
</figure>
</div>
{/if}
<div class="media-content">
<p class="title">{$item->cardItem->title}</p>
<p class="subtitle">{$item->cardItem->subTitle}</p>
</div>
</div>
<div class="content">
<p>{$item->cardItem->description}</p>
</div>
{/block}
{block footer}
<a href="{path($item->cardItem->link)}" class="card-footer-item">Go</a>
{/block}
{/embed}
</div>
{/foreach}
</div>
{if !$iterator->last}
<hr />
{/if}
{/foreach}
{/block}
+6
View File
@@ -14,5 +14,11 @@
{else}
<p>{$msg}</p>
{/if}
{if isset($response)}
<details>
<summary>Show debug info</summary>
<pre style="max-height: 60vh;">{json_encode($response, JSON_PRETTY_PRINT)}</pre>
</details>
{/if}
</div>
{/block}
+39
View File
@@ -0,0 +1,39 @@
{layout '../layouts/default.latte'}
{block header}
<p class="title">Discover</p>
{/block}
{block content}
<div class="columns is-multiline is-vcentered">
{foreach $feed->items as $item}
<div class="column is-one-quarter">
{embed '../components/card.latte'}
{block content}
<div class="media">
<!-- Show image if exists -->
{if !empty($item->info->detail->avatarLarger)}
<div class="media-left">
<figure class="image is-96x96">
<img loading="lazy" width="96" height="96" src="{url_stream($item->info->detail->avatarLarger)}" />
</figure>
</div>
{/if}
<div class="media-content">
<p class="title">{$item->info->detail->nickname}</p>
<p class="subtitle">{$item->info->detail->uniqueId}</p>
</div>
</div>
<div class="content">
<p>{$item->info->detail->signature}</p>
</div>
{/block}
{block footer}
<a href="{url_user($item->info->detail->uniqueId)}" class="card-footer-item">Go</a>
{/block}
{/embed}
</div>
{/foreach}
</div>
{include '../components/themes/common/controls.latte', feed: $feed}
{/block}
@@ -3,7 +3,7 @@
{var $has_rss = true}
{block header}
<p class="title">Trending</p>
<p class="title">For you</p>
<p class="subtitle">{include '../components/rss.latte'}</p>
{/block}
+7 -7
View File
@@ -3,7 +3,7 @@
{block content}
<p class="title has-text-centered">Welcome to ProxiTok!</p>
<p class="subtitle has-text-centered">An alternative open source frontend for TikTok</p>
{embed '../components/form.latte', path: '/redirect', method: 'GET'}
{embed '../components/form.latte', path: '/redirect/search', method: 'GET'}
{block fields}
<div class="field has-addons has-addons-centered">
<div class="control">
@@ -28,14 +28,14 @@
</div>
{/block}
{/embed}
<div class="columns is-centered is-mobile">
<div class="columns is-centered is-mobile mt-2">
<div class="column is-narrow">
<p>Discover</p>
<a class="button is-success" href="{path('/discover')}">Go</a>
<p>For you</p>
<a class="button is-success" href="{path('/foryou')}">Go</a>
</div>
<div class="column is-narrow">
<p>Trending</p>
<a class="button is-success" href="{path('/trending')}">Go</a>
<div class="column is-narrow">
<p>Following</p>
<a class="button is-success" href="{path('/following')}">Go</a>
</div>
</div>
{/block}
+3 -3
View File
@@ -1,9 +1,9 @@
{layout '../layouts/default.latte'}
{block header}
<p class="title">{$data->info->detail->title}</p>
<p class="subtitle">{$data->info->detail->desc}</p>
<p>Videos: {number($data->info->stats->videoCount)}</p>
<p class="title">{$info->detail->title}</p>
<p class="subtitle">{$info->detail->desc}</p>
<p>Videos: {number($info->stats->videoCount)}</p>
{/block}
{block content}
+10 -3
View File
@@ -1,18 +1,25 @@
{contentType application/rss+xml}
<?xml version="1.0" encoding="utf-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<title>{$title}</title>
<description><![CDATA[{$desc}]]></description>
{if $image}
<image>
<link>{$link}</link>
<title>{$title}</title>
<url>{$image}</url>
</image>
{/if}
<link>{$link}</link>
<atom:link href="{$link . '/rss'}" rel="self" type="application/rss+xml"></atom:link>
{foreach $items as $item}
<item>
<title>{$item->desc}</title>
<description><![CDATA[<p>{$item->desc}</p><video controls="controls" preload="auto" src="{url_stream($item->video->playAddr)}"></video>]]></description>
<description><![CDATA[<p n:ifcontent>{render_desc($item->desc, $item->textExtra ?? [])|noescape}</p>{include '../components/themes/common/content.latte', item: $item, isAutoplay: false, isSimple: true}]]></description>
<link>{url_video_internal($item->author->uniqueId, $item->id)}</link>
<pubDate>{date('r', $item->createTime)}</pubDate>
<guid isPermaLink="false">{$item->id}</guid>
<dc:creator>@{$item->author->uniqueId}</dc:creator>
</item>
{/foreach}
</channel>
+13 -4
View File
@@ -2,15 +2,24 @@
{var $has_rss = true}
{var $og = $info->meta->og}
{var $og_content = $info->detail->profileLarger}
{var $og_url = url_tag($info->detail->title)}
{block header}
{if $data->info->detail->profileLarger !== ''}
{if $info->detail->profileLarger !== ''}
<figure class="image is-inline-block is-128x128">
<img class="is-rounded" src="{url_stream($data->info->detail->profileLarger)}" />
<img class="is-rounded" src="{url_stream($info->detail->profileLarger)}" />
</figure>
{/if}
<p class="title">{$data->info->detail->title}</p>
<p class="title">{$info->detail->title}</p>
<p class="subtitle">{include '../components/rss.latte'}</p>
<p>Videos: {number($data->info->stats->videoCount)} / Views: {number($data->info->stats->viewCount)}</p>
<p>Views: {number($info->stats->viewCount)}</p>
{embed '../components/details.latte', title: 'Details'}
{block content}
<p>Is commerce: {bool_to_str($info->detail->isCommerce)}</p>
{/block}
{/embed}
{/block}
{block content}
+33 -5
View File
@@ -2,15 +2,43 @@
{var $has_rss = true}
{var $og = $info->meta->og}
{var $og_content = $info->detail->avatarLarger}
{var $og_url = url_user($info->detail->uniqueId)}
{block header}
<figure class="image is-inline-block is-128x128">
<img class="is-rounded" src="{url_stream($data->info->detail->avatarLarger)}" />
<img class="is-rounded" src="{url_stream($info->detail->avatarLarger)}" />
</figure>
<p class="title">{$data->info->detail->uniqueId}</p>
<p class="title">
{if $info->detail->verified}
{include '../components/icon.latte', icon: 'check-o', text: $info->detail->nickname}
{else}
{$info->detail->nickname}
{/if}
</p>
<p class="subtitle">{include '../components/rss.latte'}</p>
<p>{$data->info->detail->signature}</p>
<p>Following: {number($data->info->stats->followingCount)} / Followers: {number($data->info->stats->followerCount)}</p>
<p>Hearts: {number($data->info->stats->heartCount)} / Videos: {$data->info->stats->videoCount}</p>
{if $info->detail->privateAccount}
<p><strong>Private account</strong></p>
{/if}
<b>Description</b>
<p class="new-line">{$info->detail->signature}</p>
{if isset($info->detail->bioLink)}
<p>Link: <a href="{$info->detail->bioLink->link}">{$info->detail->bioLink->link}</a></p>
{/if}
<b>Stats</b>
<p>Following: {number($info->stats->followingCount)} / Followers: {number($info->stats->followerCount)}</p>
<p>Hearts: {number($info->stats->heartCount)} / Videos: {$info->stats->videoCount}</p>
{embed '../components/details.latte', title: "Details"}
{block content}
{if isset($info->detail->region)}
<p>Region: {$info->detail->region}</p>
{/if}
{if $info->detail->commerceUserInfo->commerceUser}
<p>Category: {$info->detail->commerceUserInfo->category}</p>
{/if}
{/block}
{/embed}
{/block}
{block content}
+49 -11
View File
@@ -1,42 +1,80 @@
{layout "../layouts/{$layout}.latte"}
{var $og = $info->meta->og}
{var $og_content = $item->video->originCover}
{var $og_url = url_video_internal($info->detail->uniqueId, $item->id)}
{if !empty($item->desc)}
{var $title = trim($item->desc)}
{/if}
{block content}
<div class="columns is-centered is-vcentered is-gapless">
<div class="columns is-centered is-vcentered">
<div class="column has-text-centered">
<video controls autoplay poster="{url_stream($item->video->originCover)}">
<source src="{url_stream($item->video->playAddr)}" type="video/mp4" />
</video>
{include '../components/themes/common/content.latte', item: $item, isAutoplay: true, isBig: true}
</div>
<div class="column">
<div class="box">
<article class="media">
<figure class="media-left">
<p class="image is-64x64">
<img src="{url_stream($detail->avatarThumb)}" />
<img src="{url_stream($info->detail->avatarThumb)}" />
</p>
</figure>
<div class="media-content">
<p>
<strong>{$detail->nickname}</strong>
<strong>{$info->detail->nickname}</strong>
<small>
<a href="{url_user($detail->uniqueId)}">@{$detail->uniqueId}</a>
<a href="{url_user($info->detail->uniqueId)}">@{$info->detail->uniqueId}</a>
</small>
<small title="{date('M d, Y H:i:s e', $item->createTime)}">{date('M d, Y', $item->createTime)}</small>
</p>
{if !empty($item->challenges)}
<p>{include '../components/themes/common/tags.latte', challenges: $item->challenges}</p>
{include '../components/themes/common/tags.latte', challenges: $item->challenges}
{/if}
<div class="content">
<p n:ifcontent>{$item->desc}</p>
<p n:ifcontent>{render_desc($item->desc, $item->textExtra ?? [])|noescape}</p>
{include '../components/themes/common/audio.latte', songTitle: $item->music->title, url: $item->music->playUrl}
{include '../components/themes/common/stats.latte', playCount: $item->stats->playCount, diggCount: $item->stats->diggCount, commentCount: $item->stats->commentCount, shareCount: $item->stats->shareCount}
<div class="has-text-centered">
{include '../components/themes/common/share.latte', uniqueId: $detail->uniqueId, id: $item->id}
{include '../components/themes/common/download.latte', playAddr: $item->video->playAddr, id: $item->id, uniqueId: $detail->uniqueId}
{include '../components/themes/common/share.latte', uniqueId: $info->detail->uniqueId, id: $item->id}
{if isset($item->video->playAddr) && $item->video->playAddr !== ""}
<!-- Download links, not shown if item is a gallery -->
{include '../components/themes/common/download.latte', playAddr: $item->video->playAddr, id: $item->id, uniqueId: $info->detail->uniqueId}
{/if}
</div>
</div>
</div>
</article>
</div>
{if !empty($item->comments)}
<div class="box">
<p class="is-size-5 has-text-centered">Comments</p>
{foreach $item->comments as $comment}
<article class="media">
<figure class="media-left">
<p class="image is-64x64">
<img src="{url_stream($comment->user->avatar_thumb->url_list[0])}" />
</p>
</figure>
<div class="media-content">
<div class="content">
<p>
<strong>{$comment->user->unique_id}</strong>
<small>
<a href="{url_user($comment->user->unique_id)}">@{$comment->user->nickname}</a>
</small>
<small title="{date('M d, Y H:i:s e', $comment->create_time)}">{date('M d, Y', $comment->create_time)}</small>
<br>
{$comment->text}
</p>
</div>
<p>{include '../components/icon.latte', icon: 'heart', text: number($comment->digg_count)}</p>
</div>
</article>
{/foreach}
</div>
{/if}
</div>
</div>
{/block}