#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# @Author: https://github.com/Evil0ctal/
# @Time: 2021/11/06
# @Update: 2022/12/25
# @Version: 3.0.1
# @Function:
# 用于在线批量解析Douyin/TikTok的无水印视频/图集。
# 基于 PyWebIO,将scraper.py返回的内容显示在网页上。
import configparser
import os
import re
import time
from scraper import Scraper
from pywebio import *
from pywebio import config as pywebio_config
from pywebio.input import *
from pywebio.output import *
from pywebio.session import info as session_info, run_asyncio_coroutine
config = configparser.ConfigParser()
config.read('config.ini', encoding='utf-8')
# 创建一个Scraper类的实例/Create an instance of the Scraper class
api = Scraper()
# 自动检测语言返回翻译/Auto detect language to return translation
def t(zh: str, en: str) -> str:
return zh if 'zh' in session_info.user_language else en
# 解析抖音分享口令中的链接并返回列表/Parse the link in the Douyin share command and return a list
def find_url(string: str) -> list:
url = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', string)
return url
# 校验输入值/Validate input value
def valid_check(input_data: str) -> str or None:
# 检索出所有链接并返回列表/Retrieve all links and return a list
url_list = find_url(input_data)
# 总共找到的链接数量/Total number of links found
total_urls = len(url_list)
if total_urls == 0:
return t('没有检测到有效的链接,请检查输入的内容是否正确。',
'No valid link detected, please check if the input content is correct.')
else:
# 最大接受提交URL的数量/Maximum number of URLs accepted
max_urls = config['Web_APP']['Max_Take_URLs']
if total_urls > int(max_urls):
warn_info = t('URL数量过多,只会处理前{}个URL。'.format(max_urls),
'Too many URLs, only the first {} URLs will be processed.'.format(max_urls))
return warn_info
# 错误处理/Error handling
def error_do(reason: str, value: str) -> None:
# 输出一个毫无用处的信息
put_html("
")
put_error(
t("发生了了意料之外的错误,输入值已被记录。", "An unexpected error occurred, the input value has been recorded."))
put_html('
⚠{}
'.format(t('详情', 'Details')))
put_table([
[t('原因', 'reason'), t('输入值', 'input value')],
[reason, value]])
put_markdown(t('可能的原因:', 'Possible reasons:'))
put_markdown(t('服务器可能被目标主机的防火墙限流(稍等片刻后再次尝试)',
'The server may be limited by the target host firewall (try again after a while)'))
put_markdown(t('输入了错误的链接(API-V1暂不支持主页链接解析)',
'Entered the wrong link (the home page link is not supported for parsing with API-V1)'))
put_markdown(
t('如果需要解析个人主页,请使用TikHub_API', 'If you need to parse the personal homepage, please use TikHub_API'))
put_markdown(t('TikHub_API 文档: [https://api.tikhub.io/docs](https://api.tikhub.io/docs)',
'TikHub_API Documentation: [https://api.tikhub.io/docs](https://api.tikhub.io/docs)'))
put_markdown(t('该视频已经被删除或屏蔽(你看的都是些啥(⊙_⊙)?)',
'The video has been deleted or blocked (what are you watching (⊙_⊙)?)'))
put_markdown(t('其他原因(请联系作者)', 'Other reasons (please contact the author)'))
put_markdown(t('你可以在右上角的关于菜单中查看本站错误日志。',
'You can view the error log of this site in the about menu in the upper right corner.'))
put_markdown('[{}](https://github.com/Evil0ctal/Douyin_TikTok_Download_API/issues)'.format(
t('点击此处在GitHub上进行反馈', 'Click here to give feedback on GitHub')))
put_html("")
if config['Web_APP']['Allow_Logs'] == 'True':
# 如果douyin或tiktok在输入值中,则记录到日志文件/If douyin or tiktok is in the input value, record it to the log file
if 'douyin' in value or 'tiktok' in value:
# 将错误记录在logs.txt中
error_date = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
print(f"{error_date}: 正在记录错误信息...")
with open('logs.txt', 'a') as f:
f.write(error_date + ":\n" + str(reason) + '\n' + "Input value: " + value + '\n')
else:
print(t('输入值中没有douyin或tiktok,不记录到日志文件中',
'No douyin or tiktok in the input value, not recorded to the log file'))
# iOS快捷指令弹窗/IOS shortcut pop-up
def ios_pop_window():
with popup(t("iOS快捷指令", "iOS Shortcut")):
version = config["Web_API"]["iOS_Shortcut_Version"]
update = config["Web_API"]['iOS_Shortcut_Update_Time']
link = config["Web_API"]['iOS_Shortcut_Link']
link_en = config["Web_API"]['iOS_Shortcut_Link_EN']
note = config["Web_API"]['iOS_Shortcut_Update_Note']
note_en = config["Web_API"]['iOS_Shortcut_Update_Note_EN']
put_markdown(t('#### 📢 快捷指令介绍:', '#### 📢 Shortcut Introduction:'))
put_markdown(
t('快捷指令运行在iOS平台,本快捷指令可以快速调用本项目的公共API将抖音或TikTok的视频或图集下载到你的手机相册中,暂时只支持单个链接进行下载。',
'The shortcut runs on the iOS platform, and this shortcut can quickly call the public API of this project to download the video or album of Douyin or TikTok to your phone album. It only supports single link download for now.'))
put_markdown(t('#### 📲 使用方法 ①:', '#### 📲 Operation method ①:'))
put_markdown(t('在抖音或TikTok的APP内,浏览你想要无水印保存的视频或图集。',
'The shortcut needs to be used in the Douyin or TikTok app, browse the video or album you want to save without watermark.'))
put_markdown(t('然后点击右下角分享按钮,选择更多,然后下拉找到 "抖音TikTok无水印下载" 这个选项。',
'Then click the share button in the lower right corner, select more, and then scroll down to find the "Douyin TikTok No Watermark Download" option.'))
put_markdown(t('如遇到通知询问是否允许快捷指令访问xxxx (域名或服务器),需要点击允许才可以正常使用。',
'If you are asked whether to allow the shortcut to access xxxx (domain name or server), you need to click Allow to use it normally.'))
put_markdown(t('该快捷指令会在你相册创建一个新的相薄方便你浏览保存的内容。',
'The shortcut will create a new album in your photo album to help you browse the saved content.'))
put_markdown(t('#### 📲 使用方法 ②:', '#### 📲 Operation method ②:'))
put_markdown(t('在抖音或TikTok的视频下方点击分享,然后点击复制链接,然后去快捷指令APP中运行该快捷指令。',
'Click share below the video of Douyin or TikTok, then click to copy the link, then go to the shortcut command APP to run the shortcut command.'))
put_markdown(t('如果弹窗询问是否允许读取剪切板请同意,随后快捷指令将链接内容保存至相册中。',
'if the pop-up window asks whether to allow reading the clipboard, please agree, and then the shortcut command will save the link content to the album middle.'))
put_html('')
put_text(t(f"最新快捷指令版本: {version}", f"Latest shortcut version: {version}"))
put_text(t(f"快捷指令更新时间: {update}", f"Shortcut update time: {update}"))
put_text(t(f"快捷指令更新内容: {note}", f"Shortcut update content: {note_en}"))
put_link("[点击获取快捷指令 - 中文]", link, new_window=True)
put_html(" ")
put_link("[Click get Shortcut - English]", link_en, new_window=True)
# API文档弹窗/API documentation pop-up
def api_document_pop_window():
with popup(t("API文档", "API Document")):
put_markdown(t("💾TikHub_API文档", "💾TikHub_API Document"))
put_markdown(t('TikHub_API 支持抖音和TikTok的更多接口, 如主页解析,视频解析,视频评论解析,个人点赞列表解析等...',
'TikHub_API supports more interfaces of Douyin and TikTok, such as home page parsing, video parsing, video comment parsing, personal like list parsing, etc...'))
put_link('[TikHub_API Docs]', 'https://api.tikhub.io/docs', new_window=True)
put_html('')
put_markdown(t("💽API-V1文档", "💽API-V1 Document"))
put_markdown(t("API-V1 支持抖音和TikTok的单一视频解析,具体请查看接口文档。",
"API-V1 supports single video parsing of Douyin and TikTok. For details, please refer to the API documentation."))
put_link('[API-V1 Docs]', 'https://api.douyin.wtf/docs', new_window=True)
# 日志文件弹窗/Log file pop-up
def log_popup_window():
with popup(t('错误日志', 'Error Log')):
put_html('
⚠️{}
'.format('关于解析失败可能的原因', 'About the possible reasons for parsing failure'))
put_markdown(t('服务器可能被目标主机的防火墙限流(稍等片刻后再次尝试)',
'The server may be limited by the target host firewall (try again after a while)'))
put_markdown(t('输入了错误的链接(API-V1暂不支持主页链接解析)',
'Entered the wrong link (the home page link is not supported for parsing with API-V1)'))
put_markdown(
t('如果需要解析个人主页,请使用TikHub_API', 'If you need to parse the personal homepage, please use TikHub_API'))
put_markdown(t('TikHub_API 文档: [https://api.tikhub.io/docs](https://api.tikhub.io/docs)',
'TikHub_API Documentation: [https://api.tikhub.io/docs](https://api.tikhub.io/docs)'))
put_markdown(t('该视频已经被删除或屏蔽(你看的都是些啥(⊙_⊙)?)',
'The video has been deleted or blocked (what are you watching (⊙_⊙)?)'))
put_markdown(t('[点击此处在GitHub上进行反馈](https://github.com/Evil0ctal/Douyin_TikTok_Download_API/issues)',
'[Click here to feedback on GitHub](https://github.com/Evil0ctal/Douyin_TikTok_Download_API/issues)'))
put_html('')
# 判断日志文件是否存在
if os.path.exists('logs.txt'):
put_text(t('点击logs.txt可下载日志:', 'Click logs.txt to download the log:'))
content = open(r'./logs.txt', 'rb').read()
put_file('logs.txt', content=content)
with open('./logs.txt', 'r') as f:
content = f.read()
put_text(str(content))
else:
put_text(t('日志文件不存在,请等发生错误时再回来看看。',
'The log file does not exist, please come back and take a look when an error occurs.'))
# 关于弹窗/About pop-up
def about_popup_window():
with popup(t('更多信息', 'More Information')):
put_html('