channel.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792
  1. import asyncio
  2. import base64
  3. import copy
  4. import os
  5. import pickle
  6. import re
  7. from collections import defaultdict
  8. from logging import INFO
  9. from bs4 import NavigableString
  10. from opencc import OpenCC
  11. import utils.constants as constants
  12. from utils.config import config
  13. from utils.speed import (
  14. get_speed,
  15. sort_urls,
  16. check_ffmpeg_installed_status
  17. )
  18. from utils.tools import (
  19. get_name_url,
  20. check_url_by_keywords,
  21. check_url_ipv_type,
  22. get_total_urls,
  23. process_nested_dict,
  24. add_url_info,
  25. remove_cache_info,
  26. resource_path,
  27. get_urls_from_file,
  28. get_name_urls_from_file,
  29. get_logger,
  30. get_datetime_now,
  31. format_url_with_cache,
  32. get_url_host
  33. )
  34. def format_channel_data(url: str, origin: str = None) -> tuple:
  35. """
  36. Format the channel data
  37. """
  38. info = url.partition("$")[2]
  39. url_origin = "whitelist" if info and info.startswith("!") else origin
  40. url = format_url_with_cache(url) if url_origin == origin else url
  41. return url, None, None, url_origin
  42. def get_channel_data_from_file(channels, file, whitelist, open_local=config.open_local, local_data=None):
  43. """
  44. Get the channel data from the file
  45. """
  46. current_category = ""
  47. for line in file:
  48. line = line.strip()
  49. if "#genre#" in line:
  50. current_category = line.partition(",")[0]
  51. else:
  52. name_url = get_name_url(
  53. line, pattern=constants.demo_txt_pattern, check_url=False
  54. )
  55. if name_url and name_url[0]:
  56. name = name_url[0]["name"]
  57. url = name_url[0]["url"]
  58. category_dict = channels[current_category]
  59. if name not in category_dict:
  60. category_dict[name] = []
  61. if name in whitelist:
  62. for whitelist_url in whitelist[name]:
  63. category_dict[name].append((whitelist_url, None, None, "whitelist"))
  64. if open_local:
  65. if url:
  66. data = format_channel_data(url, "local")
  67. if data not in category_dict[name]:
  68. category_dict[name].append(data)
  69. if local_data and name in local_data:
  70. for local_url in local_data[name]:
  71. local_channel_data = format_channel_data(local_url, "local")
  72. if local_channel_data not in category_dict[name]:
  73. category_dict[name].append(local_channel_data)
  74. return channels
  75. def get_channel_items():
  76. """
  77. Get the channel items from the source file
  78. """
  79. user_source_file = resource_path(config.source_file)
  80. channels = defaultdict(lambda: defaultdict(list))
  81. local_data = get_name_urls_from_file(resource_path(config.local_file))
  82. whitelist = get_name_urls_from_file(constants.whitelist_path)
  83. whitelist_urls = get_urls_from_file(constants.whitelist_path)
  84. whitelist_len = len(list(whitelist.keys()))
  85. if whitelist_len:
  86. print(f"Found {whitelist_len} channel in whitelist")
  87. if os.path.exists(user_source_file):
  88. with open(user_source_file, "r", encoding="utf-8") as file:
  89. channels = get_channel_data_from_file(
  90. channels, file, whitelist, config.open_local, local_data
  91. )
  92. if config.open_history:
  93. result_cache_path = resource_path(constants.cache_path)
  94. if os.path.exists(result_cache_path):
  95. with open(result_cache_path, "rb") as file:
  96. old_result = pickle.load(file)
  97. for cate, data in channels.items():
  98. if cate in old_result:
  99. for name, info_list in data.items():
  100. urls = [
  101. item[0].partition("$")[0]
  102. for item in info_list
  103. if item[0]
  104. ]
  105. if name in old_result[cate]:
  106. for info in old_result[cate][name]:
  107. if info:
  108. try:
  109. if info[3] == "whitelist" and not any(
  110. url in info[0] for url in whitelist_urls):
  111. continue
  112. except:
  113. pass
  114. pure_url = info[0].partition("$")[0]
  115. if pure_url not in urls:
  116. channels[cate][name].append(info)
  117. return channels
  118. def format_channel_name(name):
  119. """
  120. Format the channel name with sub and replace and lower
  121. """
  122. if config.open_keep_all:
  123. return name
  124. cc = OpenCC("t2s")
  125. name = cc.convert(name)
  126. for region in constants.region_list:
  127. name = name.replace(f"{region}|", "")
  128. name = re.sub(constants.sub_pattern, "", name)
  129. for old, new in constants.replace_dict.items():
  130. name = name.replace(old, new)
  131. return name.lower()
  132. def channel_name_is_equal(name1, name2):
  133. """
  134. Check if the channel name is equal
  135. """
  136. if config.open_keep_all:
  137. return True
  138. name1_format = format_channel_name(name1)
  139. name2_format = format_channel_name(name2)
  140. return name1_format == name2_format
  141. def get_channel_results_by_name(name, data):
  142. """
  143. Get channel results from data by name
  144. """
  145. format_name = format_channel_name(name)
  146. results = data.get(format_name, [])
  147. return results
  148. def get_element_child_text_list(element, child_name):
  149. """
  150. Get the child text of the element
  151. """
  152. text_list = []
  153. children = element.find_all(child_name)
  154. if children:
  155. for child in children:
  156. text = child.get_text(strip=True)
  157. if text:
  158. text_list.append(text)
  159. return text_list
  160. def get_multicast_ip_list(urls):
  161. """
  162. Get the multicast ip list from urls
  163. """
  164. ip_list = []
  165. for url in urls:
  166. pattern = r"rtp://((\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?::(\d+))?)"
  167. matcher = re.search(pattern, url)
  168. if matcher:
  169. ip_list.append(matcher.group(1))
  170. return ip_list
  171. def get_channel_multicast_region_ip_list(result, channel_region, channel_type):
  172. """
  173. Get the channel multicast region ip list by region and type from result
  174. """
  175. return [
  176. ip
  177. for result_region, result_obj in result.items()
  178. if result_region in channel_region
  179. for type, urls in result_obj.items()
  180. if type in channel_type
  181. for ip in get_multicast_ip_list(urls)
  182. ]
  183. def get_channel_multicast_name_region_type_result(result, names):
  184. """
  185. Get the multicast name and region and type result by names from result
  186. """
  187. name_region_type_result = {}
  188. for name in names:
  189. data = result.get(name)
  190. if data:
  191. name_region_type_result[name] = data
  192. return name_region_type_result
  193. def get_channel_multicast_region_type_list(result):
  194. """
  195. Get the channel multicast region type list from result
  196. """
  197. region_list = config.multicast_region_list
  198. region_type_list = {
  199. (region, type)
  200. for region_type in result.values()
  201. for region, types in region_type.items()
  202. if "all" in region_list
  203. or "ALL" in region_list
  204. or "全部" in region_list
  205. or region in region_list
  206. for type in types
  207. }
  208. return list(region_type_list)
  209. def get_channel_multicast_result(result, search_result):
  210. """
  211. Get the channel multicast info result by result and search result
  212. """
  213. info_result = {}
  214. multicast_name = constants.origin_map["multicast"]
  215. whitelist = get_urls_from_file(constants.whitelist_path)
  216. blacklist = get_urls_from_file(constants.blacklist_path)
  217. for name, result_obj in result.items():
  218. info_list = [
  219. (
  220. (
  221. add_url_info(
  222. f"http://{url}/rtp/{ip}",
  223. f"{result_region}{result_type}{multicast_name}-cache:{url}",
  224. )
  225. if config.open_sort
  226. else add_url_info(
  227. f"http://{url}/rtp/{ip}",
  228. f"{result_region}{result_type}{multicast_name}",
  229. )
  230. ),
  231. date,
  232. resolution,
  233. )
  234. for result_region, result_types in result_obj.items()
  235. if result_region in search_result
  236. for result_type, result_type_urls in result_types.items()
  237. if result_type in search_result[result_region]
  238. for ip in get_multicast_ip_list(result_type_urls) or []
  239. for url, date, resolution in search_result[result_region][result_type]
  240. if (whitelist and check_url_by_keywords(f"http://{url}/rtp/{ip}", whitelist)) or
  241. (
  242. check_url_ipv_type(f"http://{url}/rtp/{ip}") and not check_url_by_keywords(
  243. f"http://{url}/rtp/{ip}", blacklist))
  244. ]
  245. info_result[name] = info_list
  246. return info_result
  247. def get_results_from_soup(soup, name):
  248. """
  249. Get the results from the soup
  250. """
  251. results = []
  252. if not soup.descendants:
  253. return results
  254. for element in soup.descendants:
  255. if isinstance(element, NavigableString):
  256. text = element.get_text(strip=True)
  257. url = get_channel_url(text)
  258. if url and not any(item[0] == url for item in results):
  259. url_element = soup.find(lambda tag: tag.get_text(strip=True) == url)
  260. if url_element:
  261. name_element = url_element.find_previous_sibling()
  262. if name_element:
  263. channel_name = name_element.get_text(strip=True)
  264. if channel_name_is_equal(name, channel_name):
  265. info_element = url_element.find_next_sibling()
  266. date, resolution = get_channel_info(
  267. info_element.get_text(strip=True)
  268. )
  269. results.append((url, date, resolution))
  270. return results
  271. def get_results_from_multicast_soup(soup, hotel=False):
  272. """
  273. Get the results from the multicast soup
  274. """
  275. results = []
  276. if not soup.descendants:
  277. return results
  278. for element in soup.descendants:
  279. if isinstance(element, NavigableString):
  280. text = element.strip()
  281. if "失效" in text:
  282. continue
  283. url = get_channel_url(text)
  284. if url and not any(item["url"] == url for item in results):
  285. url_element = soup.find(lambda tag: tag.get_text(strip=True) == url)
  286. if not url_element:
  287. continue
  288. parent_element = url_element.find_parent()
  289. info_element = parent_element.find_all(recursive=False)[-1]
  290. if not info_element:
  291. continue
  292. info_text = info_element.get_text(strip=True)
  293. if "上线" in info_text and " " in info_text:
  294. date, region, type = get_multicast_channel_info(info_text)
  295. if hotel and "酒店" not in region:
  296. continue
  297. results.append(
  298. {
  299. "url": url,
  300. "date": date,
  301. "region": region,
  302. "type": type,
  303. }
  304. )
  305. return results
  306. def get_results_from_soup_requests(soup, name):
  307. """
  308. Get the results from the soup by requests
  309. """
  310. results = []
  311. elements = soup.find_all("div", class_="resultplus") if soup else []
  312. for element in elements:
  313. name_element = element.find("div", class_="channel")
  314. if name_element:
  315. channel_name = name_element.get_text(strip=True)
  316. if channel_name_is_equal(name, channel_name):
  317. text_list = get_element_child_text_list(element, "div")
  318. url = date = resolution = None
  319. for text in text_list:
  320. text_url = get_channel_url(text)
  321. if text_url:
  322. url = text_url
  323. if " " in text:
  324. text_info = get_channel_info(text)
  325. date, resolution = text_info
  326. if url:
  327. results.append((url, date, resolution))
  328. return results
  329. def get_results_from_multicast_soup_requests(soup, hotel=False):
  330. """
  331. Get the results from the multicast soup by requests
  332. """
  333. results = []
  334. if not soup:
  335. return results
  336. elements = soup.find_all("div", class_="result")
  337. for element in elements:
  338. name_element = element.find("div", class_="channel")
  339. if not name_element:
  340. continue
  341. text_list = get_element_child_text_list(element, "div")
  342. url, date, region, type = None, None, None, None
  343. valid = True
  344. for text in text_list:
  345. if "失效" in text:
  346. valid = False
  347. break
  348. text_url = get_channel_url(text)
  349. if text_url:
  350. url = text_url
  351. if url and "上线" in text and " " in text:
  352. date, region, type = get_multicast_channel_info(text)
  353. if url and valid:
  354. if hotel and "酒店" not in region:
  355. continue
  356. results.append({"url": url, "date": date, "region": region, "type": type})
  357. return results
  358. def get_channel_url(text):
  359. """
  360. Get the url from text
  361. """
  362. url = None
  363. url_search = re.search(
  364. constants.url_pattern,
  365. text,
  366. )
  367. if url_search:
  368. url = url_search.group()
  369. return url
  370. def get_channel_info(text):
  371. """
  372. Get the channel info from text
  373. """
  374. date, resolution = None, None
  375. if text:
  376. date, resolution = (
  377. (text.partition(" ")[0] if text.partition(" ")[0] else None),
  378. (
  379. text.partition(" ")[2].partition("•")[2]
  380. if text.partition(" ")[2].partition("•")[2]
  381. else None
  382. ),
  383. )
  384. return date, resolution
  385. def get_multicast_channel_info(text):
  386. """
  387. Get the multicast channel info from text
  388. """
  389. date, region, type = None, None, None
  390. if text:
  391. text_split = text.split(" ")
  392. filtered_data = list(filter(lambda x: x.strip() != "", text_split))
  393. if filtered_data and len(filtered_data) == 4:
  394. date = filtered_data[0]
  395. region = filtered_data[2]
  396. type = filtered_data[3]
  397. return date, region, type
  398. def init_info_data(data, cate, name):
  399. """
  400. Init channel info data
  401. """
  402. if data.get(cate) is None:
  403. data[cate] = {}
  404. if data[cate].get(name) is None:
  405. data[cate][name] = []
  406. def append_data_to_info_data(info_data, cate, name, data, origin=None, check=True, whitelist=None, blacklist=None):
  407. """
  408. Append channel data to total info data
  409. """
  410. init_info_data(info_data, cate, name)
  411. urls = set([x[0].partition("$")[0] for x in info_data[cate][name] if x[0]])
  412. url_hosts = set([get_url_host(url) for url in urls])
  413. for item in data:
  414. try:
  415. url, date, resolution, *rest = item
  416. url_origin = origin or (rest[0] if rest else None)
  417. if not url_origin:
  418. continue
  419. if url:
  420. url_partition = url.partition("$")
  421. pure_url = url_partition[0]
  422. url_host = get_url_host(url_partition[0])
  423. url_info = url_partition[2]
  424. white_info = url_info and url_info.startswith("!")
  425. if not white_info:
  426. if pure_url in urls:
  427. continue
  428. if url_host in url_hosts:
  429. for p_url in urls:
  430. if get_url_host(p_url) == url_host and len(p_url) < len(pure_url):
  431. urls.remove(p_url)
  432. urls.add(pure_url)
  433. for index, info in enumerate(info_data[cate][name]):
  434. if info[0] and get_url_host(info[0]) == url_host:
  435. info_data[cate][name][index] = (url, date, resolution, url_origin)
  436. break
  437. break
  438. continue
  439. if white_info or (whitelist and check_url_by_keywords(url, whitelist)):
  440. url_origin = "whitelist"
  441. if (
  442. url_origin == "whitelist"
  443. or (not check)
  444. or (
  445. check and check_url_ipv_type(pure_url) and not check_url_by_keywords(url, blacklist))
  446. ):
  447. info_data[cate][name].append((url, date, resolution, url_origin))
  448. urls.add(pure_url)
  449. url_hosts.add(url_host)
  450. except:
  451. continue
  452. def get_origin_method_name(method):
  453. """
  454. Get the origin method name
  455. """
  456. return "hotel" if method.startswith("hotel_") else method
  457. def append_old_data_to_info_data(info_data, cate, name, data, whitelist=None, blacklist=None):
  458. """
  459. Append history and local channel data to total info data
  460. """
  461. append_data_to_info_data(
  462. info_data,
  463. cate,
  464. name,
  465. data,
  466. whitelist=whitelist,
  467. blacklist=blacklist
  468. )
  469. local_len = len([x for x in data if x[3] in ["local", 'whitelist']])
  470. print("History:", len(data) - local_len, end=", ")
  471. print("Local:", local_len, end=", ")
  472. def append_total_data(
  473. items,
  474. names,
  475. data,
  476. hotel_fofa_result=None,
  477. multicast_result=None,
  478. hotel_foodie_result=None,
  479. subscribe_result=None,
  480. online_search_result=None,
  481. ):
  482. """
  483. Append all method data to total info data
  484. """
  485. total_result = [
  486. ("hotel_fofa", hotel_fofa_result),
  487. ("multicast", multicast_result),
  488. ("hotel_foodie", hotel_foodie_result),
  489. ("subscribe", subscribe_result),
  490. ("online_search", online_search_result),
  491. ]
  492. whitelist = get_urls_from_file(constants.whitelist_path)
  493. blacklist = get_urls_from_file(constants.blacklist_path)
  494. for cate, channel_obj in items:
  495. for name, old_info_list in channel_obj.items():
  496. print(f"{name}:", end=" ")
  497. if old_info_list and (config.open_history or config.open_local):
  498. append_old_data_to_info_data(data, cate, name, old_info_list, whitelist=whitelist, blacklist=blacklist)
  499. for method, result in total_result:
  500. if config.open_method[method]:
  501. origin_method = get_origin_method_name(method)
  502. if not origin_method:
  503. continue
  504. name_results = get_channel_results_by_name(name, result)
  505. append_data_to_info_data(
  506. data, cate, name, name_results, origin=origin_method, whitelist=whitelist, blacklist=blacklist
  507. )
  508. print(f"{method.capitalize()}:", len(name_results), end=", ")
  509. print(
  510. "Total:",
  511. len(data.get(cate, {}).get(name, [])),
  512. )
  513. if config.open_keep_all:
  514. extra_cate = "📥其它频道"
  515. for method, result in total_result:
  516. if config.open_method[method]:
  517. origin_method = get_origin_method_name(method)
  518. if not origin_method:
  519. continue
  520. for name, urls in result.items():
  521. if name in names:
  522. continue
  523. print(f"{name}:", end=" ")
  524. if config.open_history or config.open_local:
  525. old_info_list = channel_obj.get(name, [])
  526. if old_info_list:
  527. append_old_data_to_info_data(
  528. data, extra_cate, name, old_info_list
  529. )
  530. append_data_to_info_data(
  531. data, extra_cate, name, urls, origin=origin_method, whitelist=whitelist, blacklist=blacklist
  532. )
  533. print(name, f"{method.capitalize()}:", len(urls), end=", ")
  534. print(
  535. "Total:",
  536. len(data.get(cate, {}).get(name, [])),
  537. )
  538. async def process_sort_channel_list(data, ipv6=False, callback=None):
  539. """
  540. Process the sort channel list
  541. """
  542. ipv6_proxy = None if (not config.open_ipv6 or ipv6) else constants.ipv6_proxy
  543. open_filter_resolution = config.open_filter_resolution
  544. min_resolution = config.min_resolution_value
  545. get_resolution = open_filter_resolution and check_ffmpeg_installed_status()
  546. sort_timeout = config.sort_timeout
  547. need_sort_data = copy.deepcopy(data)
  548. process_nested_dict(need_sort_data, seen={}, flag=r"cache:(.*)", force_str="!")
  549. result = {}
  550. semaphore = asyncio.Semaphore(10)
  551. async def limited_get_speed(info, ipv6_proxy, filter_resolution, min_resolution, timeout, callback):
  552. async with semaphore:
  553. return await get_speed(info[0], ipv6_proxy=ipv6_proxy, filter_resolution=filter_resolution,
  554. min_resolution=min_resolution, timeout=timeout,
  555. callback=callback)
  556. tasks = [
  557. asyncio.create_task(
  558. limited_get_speed(
  559. info,
  560. ipv6_proxy=ipv6_proxy,
  561. filter_resolution=get_resolution,
  562. min_resolution=min_resolution,
  563. timeout=sort_timeout,
  564. callback=callback,
  565. )
  566. )
  567. for channel_obj in need_sort_data.values()
  568. for info_list in channel_obj.values()
  569. for info in info_list
  570. ]
  571. await asyncio.gather(*tasks)
  572. logger = get_logger(constants.sort_log_path, level=INFO, init=True)
  573. open_supply = config.open_supply
  574. open_filter_speed = config.open_filter_speed
  575. min_speed = config.min_speed
  576. for cate, obj in data.items():
  577. for name, info_list in obj.items():
  578. info_list = sort_urls(name, info_list, supply=open_supply, filter_speed=open_filter_speed,
  579. min_speed=min_speed, filter_resolution=open_filter_resolution,
  580. min_resolution=min_resolution, logger=logger)
  581. append_data_to_info_data(
  582. result,
  583. cate,
  584. name,
  585. info_list,
  586. check=False,
  587. )
  588. logger.handlers.clear()
  589. return result
  590. def write_channel_to_file(data, ipv6=False, callback=None):
  591. """
  592. Write channel to file
  593. """
  594. try:
  595. path = constants.result_path
  596. if not os.path.exists("output"):
  597. os.makedirs("output")
  598. no_result_name = []
  599. open_empty_category = config.open_empty_category
  600. ipv_type_prefer = list(config.ipv_type_prefer)
  601. if any(pref in ipv_type_prefer for pref in ["自动", "auto"]):
  602. ipv_type_prefer = ["ipv6", "ipv4"] if ipv6 else ["ipv4", "ipv6"]
  603. origin_type_prefer = config.origin_type_prefer
  604. first_cate = True
  605. content = ""
  606. for cate, channel_obj in data.items():
  607. print(f"\n{cate}:", end=" ")
  608. content += f"{'\n\n' if not first_cate else ''}{cate},#genre#"
  609. first_cate = False
  610. channel_obj_keys = channel_obj.keys()
  611. names_len = len(list(channel_obj_keys))
  612. for i, name in enumerate(channel_obj_keys):
  613. info_list = data.get(cate, {}).get(name, [])
  614. channel_urls = get_total_urls(info_list, ipv_type_prefer, origin_type_prefer)
  615. end_char = ", " if i < names_len - 1 else ""
  616. print(f"{name}:", len(channel_urls), end=end_char)
  617. if not channel_urls:
  618. if open_empty_category:
  619. no_result_name.append(name)
  620. continue
  621. for url in channel_urls:
  622. content += f"\n{name},{url}"
  623. if callback:
  624. callback()
  625. print()
  626. if open_empty_category and no_result_name:
  627. print("\n🈳 No result channel name:")
  628. content += "\n\n🈳无结果频道,#genre#"
  629. for i, name in enumerate(no_result_name):
  630. end_char = ", " if i < len(no_result_name) - 1 else ""
  631. print(name, end=end_char)
  632. content += f"\n{name},url"
  633. print()
  634. if config.open_update_time:
  635. update_time_url = next(
  636. (urls[0] for channel_obj in data.values()
  637. for info_list in channel_obj.values()
  638. if (urls := get_total_urls(info_list, ipv_type_prefer, origin_type_prefer))),
  639. "url"
  640. )
  641. if config.update_time_position == "top":
  642. content = f"🕘️更新时间,#genre#\n{get_datetime_now()},{update_time_url}\n\n{content}"
  643. else:
  644. content += f"\n\n🕘️更新时间,#genre#\n{get_datetime_now()},{update_time_url}"
  645. with open(path, "w", encoding="utf-8") as f:
  646. f.write(content)
  647. except Exception as e:
  648. print(f"❌ Write channel to file failed: {e}")
  649. def get_multicast_fofa_search_org(region, type):
  650. """
  651. Get the fofa search organization for multicast
  652. """
  653. org = None
  654. if region == "北京" and type == "联通":
  655. org = "China Unicom Beijing Province Network"
  656. elif type == "联通":
  657. org = "CHINA UNICOM China169 Backbone"
  658. elif type == "电信":
  659. org = "Chinanet"
  660. elif type == "移动":
  661. org = "China Mobile communications corporation"
  662. return org
  663. def get_multicast_fofa_search_urls():
  664. """
  665. Get the fofa search urls for multicast
  666. """
  667. rtp_file_names = []
  668. for filename in os.listdir(resource_path("config/rtp")):
  669. if filename.endswith(".txt") and "_" in filename:
  670. filename = filename.replace(".txt", "")
  671. rtp_file_names.append(filename)
  672. region_list = config.multicast_region_list
  673. region_type_list = [
  674. (parts[0], parts[1])
  675. for name in rtp_file_names
  676. if (parts := name.partition("_"))[0] in region_list
  677. or "all" in region_list
  678. or "ALL" in region_list
  679. or "全部" in region_list
  680. ]
  681. search_urls = []
  682. for region, type in region_type_list:
  683. search_url = "https://fofa.info/result?qbase64="
  684. search_txt = f'"udpxy" && country="CN" && region="{region}" && org="{get_multicast_fofa_search_org(region, type)}"'
  685. bytes_string = search_txt.encode("utf-8")
  686. search_txt = base64.b64encode(bytes_string).decode("utf-8")
  687. search_url += search_txt
  688. search_urls.append((search_url, region, type))
  689. return search_urls
  690. def get_channel_data_cache_with_compare(data, new_data):
  691. """
  692. Get channel data with cache compare new data
  693. """
  694. for cate, obj in new_data.items():
  695. for name, url_info in obj.items():
  696. if url_info and cate in data and name in data[cate]:
  697. new_urls = {
  698. new_url.partition("$")[0]: new_resolution
  699. for new_url, _, new_resolution, _ in url_info
  700. }
  701. updated_data = []
  702. for info in data[cate][name]:
  703. url, date, resolution, origin = info
  704. base_url = url.partition("$")[0]
  705. if base_url in new_urls:
  706. resolution = new_urls[base_url]
  707. updated_data.append((url, date, resolution, origin))
  708. data[cate][name] = updated_data
  709. def format_channel_url_info(data):
  710. """
  711. Format channel url info, remove cache, add resolution to url
  712. """
  713. for obj in data.values():
  714. for url_info in obj.values():
  715. for i, (url, date, resolution, origin) in enumerate(url_info):
  716. url = remove_cache_info(url)
  717. url_info[i] = (url, date, resolution, origin)