podflow 2025.1.26__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- Podflow/__init__.py +137 -0
- Podflow/basic/__init__.py +2 -0
- Podflow/basic/file_save.py +21 -0
- Podflow/basic/folder_build.py +16 -0
- Podflow/basic/get_duration.py +18 -0
- Podflow/basic/get_file_list.py +57 -0
- Podflow/basic/get_html_dict.py +30 -0
- Podflow/basic/http_client.py +75 -0
- Podflow/basic/list_merge_tidy.py +15 -0
- Podflow/basic/qr_code.py +55 -0
- Podflow/basic/split_dict.py +14 -0
- Podflow/basic/time_format.py +16 -0
- Podflow/basic/time_stamp.py +61 -0
- Podflow/basic/vary_replace.py +11 -0
- Podflow/basic/write_log.py +43 -0
- Podflow/bilibili/__init__.py +2 -0
- Podflow/bilibili/build.py +201 -0
- Podflow/bilibili/get.py +477 -0
- Podflow/bilibili/login.py +307 -0
- Podflow/config/__init__.py +2 -0
- Podflow/config/build_original.py +41 -0
- Podflow/config/channge_icon.py +163 -0
- Podflow/config/correct_channelid.py +230 -0
- Podflow/config/correct_config.py +103 -0
- Podflow/config/get_channelid.py +22 -0
- Podflow/config/get_channelid_id.py +21 -0
- Podflow/config/get_config.py +34 -0
- Podflow/download/__init__.py +2 -0
- Podflow/download/convert_bytes.py +18 -0
- Podflow/download/delete_part.py +20 -0
- Podflow/download/dl_aideo_video.py +307 -0
- Podflow/download/show_progress.py +46 -0
- Podflow/download/wait_animation.py +34 -0
- Podflow/download/youtube_and_bilibili_download.py +30 -0
- Podflow/ffmpeg_judge.py +45 -0
- Podflow/httpfs/__init__.py +2 -0
- Podflow/httpfs/app_bottle.py +212 -0
- Podflow/httpfs/port_judge.py +21 -0
- Podflow/main.py +248 -0
- Podflow/makeup/__init__.py +2 -0
- Podflow/makeup/del_makeup_yt_format_fail.py +19 -0
- Podflow/makeup/make_up_file.py +51 -0
- Podflow/makeup/make_up_file_format_mod.py +96 -0
- Podflow/makeup/make_up_file_mod.py +30 -0
- Podflow/message/__init__.py +2 -0
- Podflow/message/backup_zip_save.py +45 -0
- Podflow/message/create_main_rss.py +44 -0
- Podflow/message/display_qrcode_and_url.py +36 -0
- Podflow/message/fail_message_initialize.py +165 -0
- Podflow/message/format_time.py +27 -0
- Podflow/message/get_original_rss.py +65 -0
- Podflow/message/get_video_format.py +111 -0
- Podflow/message/get_video_format_multithread.py +42 -0
- Podflow/message/get_youtube_and_bilibili_video_format.py +87 -0
- Podflow/message/media_format.py +195 -0
- Podflow/message/original_rss_fail_print.py +15 -0
- Podflow/message/rss_create_hash.py +26 -0
- Podflow/message/title_correction.py +30 -0
- Podflow/message/update_information_display.py +72 -0
- Podflow/message/update_youtube_bilibili_rss.py +116 -0
- Podflow/message/want_retry.py +21 -0
- Podflow/message/xml_item.py +83 -0
- Podflow/message/xml_original_item.py +92 -0
- Podflow/message/xml_rss.py +46 -0
- Podflow/netscape/__init__.py +2 -0
- Podflow/netscape/bulid_netscape.py +44 -0
- Podflow/netscape/get_cookie_dict.py +21 -0
- Podflow/parse_arguments.py +80 -0
- Podflow/remove/__init__.py +2 -0
- Podflow/remove/remove_dir.py +33 -0
- Podflow/remove/remove_file.py +23 -0
- Podflow/youtube/__init__.py +2 -0
- Podflow/youtube/build.py +287 -0
- Podflow/youtube/get.py +376 -0
- Podflow/youtube/login.py +39 -0
- podflow-2025.1.26.dist-info/METADATA +214 -0
- podflow-2025.1.26.dist-info/RECORD +80 -0
- podflow-2025.1.26.dist-info/WHEEL +5 -0
- podflow-2025.1.26.dist-info/entry_points.txt +6 -0
- podflow-2025.1.26.dist-info/top_level.txt +1 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
# Podflow/message/xml_rss.py
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
import time
|
5
|
+
|
6
|
+
|
7
|
+
# 生成XML模块
|
8
|
+
def xml_rss(title, link, description, category, icon, items):
|
9
|
+
# 获取当前时间
|
10
|
+
current_time_now = time.time() # 获取当前时间的秒数
|
11
|
+
# 获取当前时区和夏令时信息
|
12
|
+
time_info_now = time.localtime(current_time_now)
|
13
|
+
# 构造时间字符串
|
14
|
+
formatted_time_now = time.strftime("%a, %d %b %Y %H:%M:%S %z", time_info_now)
|
15
|
+
#itunes_summary = description.replace("\n", "
")
|
16
|
+
if title == "Podflow":
|
17
|
+
author = "gruel-zxz"
|
18
|
+
subtitle = "gruel-zxz-podflow"
|
19
|
+
else:
|
20
|
+
author = title
|
21
|
+
subtitle = title
|
22
|
+
return f"""<?xml version="1.0" encoding="UTF-8"?>
|
23
|
+
<rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
|
24
|
+
<channel>
|
25
|
+
<title>{title}</title>
|
26
|
+
<link>{link}</link>
|
27
|
+
<description>{description}</description>
|
28
|
+
<category>{category}</category>
|
29
|
+
<generator>Podflow (support us at https://github.com/gruel-zxz/podflow)</generator>
|
30
|
+
<language>en-us</language>
|
31
|
+
<lastBuildDate>{formatted_time_now}</lastBuildDate>
|
32
|
+
<pubDate>Sun, 24 Apr 2005 11:20:54 +0800</pubDate>
|
33
|
+
<image>
|
34
|
+
<url>{icon}</url>
|
35
|
+
<title>{title}</title>
|
36
|
+
<link>{link}</link>
|
37
|
+
</image>
|
38
|
+
<itunes:author>{author}</itunes:author>
|
39
|
+
<itunes:subtitle>{subtitle}</itunes:subtitle>
|
40
|
+
<itunes:summary><![CDATA[{description}]]></itunes:summary>
|
41
|
+
<itunes:image href="{icon}"></itunes:image>
|
42
|
+
<itunes:explicit>no</itunes:explicit>
|
43
|
+
<itunes:category text="{category}"></itunes:category>
|
44
|
+
{items}
|
45
|
+
</channel>
|
46
|
+
</rss>"""
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Podflow/netscape/bulid_netscape.py
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
from Podflow.basic.file_save import file_save
|
5
|
+
|
6
|
+
|
7
|
+
# 生成Netscape_HTTP_Cookie模块
|
8
|
+
def bulid_netscape(file_name, cookie=None):
|
9
|
+
if cookie is None:
|
10
|
+
cookie = {}
|
11
|
+
if "bilibili" in file_name:
|
12
|
+
cookie_jar = f'''# Netscape HTTP Cookie File
|
13
|
+
# This file is generated by yt-dlp. Do not edit.
|
14
|
+
|
15
|
+
.bilibili.com TRUE / FALSE 0 SESSDATA {cookie.get("SESSDATA", "")}
|
16
|
+
.bilibili.com TRUE / FALSE 0 bili_jct {cookie.get("bili_jct", "")}
|
17
|
+
.bilibili.com TRUE / FALSE 0 DedeUserID {cookie.get("DedeUserID", "")}
|
18
|
+
.bilibili.com TRUE / FALSE 0 DedeUserID__ckMd5 {cookie.get("DedeUserID__ckMd5", "")}
|
19
|
+
.bilibili.com TRUE / FALSE 0 sid {cookie.get("sid", "")}
|
20
|
+
.bilibili.com TRUE / FALSE 0 buvid3 {cookie.get("buvid3", "")}
|
21
|
+
.bilibili.com TRUE / FALSE 0 b_nut {cookie.get("b_nut", "")}'''
|
22
|
+
elif "youtube" in file_name:
|
23
|
+
cookie_jar = f'''# Netscape HTTP Cookie File
|
24
|
+
# This file is generated by yt-dlp. Do not edit.
|
25
|
+
|
26
|
+
.youtube.com TRUE / TRUE 0 __Secure-3PSID {cookie.get("__Secure-3PSID", "")}
|
27
|
+
.youtube.com TRUE / TRUE 0 __Secure-1PSIDTS {cookie.get("__Secure-1PSIDTS", "")}
|
28
|
+
.youtube.com TRUE / TRUE 0 SAPISID {cookie.get("SAPISID", "")}
|
29
|
+
.youtube.com TRUE / TRUE 0 __Secure-1PSIDCC {cookie.get("__Secure-1PSIDCC", "")}
|
30
|
+
.youtube.com TRUE / TRUE 0 SSID {cookie.get("SSID", "")}
|
31
|
+
.youtube.com TRUE / TRUE 0 __Secure-1PAPISID {cookie.get("__Secure-1PAPISID", "")}
|
32
|
+
.youtube.com TRUE / TRUE 0 __Secure-1PSID {cookie.get("__Secure-1PSID", "")}
|
33
|
+
.youtube.com TRUE / TRUE 0 __Secure-3PAPISID {cookie.get("__Secure-3PAPISID", "")}
|
34
|
+
.youtube.com TRUE / TRUE 0 __Secure-3PSIDCC {cookie.get("__Secure-3PSIDCC", "")}
|
35
|
+
.youtube.com TRUE / TRUE 0 __Secure-3PSIDTS {cookie.get("__Secure-3PSIDTS", "")}
|
36
|
+
.youtube.com TRUE / TRUE 0 LOGIN_INFO {cookie.get("LOGIN_INFO", "")}
|
37
|
+
.youtube.com TRUE / FALSE 0 PREF {cookie.get("PREF", "")}
|
38
|
+
.youtube.com TRUE / TRUE 0 YSC {cookie.get("YSC", "")}
|
39
|
+
.youtube.com TRUE / TRUE 0 VISITOR_INFO1_LIVE {cookie.get("VISITOR_INFO1_LIVE", "")}
|
40
|
+
.youtube.com TRUE / TRUE 0 VISITOR_PRIVACY_METADATA {cookie.get("VISITOR_PRIVACY_METADATA", "")}'''
|
41
|
+
else:
|
42
|
+
cookie_jar = '''# Netscape HTTP Cookie File
|
43
|
+
# This file is generated by yt-dlp. Do not edit.'''
|
44
|
+
file_save(cookie_jar, f"{file_name}.txt", "channel_data")
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Podflow/Netscape/get_cookie_dict.py
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
from datetime import datetime
|
5
|
+
from http.cookiejar import LoadError, MozillaCookieJar
|
6
|
+
|
7
|
+
|
8
|
+
# 将Netscape转Dict模块
|
9
|
+
def get_cookie_dict(file):
|
10
|
+
parts = file.split("/")
|
11
|
+
try:
|
12
|
+
# 加载Netscape格式的cookie文件
|
13
|
+
cookie_jar = MozillaCookieJar(file)
|
14
|
+
cookie_jar.load(ignore_discard=True)
|
15
|
+
return {cookie.name: cookie.value for cookie in cookie_jar}
|
16
|
+
except FileNotFoundError:
|
17
|
+
print(f"{datetime.now().strftime('%H:%M:%S')}|{parts[-1]}文件不存在")
|
18
|
+
return None
|
19
|
+
except LoadError:
|
20
|
+
print(f"{datetime.now().strftime('%H:%M:%S')}|{parts[-1]}文件错误")
|
21
|
+
return None
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# Podflow/parse_arguments.py
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
import argparse
|
5
|
+
from Podflow import parse
|
6
|
+
|
7
|
+
|
8
|
+
def positive_int(value):
|
9
|
+
ivalue = int(value)
|
10
|
+
if ivalue <= 0:
|
11
|
+
raise argparse.ArgumentTypeError(f"{value} is not a positive integer")
|
12
|
+
return ivalue
|
13
|
+
|
14
|
+
|
15
|
+
# 获取命令行参数并判断
|
16
|
+
def parse_arguments():
|
17
|
+
# 创建 ArgumentParser 对象
|
18
|
+
parser = argparse.ArgumentParser(
|
19
|
+
description="you can try: Podflow -n 24 -d 3600"
|
20
|
+
)
|
21
|
+
# 参数
|
22
|
+
parser.add_argument(
|
23
|
+
"-n",
|
24
|
+
"--times",
|
25
|
+
nargs=1,
|
26
|
+
type=positive_int,
|
27
|
+
metavar="NUM",
|
28
|
+
help="number of times",
|
29
|
+
)
|
30
|
+
parser.add_argument(
|
31
|
+
"-d",
|
32
|
+
"--delay",
|
33
|
+
type=positive_int,
|
34
|
+
default=1500,
|
35
|
+
metavar="NUM",
|
36
|
+
help="delay in seconds(default: 1500)",
|
37
|
+
)
|
38
|
+
parser.add_argument(
|
39
|
+
"-c",
|
40
|
+
"--config",
|
41
|
+
type=str,
|
42
|
+
default="config.json",
|
43
|
+
metavar="FILE_PATH",
|
44
|
+
help="path to the config.json file",
|
45
|
+
)
|
46
|
+
parser.add_argument(
|
47
|
+
"-p",
|
48
|
+
"--period",
|
49
|
+
type=positive_int,
|
50
|
+
metavar="NUM",
|
51
|
+
default=1,
|
52
|
+
help="Specify the update frequency (unit: times/day), default value is 1",
|
53
|
+
)
|
54
|
+
parser.add_argument(
|
55
|
+
"--shortcuts",
|
56
|
+
nargs="*",
|
57
|
+
type=str,
|
58
|
+
metavar="URL",
|
59
|
+
help="only shortcuts can be work",
|
60
|
+
)
|
61
|
+
parser.add_argument("--file", nargs="?", help=argparse.SUPPRESS) # 仅运行在ipynb中
|
62
|
+
parser.add_argument("--httpfs", action="store_true", help=argparse.SUPPRESS)
|
63
|
+
# 解析参数
|
64
|
+
args = parser.parse_args()
|
65
|
+
parse.time_delay = args.delay
|
66
|
+
parse.config = args.config
|
67
|
+
parse.period = args.period
|
68
|
+
parse.file = args.file
|
69
|
+
parse.httpfs = args.httpfs
|
70
|
+
# 检查并处理参数的状态
|
71
|
+
if args.times is not None:
|
72
|
+
parse.update_num = int(args.times[0])
|
73
|
+
if args.shortcuts is not None:
|
74
|
+
parse.update_num = 1
|
75
|
+
parse.argument = "a-shell"
|
76
|
+
parse.shortcuts_url_original = args.shortcuts
|
77
|
+
if args.file is not None and ".json" in args.file:
|
78
|
+
parse.update_num = 1
|
79
|
+
parse.argument = ""
|
80
|
+
parse.shortcuts_url_original = []
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Podflow/remove/remove_dir.py
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
import os
|
5
|
+
import re
|
6
|
+
import shutil
|
7
|
+
from Podflow import gVar
|
8
|
+
from Podflow.basic.write_log import write_log
|
9
|
+
|
10
|
+
|
11
|
+
# 删除已抛弃的媒体文件夹模块
|
12
|
+
def remove_dir():
|
13
|
+
def remove_path(name):
|
14
|
+
directory_path = f"channel_audiovisual/{name}"
|
15
|
+
# 检查目录是否存在
|
16
|
+
if os.path.exists(directory_path):
|
17
|
+
# 删除该目录及其内容
|
18
|
+
shutil.rmtree(directory_path)
|
19
|
+
write_log(f"{name}文件夹已删除")
|
20
|
+
|
21
|
+
folder_names = [
|
22
|
+
folder
|
23
|
+
for folder in os.listdir("channel_audiovisual")
|
24
|
+
if os.path.isdir(f"channel_audiovisual/{folder}")
|
25
|
+
]
|
26
|
+
folder_names_youtube = [name for name in folder_names if re.match(r"UC.{22}", name)]
|
27
|
+
for name in folder_names_youtube:
|
28
|
+
if name not in gVar.channelid_youtube_ids_original:
|
29
|
+
remove_path(name)
|
30
|
+
folder_names_bilibili = [name for name in folder_names if re.match(r"[0-9]+", name)]
|
31
|
+
for name in folder_names_bilibili:
|
32
|
+
if name not in gVar.channelid_bilibili_ids_original:
|
33
|
+
remove_path(name)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Podflow/remove/remove_file.py
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
import os
|
5
|
+
from Podflow import gVar
|
6
|
+
from Podflow.basic.write_log import write_log
|
7
|
+
|
8
|
+
|
9
|
+
# 删除多余媒体文件模块
|
10
|
+
def remove_file():
|
11
|
+
channelid_youtube_ids = gVar.channelid_youtube_ids
|
12
|
+
for output_dir, name in channelid_youtube_ids.items():
|
13
|
+
for file_name in os.listdir(f"channel_audiovisual/{output_dir}"):
|
14
|
+
if file_name not in gVar.all_youtube_content_ytid[output_dir]:
|
15
|
+
os.remove(f"channel_audiovisual/{output_dir}/{file_name}")
|
16
|
+
write_log(f"{name}|{file_name}已删除")
|
17
|
+
|
18
|
+
channelid_bilibili_ids = gVar.channelid_bilibili_ids
|
19
|
+
for output_dir, name in channelid_bilibili_ids.items():
|
20
|
+
for file_name in os.listdir(f"channel_audiovisual/{output_dir}"):
|
21
|
+
if file_name not in gVar.all_bilibili_content_bvid[output_dir]:
|
22
|
+
os.remove(f"channel_audiovisual/{output_dir}/{file_name}")
|
23
|
+
write_log(f"{name}|{file_name}已删除")
|
Podflow/youtube/build.py
ADDED
@@ -0,0 +1,287 @@
|
|
1
|
+
# Podflow/youtube/build.py
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
import re
|
5
|
+
import html
|
6
|
+
import threading
|
7
|
+
import contextlib
|
8
|
+
import xml.etree.ElementTree as ET
|
9
|
+
from datetime import datetime, timezone
|
10
|
+
from Podflow import gVar
|
11
|
+
from Podflow.message.xml_rss import xml_rss
|
12
|
+
from Podflow.basic.file_save import file_save
|
13
|
+
from Podflow.message.xml_item import xml_item
|
14
|
+
from Podflow.basic.http_client import http_client
|
15
|
+
from Podflow.message.format_time import format_time
|
16
|
+
from Podflow.basic.get_html_dict import get_html_dict
|
17
|
+
from Podflow.message.xml_original_item import xml_original_item
|
18
|
+
|
19
|
+
|
20
|
+
# 获取YouTube频道简介模块
|
21
|
+
def get_youtube_introduction():
|
22
|
+
# 创建线程锁
|
23
|
+
youtube_xml_get_lock = threading.Lock()
|
24
|
+
|
25
|
+
# 使用http获取youtube频道简介和图标模块
|
26
|
+
def youtube_xml_get(output_dir):
|
27
|
+
if channel_about := http_client(
|
28
|
+
f"https://www.youtube.com/channel/{output_dir}/about",
|
29
|
+
f"{gVar.channelid_youtube_ids[output_dir]} 简介",
|
30
|
+
2,
|
31
|
+
5,
|
32
|
+
):
|
33
|
+
channel_about = channel_about.text
|
34
|
+
xml_tree = {
|
35
|
+
"icon": re.sub(
|
36
|
+
r"=s(0|[1-9]\d{0,3}|1[0-9]{1,3}|20[0-3][0-9]|204[0-8])-c-k",
|
37
|
+
"=s2048-c-k",
|
38
|
+
re.search(
|
39
|
+
r"https?://yt3.googleusercontent.com/[^\s]*(?=\">)",
|
40
|
+
channel_about,
|
41
|
+
).group(),
|
42
|
+
)
|
43
|
+
}
|
44
|
+
xml_tree["description"] = re.search(
|
45
|
+
r"(?<=\<meta itemprop\=\"description\" content\=\").*?(?=\")",
|
46
|
+
channel_about,
|
47
|
+
flags=re.DOTALL,
|
48
|
+
).group()
|
49
|
+
with youtube_xml_get_lock:
|
50
|
+
gVar.youtube_xml_get_tree[output_dir] = xml_tree
|
51
|
+
|
52
|
+
# 创建线程列表
|
53
|
+
youtube_xml_get_threads = []
|
54
|
+
for output_dir in gVar.channelid_youtube_ids_update:
|
55
|
+
thread = threading.Thread(target=youtube_xml_get, args=(output_dir,))
|
56
|
+
youtube_xml_get_threads.append(thread)
|
57
|
+
thread.start()
|
58
|
+
# 等待所有线程完成
|
59
|
+
for thread in youtube_xml_get_threads:
|
60
|
+
thread.join()
|
61
|
+
|
62
|
+
|
63
|
+
# 获取YouTube播放列表模块
|
64
|
+
def get_youtube_playlist(url, channelid_title):
|
65
|
+
videoids = []
|
66
|
+
ytInitialData = get_html_dict(url, f"{channelid_title}|播放列表", "ytInitialData")
|
67
|
+
with contextlib.suppress(KeyError):
|
68
|
+
contents = ytInitialData["contents"]["twoColumnBrowseResultsRenderer"]["tabs"][
|
69
|
+
0
|
70
|
+
]["tabRenderer"]["content"]["sectionListRenderer"]["contents"][0][
|
71
|
+
"itemSectionRenderer"
|
72
|
+
]["contents"][0]["playlistVideoListRenderer"]["contents"]
|
73
|
+
videoids.extend(
|
74
|
+
content["playlistVideoRenderer"]["videoId"] for content in contents
|
75
|
+
)
|
76
|
+
return videoids
|
77
|
+
|
78
|
+
|
79
|
+
# 从HTML获取YouTube媒体信息模块
|
80
|
+
def youtube_html_guid_message(guid):
|
81
|
+
ytInitialPlayerResponse = get_html_dict(
|
82
|
+
f"https://www.youtube.com/watch?v={guid}",
|
83
|
+
f"{guid} HTML",
|
84
|
+
"ytInitialPlayerResponse",
|
85
|
+
)
|
86
|
+
try:
|
87
|
+
image = ytInitialPlayerResponse["microformat"]["playerMicroformatRenderer"][
|
88
|
+
"thumbnail"
|
89
|
+
]["thumbnails"][0]["url"]
|
90
|
+
title = ytInitialPlayerResponse["microformat"]["playerMicroformatRenderer"][
|
91
|
+
"title"
|
92
|
+
]["simpleText"]
|
93
|
+
description = ytInitialPlayerResponse["microformat"][
|
94
|
+
"playerMicroformatRenderer"
|
95
|
+
]["description"]["simpleText"]
|
96
|
+
published = ytInitialPlayerResponse["microformat"]["playerMicroformatRenderer"][
|
97
|
+
"publishDate"
|
98
|
+
]
|
99
|
+
return published, image, title, description
|
100
|
+
except KeyError:
|
101
|
+
return None
|
102
|
+
|
103
|
+
|
104
|
+
# 生成YouTube的item模块
|
105
|
+
def youtube_xml_item(entry, title_change=None):
|
106
|
+
if title_change is None:
|
107
|
+
title_change = {}
|
108
|
+
|
109
|
+
# 输入时间字符串和原始时区
|
110
|
+
time_str = re.search(r"(?<=<published>).+(?=</published>)", entry).group()
|
111
|
+
pubDate = format_time(time_str)
|
112
|
+
output_dir = re.search(r"(?<=<yt:channelId>).+(?=</yt:channelId>)", entry).group()
|
113
|
+
description = re.search(
|
114
|
+
r"(?<=<media:description>).+(?=</media:description>)",
|
115
|
+
re.sub(r"\n+", "\n", entry),
|
116
|
+
flags=re.DOTALL,
|
117
|
+
)
|
118
|
+
description = description.group() if description else ""
|
119
|
+
ytid = re.search(r"(?<=<yt:videoId>).+(?=</yt:videoId>)", entry).group()
|
120
|
+
return xml_item(
|
121
|
+
ytid,
|
122
|
+
output_dir,
|
123
|
+
f"https://youtube.com/watch?v={ytid}",
|
124
|
+
gVar.channelid_youtube[gVar.channelid_youtube_ids[output_dir]]["title"],
|
125
|
+
re.search(r"(?<=<title>).+(?=</title>)", entry).group(),
|
126
|
+
description,
|
127
|
+
pubDate,
|
128
|
+
re.search(r"(?<=<media:thumbnail url=\").+(?=\" width=\")", entry).group(),
|
129
|
+
title_change,
|
130
|
+
)
|
131
|
+
|
132
|
+
|
133
|
+
def get_xml_item(guid, item, channelid_title, title_change, output_dir):
|
134
|
+
video_website = f"https://youtube.com/watch?v={guid}"
|
135
|
+
if item["yt-dlp"]:
|
136
|
+
guid_value = gVar.video_id_update_format[guid]
|
137
|
+
if timestamp := guid_value["timestamp"]:
|
138
|
+
published = datetime.fromtimestamp(timestamp, timezone.utc).strftime(
|
139
|
+
"%Y-%m-%dT%H:%M:%S%z"
|
140
|
+
)
|
141
|
+
title = guid_value["title"]
|
142
|
+
description = guid_value["description"]
|
143
|
+
image = guid_value["image"]
|
144
|
+
elif guid_message := youtube_html_guid_message(guid):
|
145
|
+
published, image, title, description = guid_message
|
146
|
+
else:
|
147
|
+
return None
|
148
|
+
title = html.escape(title)
|
149
|
+
description = html.escape(re.sub(r"\n+", "\n", description))
|
150
|
+
image = re.sub(r"\?.*$", "", image)
|
151
|
+
pubDate = format_time(published)
|
152
|
+
else:
|
153
|
+
title = html.escape(item["title"])
|
154
|
+
description = html.escape(re.sub(r"\n+", "\n", item["description"]))
|
155
|
+
pubDate = format_time(item["pubDate"])
|
156
|
+
image = re.sub(r"\?.*$", "", item["image"])
|
157
|
+
return xml_item(
|
158
|
+
guid,
|
159
|
+
output_dir,
|
160
|
+
video_website,
|
161
|
+
channelid_title,
|
162
|
+
title,
|
163
|
+
description,
|
164
|
+
pubDate,
|
165
|
+
image,
|
166
|
+
title_change,
|
167
|
+
)
|
168
|
+
|
169
|
+
|
170
|
+
# 生成YouTube对应channel的需更新的items模块
|
171
|
+
def youtube_xml_items(output_dir):
|
172
|
+
items_list = [f"<!-- {output_dir} -->"]
|
173
|
+
entry_num = 0
|
174
|
+
original_judgment = True
|
175
|
+
channelid_youtube_value = gVar.channelid_youtube[
|
176
|
+
gVar.channelid_youtube_ids[output_dir]
|
177
|
+
]
|
178
|
+
channelid_title = channelid_youtube_value["title"]
|
179
|
+
title_change = channelid_youtube_value.get("title_change", [])
|
180
|
+
update_size = channelid_youtube_value["update_size"]
|
181
|
+
if title_change:
|
182
|
+
for title_index, title_value in enumerate(title_change):
|
183
|
+
if "url" in title_value:
|
184
|
+
title_change[title_index]["table"] = get_youtube_playlist(
|
185
|
+
title_value["url"], channelid_title
|
186
|
+
)
|
187
|
+
output_dir_value = gVar.channelid_youtube_rss[output_dir]
|
188
|
+
# 最新更新
|
189
|
+
if output_dir_value["type"] == "dict":
|
190
|
+
for guid in output_dir_value["content"]["list"]:
|
191
|
+
if guid not in gVar.video_id_failed:
|
192
|
+
item = output_dir_value["content"]["item"][guid]
|
193
|
+
if xml_item_text := get_xml_item(
|
194
|
+
guid, item, channelid_title, title_change, output_dir
|
195
|
+
):
|
196
|
+
items_list.append(f"{xml_item_text}<!-- {output_dir} -->")
|
197
|
+
entry_num += 1
|
198
|
+
if (
|
199
|
+
gVar.video_id_update_format[guid]["description"]
|
200
|
+
and gVar.video_id_update_format[guid]["description"][0] == "『"
|
201
|
+
):
|
202
|
+
original_judgment = False
|
203
|
+
else:
|
204
|
+
if output_dir_value["type"] == "html": # 获取最新的rss信息
|
205
|
+
file_xml = output_dir_value["content"].text
|
206
|
+
else:
|
207
|
+
file_xml = output_dir_value["content"]
|
208
|
+
entrys = re.findall(r"<entry>.+?</entry>", file_xml, re.DOTALL)
|
209
|
+
for entry in entrys:
|
210
|
+
if (
|
211
|
+
re.search(r"(?<=<yt:videoId>).+(?=</yt:videoId>)", entry).group()
|
212
|
+
not in gVar.video_id_failed
|
213
|
+
):
|
214
|
+
items_list.append(
|
215
|
+
f"{youtube_xml_item(entry, title_change)}<!-- {output_dir} -->"
|
216
|
+
)
|
217
|
+
entry_num += 1
|
218
|
+
if re.search(r"(?<=<media:description>)『", entry):
|
219
|
+
original_judgment = False
|
220
|
+
if entry_num >= update_size:
|
221
|
+
break
|
222
|
+
items_guid = re.findall(r"(?<=<guid>).+?(?=</guid>)", "".join(items_list))
|
223
|
+
# 存量接入
|
224
|
+
entry_count = channelid_youtube_value["last_size"] - len(items_guid)
|
225
|
+
if gVar.xmls_original and output_dir in gVar.xmls_original and entry_count > 0:
|
226
|
+
xml_num = 0
|
227
|
+
for xml in gVar.xmls_original[output_dir].split(f"<!-- {output_dir} -->"):
|
228
|
+
xml_guid = re.search(r"(?<=<guid>).+(?=</guid>)", xml)
|
229
|
+
if (
|
230
|
+
xml_guid
|
231
|
+
and xml_guid.group() not in items_guid
|
232
|
+
and xml_guid.group() not in gVar.video_id_failed
|
233
|
+
):
|
234
|
+
items_list.append(
|
235
|
+
f"{xml_original_item(xml, channelid_title, original_judgment, title_change)}<!-- {output_dir} -->"
|
236
|
+
)
|
237
|
+
xml_num += 1
|
238
|
+
if xml_num >= entry_count:
|
239
|
+
break
|
240
|
+
# 向后更新
|
241
|
+
with contextlib.suppress(KeyError):
|
242
|
+
backward = output_dir_value["backward"]
|
243
|
+
for backward_guid in backward["list"]:
|
244
|
+
if backward_guid not in gVar.video_id_failed:
|
245
|
+
backward_item = backward["item"][backward_guid]
|
246
|
+
if backward_xml_item_text := get_xml_item(
|
247
|
+
backward_guid,
|
248
|
+
backward_item,
|
249
|
+
channelid_title,
|
250
|
+
title_change,
|
251
|
+
output_dir,
|
252
|
+
):
|
253
|
+
items_list.append(f"{backward_xml_item_text}<!-- {output_dir} -->")
|
254
|
+
# 生成对应xml
|
255
|
+
try:
|
256
|
+
with open(
|
257
|
+
f"channel_rss/{output_dir}.xml", "r", encoding="utf-8"
|
258
|
+
) as file: # 打开文件进行读取
|
259
|
+
root = ET.parse(file).getroot()
|
260
|
+
description = (root.findall(".//description")[0]).text
|
261
|
+
description = "" if description is None else html.escape(description)
|
262
|
+
icon = (root.findall(".//url")[0]).text
|
263
|
+
except Exception: # 参数不存在直接更新
|
264
|
+
description = gVar.config["description"]
|
265
|
+
icon = gVar.config["icon"]
|
266
|
+
if (
|
267
|
+
output_dir in gVar.channelid_youtube_ids_update
|
268
|
+
and output_dir in gVar.youtube_xml_get_tree
|
269
|
+
):
|
270
|
+
description = gVar.youtube_xml_get_tree[output_dir]["description"]
|
271
|
+
icon = gVar.youtube_xml_get_tree[output_dir]["icon"]
|
272
|
+
category = gVar.config["category"]
|
273
|
+
if output_dir_value["type"] == "dict":
|
274
|
+
title = output_dir_value["content"]["title"]
|
275
|
+
else:
|
276
|
+
title = re.search(r"(?<=<title>).+(?=</title>)", file_xml).group()
|
277
|
+
link = f"https://www.youtube.com/channel/{output_dir}"
|
278
|
+
items = "".join(items_list)
|
279
|
+
items = f"""<!-- {{{output_dir}}} -->
|
280
|
+
{items}
|
281
|
+
<!-- {{{output_dir}}} -->"""
|
282
|
+
file_save(
|
283
|
+
xml_rss(title, link, description, category, icon, items),
|
284
|
+
f"{output_dir}.xml",
|
285
|
+
"channel_rss",
|
286
|
+
)
|
287
|
+
return items
|