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.
Files changed (80) hide show
  1. Podflow/__init__.py +137 -0
  2. Podflow/basic/__init__.py +2 -0
  3. Podflow/basic/file_save.py +21 -0
  4. Podflow/basic/folder_build.py +16 -0
  5. Podflow/basic/get_duration.py +18 -0
  6. Podflow/basic/get_file_list.py +57 -0
  7. Podflow/basic/get_html_dict.py +30 -0
  8. Podflow/basic/http_client.py +75 -0
  9. Podflow/basic/list_merge_tidy.py +15 -0
  10. Podflow/basic/qr_code.py +55 -0
  11. Podflow/basic/split_dict.py +14 -0
  12. Podflow/basic/time_format.py +16 -0
  13. Podflow/basic/time_stamp.py +61 -0
  14. Podflow/basic/vary_replace.py +11 -0
  15. Podflow/basic/write_log.py +43 -0
  16. Podflow/bilibili/__init__.py +2 -0
  17. Podflow/bilibili/build.py +201 -0
  18. Podflow/bilibili/get.py +477 -0
  19. Podflow/bilibili/login.py +307 -0
  20. Podflow/config/__init__.py +2 -0
  21. Podflow/config/build_original.py +41 -0
  22. Podflow/config/channge_icon.py +163 -0
  23. Podflow/config/correct_channelid.py +230 -0
  24. Podflow/config/correct_config.py +103 -0
  25. Podflow/config/get_channelid.py +22 -0
  26. Podflow/config/get_channelid_id.py +21 -0
  27. Podflow/config/get_config.py +34 -0
  28. Podflow/download/__init__.py +2 -0
  29. Podflow/download/convert_bytes.py +18 -0
  30. Podflow/download/delete_part.py +20 -0
  31. Podflow/download/dl_aideo_video.py +307 -0
  32. Podflow/download/show_progress.py +46 -0
  33. Podflow/download/wait_animation.py +34 -0
  34. Podflow/download/youtube_and_bilibili_download.py +30 -0
  35. Podflow/ffmpeg_judge.py +45 -0
  36. Podflow/httpfs/__init__.py +2 -0
  37. Podflow/httpfs/app_bottle.py +212 -0
  38. Podflow/httpfs/port_judge.py +21 -0
  39. Podflow/main.py +248 -0
  40. Podflow/makeup/__init__.py +2 -0
  41. Podflow/makeup/del_makeup_yt_format_fail.py +19 -0
  42. Podflow/makeup/make_up_file.py +51 -0
  43. Podflow/makeup/make_up_file_format_mod.py +96 -0
  44. Podflow/makeup/make_up_file_mod.py +30 -0
  45. Podflow/message/__init__.py +2 -0
  46. Podflow/message/backup_zip_save.py +45 -0
  47. Podflow/message/create_main_rss.py +44 -0
  48. Podflow/message/display_qrcode_and_url.py +36 -0
  49. Podflow/message/fail_message_initialize.py +165 -0
  50. Podflow/message/format_time.py +27 -0
  51. Podflow/message/get_original_rss.py +65 -0
  52. Podflow/message/get_video_format.py +111 -0
  53. Podflow/message/get_video_format_multithread.py +42 -0
  54. Podflow/message/get_youtube_and_bilibili_video_format.py +87 -0
  55. Podflow/message/media_format.py +195 -0
  56. Podflow/message/original_rss_fail_print.py +15 -0
  57. Podflow/message/rss_create_hash.py +26 -0
  58. Podflow/message/title_correction.py +30 -0
  59. Podflow/message/update_information_display.py +72 -0
  60. Podflow/message/update_youtube_bilibili_rss.py +116 -0
  61. Podflow/message/want_retry.py +21 -0
  62. Podflow/message/xml_item.py +83 -0
  63. Podflow/message/xml_original_item.py +92 -0
  64. Podflow/message/xml_rss.py +46 -0
  65. Podflow/netscape/__init__.py +2 -0
  66. Podflow/netscape/bulid_netscape.py +44 -0
  67. Podflow/netscape/get_cookie_dict.py +21 -0
  68. Podflow/parse_arguments.py +80 -0
  69. Podflow/remove/__init__.py +2 -0
  70. Podflow/remove/remove_dir.py +33 -0
  71. Podflow/remove/remove_file.py +23 -0
  72. Podflow/youtube/__init__.py +2 -0
  73. Podflow/youtube/build.py +287 -0
  74. Podflow/youtube/get.py +376 -0
  75. Podflow/youtube/login.py +39 -0
  76. podflow-2025.1.26.dist-info/METADATA +214 -0
  77. podflow-2025.1.26.dist-info/RECORD +80 -0
  78. podflow-2025.1.26.dist-info/WHEEL +5 -0
  79. podflow-2025.1.26.dist-info/entry_points.txt +6 -0
  80. 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,2 @@
1
+ # Podflow/netscape/__init__.py
2
+ # coding: utf-8
@@ -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,2 @@
1
+ # Podflow/remove/__init__.py
2
+ # coding: utf-8
@@ -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}已删除")
@@ -0,0 +1,2 @@
1
+ # Podflow/youtube/__init__.py
2
+ # coding: utf-8
@@ -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