fix: 将 sau_backend 目录的文件,再挪回根目录,以防止大家迁移出现问题
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from conf import BASE_DIR
|
||||
|
||||
SOCIAL_MEDIA_DOUYIN = "douyin"
|
||||
SOCIAL_MEDIA_TENCENT = "tencent"
|
||||
SOCIAL_MEDIA_TIKTOK = "tiktok"
|
||||
SOCIAL_MEDIA_BILIBILI = "bilibili"
|
||||
SOCIAL_MEDIA_KUAISHOU = "kuaishou"
|
||||
|
||||
|
||||
def get_supported_social_media() -> List[str]:
|
||||
return [SOCIAL_MEDIA_DOUYIN, SOCIAL_MEDIA_TENCENT, SOCIAL_MEDIA_TIKTOK, SOCIAL_MEDIA_KUAISHOU]
|
||||
|
||||
|
||||
def get_cli_action() -> List[str]:
|
||||
return ["upload", "login", "watch"]
|
||||
|
||||
|
||||
async def set_init_script(context):
|
||||
stealth_js_path = Path(BASE_DIR / "utils/stealth.min.js")
|
||||
await context.add_init_script(path=stealth_js_path)
|
||||
return context
|
||||
@@ -0,0 +1,309 @@
|
||||
import enum
|
||||
|
||||
|
||||
class TencentZoneTypes(enum.Enum):
|
||||
LIFESTYLE = '生活'
|
||||
CUTE_KIDS = '萌娃'
|
||||
MUSIC = '音乐'
|
||||
KNOWLEDGE = '知识'
|
||||
EMOTION = '情感'
|
||||
TRAVEL_SCENERY = '旅行风景'
|
||||
FASHION = '时尚'
|
||||
FOOD = '美食'
|
||||
LIFE_HACKS = '生活技巧'
|
||||
DANCE = '舞蹈'
|
||||
MOVIES_TV_SHOWS = '影视综艺'
|
||||
SPORTS = '运动'
|
||||
FUNNY = '搞笑'
|
||||
CELEBRITIES = '明星名人'
|
||||
NEWS_INFO = '新闻资讯'
|
||||
GAMING = '游戏'
|
||||
AUTOMOTIVE = '车'
|
||||
ANIME = '二次元'
|
||||
TALENT = '才艺'
|
||||
CUTE_PETS = '萌宠'
|
||||
INDUSTRY_MACHINERY_CONSTRUCTION = '机械'
|
||||
ANIMALS = '动物'
|
||||
PARENTING = '育儿'
|
||||
TECHNOLOGY = '科技'
|
||||
|
||||
class VideoZoneTypes(enum.Enum):
|
||||
"""
|
||||
所有分区枚举
|
||||
|
||||
- MAINPAGE: 主页
|
||||
- ANIME: 番剧
|
||||
- ANIME_SERIAL: 连载中番剧
|
||||
- ANIME_FINISH: 已完结番剧
|
||||
- ANIME_INFORMATION: 资讯
|
||||
- ANIME_OFFICAL: 官方延伸
|
||||
- MOVIE: 电影
|
||||
- GUOCHUANG: 国创
|
||||
- GUOCHUANG_CHINESE: 国产动画
|
||||
- GUOCHUANG_ORIGINAL: 国产原创相关
|
||||
- GUOCHUANG_PUPPETRY: 布袋戏
|
||||
- GUOCHUANG_MOTIONCOMIC: 动态漫·广播剧
|
||||
- GUOCHUANG_INFORMATION: 资讯
|
||||
- TELEPLAY: 电视剧
|
||||
- DOCUMENTARY: 纪录片
|
||||
- DOUGA: 动画
|
||||
- DOUGA_MAD: MAD·AMV
|
||||
- DOUGA_MMD: MMD·3D
|
||||
- DOUGA_VOICE: 短片·手书·配音
|
||||
- DOUGA_GARAGE_KIT: 手办·模玩
|
||||
- DOUGA_TOKUSATSU: 特摄
|
||||
- DOUGA_ACGNTALKS: 动漫杂谈
|
||||
- DOUGA_OTHER: 综合
|
||||
- GAME: 游戏
|
||||
- GAME_STAND_ALONE: 单机游戏
|
||||
- GAME_ESPORTS: 电子竞技
|
||||
- GAME_MOBILE: 手机游戏
|
||||
- GAME_ONLINE: 网络游戏
|
||||
- GAME_BOARD: 桌游棋牌
|
||||
- GAME_GMV: GMV
|
||||
- GAME_MUSIC: 音游
|
||||
- GAME_MUGEN: Mugen
|
||||
- KICHIKU: 鬼畜
|
||||
- KICHIKU_GUIDE: 鬼畜调教
|
||||
- KICHIKU_MAD: 音MAD
|
||||
- KICHIKU_MANUAL_VOCALOID: 人力VOCALOID
|
||||
- KICHIKU_THEATRE: 鬼畜剧场
|
||||
- KICHIKU_COURSE: 教程演示
|
||||
- MUSIC: 音乐
|
||||
- MUSIC_ORIGINAL: 原创音乐
|
||||
- MUSIC_COVER: 翻唱
|
||||
- MUSIC_PERFORM: 演奏
|
||||
- MUSIC_VOCALOID: VOCALOID·UTAU
|
||||
- MUSIC_LIVE: 音乐现场
|
||||
- MUSIC_MV: MV
|
||||
- MUSIC_COMMENTARY: 乐评盘点
|
||||
- MUSIC_TUTORIAL: 音乐教学
|
||||
- MUSIC_OTHER: 音乐综合
|
||||
- DANCE: 舞蹈
|
||||
- DANCE_OTAKU: 宅舞
|
||||
- DANCE_HIPHOP: 街舞
|
||||
- DANCE_STAR: 明星舞蹈
|
||||
- DANCE_CHINA: 中国舞
|
||||
- DANCE_THREE_D: 舞蹈综合
|
||||
- DANCE_DEMO: 舞蹈教程
|
||||
- CINEPHILE: 影视
|
||||
- CINEPHILE_CINECISM: 影视杂谈
|
||||
- CINEPHILE_MONTAGE: 影视剪辑
|
||||
- CINEPHILE_SHORTFILM: 小剧场
|
||||
- CINEPHILE_TRAILER_INFO: 预告·资讯
|
||||
- ENT: 娱乐
|
||||
- ENT_VARIETY: 综艺
|
||||
- ENT_TALKER: 娱乐杂谈
|
||||
- ENT_FANS: 粉丝创作
|
||||
- ENT_CELEBRITY: 明星综合
|
||||
- KNOWLEDGE: 知识
|
||||
- KNOWLEDGE_SCIENCE: 科学科普
|
||||
- KNOWLEDGE_SOCIAL_SCIENCE: 社科·法律·心理
|
||||
- KNOWLEDGE_HUMANITY_HISTORY: 人文历史
|
||||
- KNOWLEDGE_BUSINESS: 财经商业
|
||||
- KNOWLEDGE_CAMPUS: 校园学习
|
||||
- KNOWLEDGE_CAREER: 职业职场
|
||||
- KNOWLEDGE_DESIGN: 设计·创意
|
||||
- KNOWLEDGE_SKILL: 野生技能协会
|
||||
- TECH: 科技
|
||||
- TECH_DIGITAL: 数码
|
||||
- TECH_APPLICATION: 软件应用
|
||||
- TECH_COMPUTER_TECH: 计算机技术
|
||||
- TECH_INDUSTRY: 科工机械
|
||||
- INFORMATION: 资讯
|
||||
- INFORMATION_HOTSPOT: 热点
|
||||
- INFORMATION_GLOBAL: 环球
|
||||
- INFORMATION_SOCIAL: 社会
|
||||
- INFORMATION_MULTIPLE: 综合
|
||||
- FOOD: 美食
|
||||
- FOOD_MAKE: 美食制作
|
||||
- FOOD_DETECTIVE: 美食侦探
|
||||
- FOOD_MEASUREMENT: 美食测评
|
||||
- FOOD_RURAL: 田园美食
|
||||
- FOOD_RECORD: 美食记录
|
||||
- LIFE: 生活
|
||||
- LIFE_FUNNY: 搞笑
|
||||
- LIFE_TRAVEL: 出行
|
||||
- LIFE_RURALLIFE: 三农
|
||||
- LIFE_HOME: 家居房产
|
||||
- LIFE_HANDMAKE: 手工
|
||||
- LIFE_PAINTING: 绘画
|
||||
- LIFE_DAILY: 日常
|
||||
- CAR: 汽车
|
||||
- CAR_RACING: 赛车
|
||||
- CAR_MODIFIEDVEHICLE: 改装玩车
|
||||
- CAR_NEWENERGYVEHICLE: 新能源车
|
||||
- CAR_TOURINGCAR: 房车
|
||||
- CAR_MOTORCYCLE: 摩托车
|
||||
- CAR_STRATEGY: 购车攻略
|
||||
- CAR_LIFE: 汽车生活
|
||||
- FASHION: 时尚
|
||||
- FASHION_MAKEUP: 美妆护肤
|
||||
- FASHION_COS: 仿妆cos
|
||||
- FASHION_CLOTHING: 穿搭
|
||||
- FASHION_TREND: 时尚潮流
|
||||
- SPORTS: 运动
|
||||
- SPORTS_BASKETBALL: 篮球
|
||||
- SPORTS_FOOTBALL: 足球
|
||||
- SPORTS_AEROBICS: 健身
|
||||
- SPORTS_ATHLETIC: 竞技体育
|
||||
- SPORTS_CULTURE: 运动文化
|
||||
- SPORTS_COMPREHENSIVE: 运动综合
|
||||
- ANIMAL: 动物圈
|
||||
- ANIMAL_CAT: 喵星人
|
||||
- ANIMAL_DOG: 汪星人
|
||||
- ANIMAL_PANDA: 大熊猫
|
||||
- ANIMAL_WILD_ANIMAL: 野生动物
|
||||
- ANIMAL_REPTILES: 爬宠
|
||||
- ANIMAL_COMPOSITE: 动物综合
|
||||
- VLOG: VLOG
|
||||
"""
|
||||
|
||||
MAINPAGE = 0
|
||||
|
||||
ANIME = 13
|
||||
ANIME_SERIAL = 33
|
||||
ANIME_FINISH = 32
|
||||
ANIME_INFORMATION = 51
|
||||
ANIME_OFFICAL = 152
|
||||
|
||||
MOVIE = 23
|
||||
|
||||
GUOCHUANG = 167
|
||||
GUOCHUANG_CHINESE = 153
|
||||
GUOCHUANG_ORIGINAL = 168
|
||||
GUOCHUANG_PUPPETRY = 169
|
||||
GUOCHUANG_MOTIONCOMIC = 195
|
||||
GUOCHUANG_INFORMATION = 170
|
||||
|
||||
TELEPLAY = 11
|
||||
|
||||
DOCUMENTARY = 177
|
||||
|
||||
DOUGA = 1
|
||||
DOUGA_MAD = 24
|
||||
DOUGA_MMD = 25
|
||||
DOUGA_VOICE = 47
|
||||
DOUGA_GARAGE_KIT = 210
|
||||
DOUGA_TOKUSATSU = 86
|
||||
DOUGA_ACGNTALKS = 253
|
||||
DOUGA_OTHER = 27
|
||||
|
||||
GAME = 4
|
||||
GAME_STAND_ALONE = 17
|
||||
GAME_ESPORTS = 171
|
||||
GAME_MOBILE = 172
|
||||
GAME_ONLINE = 65
|
||||
GAME_BOARD = 173
|
||||
GAME_GMV = 121
|
||||
GAME_MUSIC = 136
|
||||
GAME_MUGEN = 19
|
||||
|
||||
KICHIKU = 119
|
||||
KICHIKU_GUIDE = 22
|
||||
KICHIKU_MAD = 26
|
||||
KICHIKU_MANUAL_VOCALOID = 126
|
||||
KICHIKU_THEATRE = 216
|
||||
KICHIKU_COURSE = 127
|
||||
|
||||
MUSIC = 3
|
||||
MUSIC_ORIGINAL = 28
|
||||
MUSIC_COVER = 31
|
||||
MUSIC_PERFORM = 59
|
||||
MUSIC_VOCALOID = 30
|
||||
MUSIC_LIVE = 29
|
||||
MUSIC_MV = 193
|
||||
MUSIC_COMMENTARY = 243
|
||||
MUSIC_TUTORIAL = 244
|
||||
MUSIC_OTHER = 130
|
||||
|
||||
DANCE = 129
|
||||
DANCE_OTAKU = 20
|
||||
DANCE_HIPHOP = 198
|
||||
DANCE_STAR = 199
|
||||
DANCE_CHINA = 200
|
||||
DANCE_THREE_D = 154
|
||||
DANCE_DEMO = 156
|
||||
|
||||
CINEPHILE = 181
|
||||
CINEPHILE_CINECISM = 182
|
||||
CINEPHILE_MONTAGE = 183
|
||||
CINEPHILE_SHORTFILM = 85
|
||||
CINEPHILE_TRAILER_INFO = 184
|
||||
|
||||
ENT = 5
|
||||
ENT_VARIETY = 71
|
||||
ENT_TALKER = 241
|
||||
ENT_FANS = 242
|
||||
ENT_CELEBRITY = 137
|
||||
|
||||
KNOWLEDGE = 36
|
||||
KNOWLEDGE_SCIENCE = 201
|
||||
KNOWLEDGE_SOCIAL_SCIENCE = 124
|
||||
KNOWLEDGE_HUMANITY_HISTORY = 228
|
||||
KNOWLEDGE_BUSINESS = 207
|
||||
KNOWLEDGE_CAMPUS = 208
|
||||
KNOWLEDGE_CAREER = 209
|
||||
KNOWLEDGE_DESIGN = 229
|
||||
KNOWLEDGE_SKILL = 122
|
||||
|
||||
TECH = 188
|
||||
TECH_DIGITAL = 95
|
||||
TECH_APPLICATION = 230
|
||||
TECH_COMPUTER_TECH = 231
|
||||
TECH_INDUSTRY = 232
|
||||
|
||||
INFORMATION = 202
|
||||
INFORMATION_HOTSPOT = 203
|
||||
INFORMATION_GLOBAL = 204
|
||||
INFORMATION_SOCIAL = 205
|
||||
INFORMATION_MULTIPLE = 206
|
||||
|
||||
FOOD = 211
|
||||
FOOD_MAKE = 76
|
||||
FOOD_DETECTIVE = 212
|
||||
FOOD_MEASUREMENT = 213
|
||||
FOOD_RURAL = 214
|
||||
FOOD_RECORD = 215
|
||||
|
||||
LIFE = 160
|
||||
LIFE_FUNNY = 138
|
||||
LIFE_TRAVEL = 250
|
||||
LIFE_RURALLIFE = 251
|
||||
LIFE_HOME = 239
|
||||
LIFE_HANDMAKE = 161
|
||||
LIFE_PAINTING = 162
|
||||
LIFE_DAILY = 21
|
||||
|
||||
CAR = 223
|
||||
CAR_RACING = 245
|
||||
CAR_MODIFIEDVEHICLE = 246
|
||||
CAR_NEWENERGYVEHICLE = 247
|
||||
CAR_TOURINGCAR = 248
|
||||
CAR_MOTORCYCLE = 240
|
||||
CAR_STRATEGY = 227
|
||||
CAR_LIFE = 176
|
||||
|
||||
FASHION = 155
|
||||
FASHION_MAKEUP = 157
|
||||
FASHION_COS = 252
|
||||
FASHION_CLOTHING = 158
|
||||
FASHION_TREND = 159
|
||||
|
||||
SPORTS = 234
|
||||
SPORTS_BASKETBALL = 235
|
||||
SPORTS_FOOTBALL = 249
|
||||
SPORTS_AEROBICS = 164
|
||||
SPORTS_ATHLETIC = 236
|
||||
SPORTS_CULTURE = 237
|
||||
SPORTS_COMPREHENSIVE = 238
|
||||
|
||||
ANIMAL = 217
|
||||
ANIMAL_CAT = 218
|
||||
ANIMAL_DOG = 219
|
||||
ANIMAL_PANDA = 220
|
||||
ANIMAL_WILD_ANIMAL = 221
|
||||
ANIMAL_REPTILES = 222
|
||||
ANIMAL_COMPOSITE = 75
|
||||
|
||||
VLOG = 19
|
||||
@@ -0,0 +1,83 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from conf import BASE_DIR
|
||||
|
||||
|
||||
def get_absolute_path(relative_path: str, base_dir: str = None) -> str:
|
||||
# Convert the relative path to an absolute path
|
||||
absolute_path = Path(BASE_DIR) / base_dir / relative_path
|
||||
return str(absolute_path)
|
||||
|
||||
|
||||
def get_title_and_hashtags(filename):
|
||||
"""
|
||||
获取视频标题和 hashtag
|
||||
|
||||
Args:
|
||||
filename: 视频文件名
|
||||
|
||||
Returns:
|
||||
视频标题和 hashtag 列表
|
||||
"""
|
||||
|
||||
# 获取视频标题和 hashtag txt 文件名
|
||||
txt_filename = filename.replace(".mp4", ".txt")
|
||||
|
||||
# 读取 txt 文件
|
||||
with open(txt_filename, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# 获取标题和 hashtag
|
||||
splite_str = content.strip().split("\n")
|
||||
title = splite_str[0]
|
||||
hashtags = splite_str[1].replace("#", "").split(" ")
|
||||
|
||||
return title, hashtags
|
||||
|
||||
|
||||
def generate_schedule_time_next_day(total_videos, videos_per_day, daily_times=None, timestamps=False, start_days=0):
|
||||
"""
|
||||
Generate a schedule for video uploads, starting from the next day.
|
||||
|
||||
Args:
|
||||
- total_videos: Total number of videos to be uploaded.
|
||||
- videos_per_day: Number of videos to be uploaded each day.
|
||||
- daily_times: Optional list of specific times of the day to publish the videos.
|
||||
- timestamps: Boolean to decide whether to return timestamps or datetime objects.
|
||||
- start_days: Start from after start_days.
|
||||
|
||||
Returns:
|
||||
- A list of scheduling times for the videos, either as timestamps or datetime objects.
|
||||
"""
|
||||
if videos_per_day <= 0:
|
||||
raise ValueError("videos_per_day should be a positive integer")
|
||||
|
||||
if daily_times is None:
|
||||
# Default times to publish videos if not provided
|
||||
daily_times = [6, 11, 14, 16, 22]
|
||||
|
||||
if videos_per_day > len(daily_times):
|
||||
raise ValueError("videos_per_day should not exceed the length of daily_times")
|
||||
|
||||
# Generate timestamps
|
||||
schedule = []
|
||||
current_time = datetime.now()
|
||||
|
||||
for video in range(total_videos):
|
||||
day = video // videos_per_day + start_days + 1 # +1 to start from the next day
|
||||
daily_video_index = video % videos_per_day
|
||||
|
||||
# Calculate the time for the current video
|
||||
hour = daily_times[daily_video_index]
|
||||
time_offset = timedelta(days=day, hours=hour - current_time.hour, minutes=-current_time.minute,
|
||||
seconds=-current_time.second, microseconds=-current_time.microsecond)
|
||||
timestamp = current_time + time_offset
|
||||
|
||||
schedule.append(timestamp)
|
||||
|
||||
if timestamps:
|
||||
schedule = [int(time.timestamp()) for time in schedule]
|
||||
return schedule
|
||||
@@ -0,0 +1,52 @@
|
||||
from pathlib import Path
|
||||
from sys import stdout
|
||||
from loguru import logger
|
||||
|
||||
from conf import BASE_DIR
|
||||
|
||||
|
||||
def log_formatter(record: dict) -> str:
|
||||
"""
|
||||
Formatter for log records.
|
||||
:param dict record: Log object containing log metadata & message.
|
||||
:returns: str
|
||||
"""
|
||||
colors = {
|
||||
"TRACE": "#cfe2f3",
|
||||
"INFO": "#9cbfdd",
|
||||
"DEBUG": "#8598ea",
|
||||
"WARNING": "#dcad5a",
|
||||
"SUCCESS": "#3dd08d",
|
||||
"ERROR": "#ae2c2c"
|
||||
}
|
||||
color = colors.get(record["level"].name, "#b3cfe7")
|
||||
return f"<fg #70acde>{{time:YYYY-MM-DD HH:mm:ss}}</fg #70acde> | <fg {color}>{{level}}</fg {color}>: <light-white>{{message}}</light-white>\n"
|
||||
|
||||
|
||||
def create_logger(log_name: str, file_path: str):
|
||||
"""
|
||||
Create custom logger for different business modules.
|
||||
:param str log_name: name of log
|
||||
:param str file_path: Optional path to log file
|
||||
:returns: Configured logger
|
||||
"""
|
||||
def filter_record(record):
|
||||
return record["extra"].get("business_name") == log_name
|
||||
|
||||
Path(BASE_DIR / file_path).parent.mkdir(exist_ok=True)
|
||||
logger.add(Path(BASE_DIR / file_path), filter=filter_record, level="INFO", rotation="10 MB", retention="10 days", backtrace=True, diagnose=True)
|
||||
return logger.bind(business_name=log_name)
|
||||
|
||||
|
||||
# Remove all existing handlers
|
||||
logger.remove()
|
||||
# Add a standard console handler
|
||||
logger.add(stdout, colorize=True, format=log_formatter)
|
||||
|
||||
douyin_logger = create_logger('douyin', 'logs/douyin.log')
|
||||
tencent_logger = create_logger('tencent', 'logs/tencent.log')
|
||||
xhs_logger = create_logger('xhs', 'logs/xhs.log')
|
||||
tiktok_logger = create_logger('tiktok', 'logs/tiktok.log')
|
||||
bilibili_logger = create_logger('bilibili', 'logs/bilibili.log')
|
||||
kuaishou_logger = create_logger('kuaishou', 'logs/kuaishou.log')
|
||||
baijiahao_logger = create_logger('baijiahao', 'logs/baijiahao.log')
|
||||
@@ -0,0 +1,28 @@
|
||||
import asyncio
|
||||
import time
|
||||
from functools import wraps
|
||||
|
||||
|
||||
def async_retry(timeout=60, max_retries=None):
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
async def wrapper(*args, **kwargs):
|
||||
start_time = time.time()
|
||||
attempts = 0
|
||||
while True:
|
||||
try:
|
||||
return await func(*args, **kwargs)
|
||||
except Exception as e:
|
||||
attempts += 1
|
||||
if max_retries is not None and attempts >= max_retries:
|
||||
print(f"Reached maximum retries of {max_retries}.")
|
||||
raise Exception(f"Failed after {max_retries} retries.") from e
|
||||
if time.time() - start_time > timeout:
|
||||
print(f"Function timeout after {timeout} seconds.")
|
||||
raise TimeoutError(f"Function execution exceeded {timeout} seconds timeout.") from e
|
||||
print(f"Attempt {attempts} failed: {e}. Retrying...")
|
||||
await asyncio.sleep(1) # Sleep to avoid tight loop or provide backoff logic here
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
Vendored
+7
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user