-
Notifications
You must be signed in to change notification settings - Fork 7
/
main.py
6377 lines (5536 loc) · 235 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import abc
import asyncio
import base64
import secrets
import string
import concurrent
import ipaddress
import json
import math
import os
import queue
import re
# import atexit
# import shutil
# import signal
# import subprocess
# import uuid
from functools import wraps
from xml.etree.ElementTree import fromstring
# import psutil
# import subprocess
import threading
import hashlib
import urllib
import zipfile
from concurrent.futures import ThreadPoolExecutor
# import execjs
import m3u8 as m3u8
from zhconv import convert
import aiohttp
import aiofiles
import redis
import requests
import time
from urllib.parse import urlparse
# import yaml
from flask import Flask, jsonify, request, send_file, render_template, send_from_directory, \
after_this_request, redirect
import chardet
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from multidict import CIMultiDict
app = Flask(__name__)
app.config['TEMPLATES_AUTO_RELOAD'] = True # 实时更新模板文件
app.config['MAX_CONTENT_LENGTH'] = 1000 * 1024 * 1024 # 上传文件最大限制1000 MB
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 # 静态文件缓存时间,默认值为 12 小时。可以通过将其设为 0 来禁止浏览器缓存静态文件
app.config['JSONIFY_TIMEOUT'] = 6000 # 设置响应超时时间为 6000 秒
r = redis.Redis(host='localhost', port=22772)
##########################################################redis key#############################################
REDIS_KEY_M3U_LINK = "m3ulink"
REDIS_KEY_M3U_DATA = "localm3u"
REDIS_KEY_M3U_EPG_LOGO = "m3uepglogo"
REDIS_KEY_M3U_EPG_GROUP = "m3uepggroup"
# 白名单下载链接
REDIS_KEY_WHITELIST_LINK = "whitelistlink"
# 白名单adguardhome
REDIS_KEY_WHITELIST_DATA = "whitelistdata"
# 白名单dnsmasq
REDIS_KEY_WHITELIST_DATA_DNSMASQ = "whitelistdatadnsmasq"
# 黑名单下载链接
REDIS_KEY_BLACKLIST_LINK = "blacklistlink"
# 黑名单openclash-fallback-filter-domain
REDIS_KEY_BLACKLIST_OPENCLASH_FALLBACK_FILTER_DOMAIN_DATA = "blacklistopfallbackfilterdomaindata"
# 黑名单blackdomain
REDIS_KEY_BLACKLIST_DOMAIN_DATA = "blackdomain"
# 白名单中国大陆IPV4下载链接
REDIS_KEY_WHITELIST_IPV4_LINK = "whitelistipv4link"
# 白名单中国大陆IPV4下载数据
REDIS_KEY_WHITELIST_IPV4_DATA = "whitelistipv4data"
# 白名单中国大陆IPV6下载链接
REDIS_KEY_WHITELIST_IPV6_LINK = "whitelistipv6link"
# 白名单中国大陆IPV6下载数据
REDIS_KEY_WHITELIST_IPV6_DATA = "whitelistipv6data"
# 密码本下载链接
REDIS_KEY_PASSWORD_LINK = "passwordlink"
# 节点下载链接
REDIS_KEY_PROXIES_LINK = "proxieslink"
# 代理类型
REDIS_KEY_PROXIES_TYPE = "proxiestype"
# 代理转换配置模板(本地组+网络组):url,name
REDIS_KEY_PROXIES_MODEL = "proxiesmodel"
# 代理转换配置选择的模板:name
REDIS_KEY_PROXIES_MODEL_CHOSEN = "proxiesmodelchosen"
# 代理转换服务器订阅:url,name
REDIS_KEY_PROXIES_SERVER = "proxiesserver"
# 代理转换选择的服务器订阅:url,name
REDIS_KEY_PROXIES_SERVER_CHOSEN = "proxiesserverchosen"
# m3u白名单:关键字,分组
REDIS_KEY_M3U_WHITELIST = "m3uwhitelist"
# m3u白名单:分组,排名
REDIS_KEY_M3U_WHITELIST_RANK = "m3uwhitelistrank"
# m3u黑名单:关键字,
REDIS_KEY_M3U_BLACKLIST = "m3ublacklist"
# 简易DNS域名白名单
REDIS_KEY_DNS_SIMPLE_WHITELIST = "dnssimplewhitelist"
# 简易DNS域名黑名单
REDIS_KEY_DNS_SIMPLE_BLACKLIST = "dnssimpleblacklist"
# 加密订阅密码历史记录,包括当前密码组
REDIS_KEY_SECRET_SUBSCRIBE_HISTORY_PASS = "secretSubscribeHistoryPass"
# 加密订阅密码当前配置
REDIS_KEY_SECRET_PASS_NOW = 'secretpassnow'
redisKeySecretPassNow = {'m3u': '', 'whitelist': '', 'blacklist': '', 'ipv4': '', 'ipv6': '', 'proxy': ''}
# # gitee账号:用户名,仓库名字,path,access Token
REDIS_KEY_GITEE = 'redisKeyGitee'
redisKeyGitee = {'username': '', 'reponame': '', 'path': '', 'accesstoken': ''}
# # github账号:用户名,仓库名字,path,access Token
REDIS_KEY_GITHUB = 'redisKeyGithub'
redisKeyGithub = {'username': '', 'reponame': '', 'path': '', 'accesstoken': ''}
# # webdav账号:ip,端口,用户名,密码,路径,协议(http/https)
REDIS_KEY_WEBDAV = 'redisKeyWebdav'
redisKeyWebDav = {'ip': '', 'port': '', 'username': '', 'password': '', 'path': '', 'agreement': ''}
REDIS_KEY_FUNCTION_DICT = "functiondict"
# 功能开关字典
function_dict = {}
# 白名单三段字典:顶级域名,一级域名长度,一级域名首位,一级域名数据
REDIS_KEY_WHITELIST_DATA_SP = "whitelistdatasp"
# 白名单三段字典:顶级域名,一级域名长度,一级域名首位,一级域名数据
whitelistSpData = {}
# 黑名单三段字典:顶级域名,一级域名长度,一级域名首位,一级域名数据
REDIS_KEY_BLACKLIST_DATA_SP = "blacklistdatasp"
# 黑名单三段字典:顶级域名,一级域名长度,一级域名首位,一级域名数据
blacklistSpData = {}
REDIS_KEY_FILE_NAME = "redisKeyFileName"
# 订阅文件名字字典,命名自由化
file_name_dict = {'allM3u': 'allM3u', 'allM3uSecret': 'allM3uSecret', 'aliveM3u': 'aliveM3u', 'healthM3u': 'healthM3u',
'tvDomainForAdguardhome': 'tvDomainForAdguardhome',
'tvDomainForAdguardhomeSecret': 'tvDomainForAdguardhomeSecret',
'whiteListDnsmasq': 'whiteListDnsmasq', 'whiteListDnsmasqSecret': 'whiteListDnsmasqSecret',
'whiteListDomian': 'whiteListDomian',
'whiteListDomianSecret': 'whiteListDomianSecret',
'openclashFallbackFilterDomain': 'openclashFallbackFilterDomain',
'openclashFallbackFilterDomainSecret': 'openclashFallbackFilterDomainSecret',
'blackListDomain': 'blackListDomain',
'blackListDomainSecret': 'blackListDomainSecret', 'ipv4': 'ipv4', 'ipv4Secret': 'ipv4Secret',
'ipv6': 'ipv6',
'ipv6Secret': 'ipv6Secret', 'proxyConfig': 'proxyConfig', 'proxyConfigSecret': 'proxyConfigSecret',
'whitelistDirectRule': 'whitelistDirectRule', 'blacklistProxyRule': 'blacklistProxyRule',
'simpleOpenclashFallBackFilterDomain': 'simpleOpenclashFallBackFilterDomain',
'simpleblacklistProxyRule': 'simpleblacklistProxyRule', 'simpleDnsmasq': 'simpleDnsmasq',
'simplewhitelistProxyRule': 'simplewhitelistProxyRule', 'minTimeout': '5', 'maxTimeout': '30',
'maxTimeoutIgnoreLastUUID': '300', 'maxTimeoutIgnoreAllUUID': '3600', 'maxTimeoutTsSeen': '300'
, 'maxTimeoutTsFree': '300', 'maxTimeoutM3u8Free': '300', 'audioType': 'copy', 'ffmpegThread': '2',
'usernameSys': 'admin', 'passwordSys': 'password'}
# 单独导入导出使用一个配置,需特殊处理:{{url:{pass,name}}}
# 下载网络配置并且加密后上传:url+加密密钥+加密文件名字
REDIS_KEY_DOWNLOAD_AND_SECRET_UPLOAD_URL_PASSWORD_NAME = 'downloadAndSecretUploadUrlPasswordAndName'
downAndSecUploadUrlPassAndName = {}
# 下载加密网络配置并且解密还原成源文件:加密url+加密密钥+源文件名字
REDIS_KEY_DOWNLOAD_AND_DESECRET_URL_PASSWORD_NAME = 'downloadAndDeSecretUrlPasswordAndName'
downAndDeSecUrlPassAndName = {}
# youtube直播源
REDIS_KEY_YOUTUBE = 'redisKeyYoutube'
# youtube直播源地址,频道名字
redisKeyYoutube = {}
# youtube真实m3u8地址
REDIS_KEY_YOUTUBE_M3U = 'redisKeyYoutubeM3u'
# youtube频道名字,真实m3u8地址
redisKeyYoutubeM3u = {}
# bilibili直播源
REDIS_KEY_BILIBILI = 'redisKeyBilibili'
# bilibili直播源地址,频道名字
redisKeyBilili = {}
# bilibili真实m3u8地址
REDIS_KEY_BILIBILI_M3U = 'redisKeyBilibiliM3u'
# bilibili频道名字,真实m3u8地址
redisKeyBililiM3u = {}
# huya直播源
REDIS_KEY_HUYA = 'redisKeyHuya'
# huya直播源地址,频道名字
redisKeyHuya = {}
# huya真实m3u8地址
REDIS_KEY_HUYA_M3U = 'redisKeyHuyaM3u'
# huya频道名字,真实m3u8地址
redisKeyHuyaM3u = {}
# YY直播源
REDIS_KEY_YY = 'redisKeyYY'
# YY直播源地址,频道名字
redisKeyYY = {}
# YY真实m3u8地址
REDIS_KEY_YY_M3U = 'redisKeyYYM3u'
# YY频道名字,真实m3u8地址
redisKeyYYM3u = {}
port_live = 22771
NORMAL_REDIS_KEY = 'normalRedisKey'
# 全部有redis备份字典key-普通redis结构,重要且数据量比较少的
allListArr = [REDIS_KEY_M3U_LINK, REDIS_KEY_WHITELIST_LINK, REDIS_KEY_BLACKLIST_LINK, REDIS_KEY_WHITELIST_IPV4_LINK,
REDIS_KEY_WHITELIST_IPV6_LINK, REDIS_KEY_PASSWORD_LINK, REDIS_KEY_PROXIES_LINK, REDIS_KEY_PROXIES_TYPE,
REDIS_KEY_PROXIES_MODEL, REDIS_KEY_PROXIES_MODEL_CHOSEN, REDIS_KEY_PROXIES_SERVER,
REDIS_KEY_PROXIES_SERVER_CHOSEN, REDIS_KEY_GITEE, REDIS_KEY_GITHUB,
REDIS_KEY_SECRET_PASS_NOW, REDIS_KEY_WEBDAV, REDIS_KEY_FILE_NAME,
REDIS_KEY_FUNCTION_DICT, REDIS_KEY_SECRET_SUBSCRIBE_HISTORY_PASS]
# 数据巨大的redis配置,一键导出时单独导出每个配置
hugeDataList = [REDIS_KEY_BILIBILI, REDIS_KEY_DNS_SIMPLE_WHITELIST, REDIS_KEY_DNS_SIMPLE_BLACKLIST, REDIS_KEY_YOUTUBE,
REDIS_KEY_M3U_WHITELIST_RANK, REDIS_KEY_M3U_BLACKLIST, REDIS_KEY_M3U_WHITELIST, REDIS_KEY_HUYA,
REDIS_KEY_YY]
SPECIAL_REDIS_KEY = 'specialRedisKey'
specialRedisKey = [REDIS_KEY_DOWNLOAD_AND_SECRET_UPLOAD_URL_PASSWORD_NAME,
REDIS_KEY_DOWNLOAD_AND_DESECRET_URL_PASSWORD_NAME]
# Adguardhome屏蔽前缀
BLACKLIST_ADGUARDHOME_FORMATION = "0.0.0.0 "
# dnsmasq白名单前缀
BLACKLIST_DNSMASQ_FORMATION_LEFT = "server=/"
# dnsmasq白名单后缀
BLACKLIST_DNSMASQ_FORMATION_right = "/114.114.114.114"
# 用于匹配纯粹域名的正则表达式
domain_regex = r'^[a-zA-Z0-9]+([\-\.]{1}[a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$'
# 用于匹配泛化匹配的域名规则的正则表达式
wildcard_regex = r'^\*\.[a-zA-Z0-9]+([\-\.]{1}[a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$'
# 用于匹配泛化匹配的域名规则的正则表达式
wildcard_regex2 = r'^\+\.[a-zA-Z0-9]+([\-\.]{1}[a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$'
# 用于匹配dnsmasq白名单格式
pattern = r'^server=\/[a-zA-Z0-9.-]+\/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|[a-zA-Z0-9.-]+)$'
OPENCLASH_FALLBACK_FILTER_DOMAIN_LEFT = " - \""
OPENCLASH_FALLBACK_FILTER_DOMAIN_RIGHT = "\""
# name,logo
CHANNEL_LOGO = {}
# name,group
CHANNEL_GROUP = {}
defalutname = "佚名"
# 订阅模板转换服务器地址API
URL = "http://192.168.5.1:25500/sub"
# m3u下载处理时提取直播源域名在adguardhome放行,只放行m3u域名不管分流
white_list_adguardhome = {}
# 白名单总缓存,数据大量,是全部规则缓存
white_list_nameserver_policy = {}
# DOMAIN-SUFFIX,域名,DIRECT--以该域名结尾的全部直连
white_list_Direct_Rules = {}
# 黑名单总缓存,数据大量,是全部规则缓存
black_list_nameserver_policy = {}
# DOMAIN-SUFFIX,域名,DIRECT--以该域名结尾的全部代理
black_list_Proxy_Rules = {}
# 下载的域名白名单存储到redis服务器里
REDIS_KEY_WHITE_DOMAINS = "whitedomains"
# 下载的域名黑名单存储到redis服务器里
REDIS_KEY_BLACK_DOMAINS = "blackdomains"
# 0-数据未更新 1-数据已更新 max-所有服务器都更新完毕(有max个服务器做负载均衡)
REDIS_KEY_UPDATE_WHITE_LIST_FLAG = "updatewhitelistflag"
REDIS_KEY_UPDATE_BLACK_LIST_FLAG = "updateblacklistflag"
REDIS_KEY_UPDATE_IPV4_LIST_FLAG = "updateipv4listflag"
REDIS_KEY_UPDATE_THREAD_NUM_FLAG = "updatethreadnumflag"
REDIS_KEY_UPDATE_CHINA_DNS_SERVER_FLAG = "updatechinadnsserverflag"
REDIS_KEY_UPDATE_CHINA_DNS_PORT_FLAG = "updatechinadnsportflag"
REDIS_KEY_UPDATE_EXTRA_DNS_SERVER_FLAG = "updateextradnsserverflag"
REDIS_KEY_UPDATE_EXTRA_DNS_PORT_FLAG = "updateextradnsportflag"
REDIS_KEY_UPDATE_SIMPLE_WHITE_LIST_FLAG = "updatesimplewhitelistflag"
REDIS_KEY_UPDATE_SIMPLE_BLACK_LIST_FLAG = "updatesimpleblacklistflag"
REDIS_KEY_UPDATE_WHITE_LIST_SP_FLAG = "updatewhitelistspflag"
REDIS_KEY_UPDATE_BLACK_LIST_SP_FLAG = "updateblacklistspflag"
REDIS_KEY_THREADS = "threadsnum"
threadsNum = {REDIS_KEY_THREADS: 1000}
REDIS_KEY_CHINA_DNS_SERVER = "chinadnsserver"
chinadnsserver = {REDIS_KEY_CHINA_DNS_SERVER: ""}
REDIS_KEY_CHINA_DNS_PORT = "chinadnsport"
chinadnsport = {REDIS_KEY_CHINA_DNS_PORT: 0}
REDIS_KEY_EXTRA_DNS_SERVER = "extradnsserver"
extradnsserver = {REDIS_KEY_EXTRA_DNS_SERVER: ""}
REDIS_KEY_EXTRA_DNS_PORT = "extradnsport"
extradnsport = {REDIS_KEY_EXTRA_DNS_PORT: 0}
REDIS_KEY_DNS_QUERY_NUM = "dnsquerynum"
dnsquerynum = {REDIS_KEY_DNS_QUERY_NUM: 0}
REDIS_KEY_DNS_TIMEOUT = "dnstimeout"
dnstimeout = {REDIS_KEY_DNS_TIMEOUT: 0}
REDIS_KEY_IP = "ip"
ip = {REDIS_KEY_IP: ""}
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
return authenticate()
return f(*args, **kwargs)
return decorated
def check_auth(username, password):
usernameSys = getFileNameByTagName('usernameSys')
passwordSys = getFileNameByTagName('passwordSys')
if username == usernameSys and password == passwordSys:
return True
# 进行用户名和密码验证的逻辑
# 如果验证通过,返回True;否则返回False
return False
def authenticate():
message = {'message': "Authentication failed."}
resp = jsonify(message)
resp.status_code = 401
resp.headers['WWW-Authenticate'] = 'Basic realm="Login Required"'
return resp
# 针对已登录用户的受保护视图函数
@app.route('/')
@requires_auth
def index():
return render_template('index.html')
# 公共路径,放的全部是加密文件,在公共服务器开放这个路径访问
public_path = '/app/ini/'
# 隐私路径,放的全部是明文文件,在公共服务器不要开放这个路径访问
secret_path = '/app/secret/'
# 路由隐藏真实路径-公共路径
@app.route('/url/<path:filename>')
def serve_files(filename):
root_dir = public_path # 根目录
return send_from_directory(root_dir, filename, as_attachment=True)
# 路由隐藏真实路径-隐私路径
@app.route('/secret/<path:filename>')
def serve_files2(filename):
root_dir = secret_path # 根目录
return send_from_directory(root_dir, filename, as_attachment=True)
# 路由youtube
@app.route('/youtube/<path:filename>')
def serve_files3(filename):
id = filename.split('.')[0]
url = redisKeyYoutubeM3u[id]
@after_this_request
def add_header(response):
response.headers['Cache-Control'] = 'public, max-age=3600'
return response
return redirect(url)
# 路由bilibili
@app.route('/bilibili/<path:filename>')
def serve_files4(filename):
id = filename.split('.')[0]
url = redisKeyBililiM3u[id]
@after_this_request
def add_header(response):
response.headers['Cache-Control'] = 'public, max-age=3600'
return response
return redirect(url)
# 路由huya
@app.route('/huya/<path:filename>')
def serve_files5(filename):
id = filename.split('.')[0]
url = redisKeyHuyaM3u[id]
@after_this_request
def add_header(response):
response.headers['Cache-Control'] = 'public, max-age=3600'
return response
return redirect(url)
# 路由YY
@app.route('/YY/<path:filename>')
def serve_files6(filename):
id = filename.split('.')[0]
url = redisKeyYYM3u[id]
@after_this_request
def add_header(response):
response.headers['Cache-Control'] = 'public, max-age=3600'
return response
return redirect(url)
##############################################################bilibili############################################
async def pingM3u(session, value, real_dict, key, sem, mintimeout, maxTimeout):
try:
async with sem, session.get(value, timeout=mintimeout) as response:
if response.status == 200:
real_dict[key] = value
except asyncio.TimeoutError:
try:
async with sem, session.get(value, timeout=maxTimeout) as response:
if response.status == 200:
real_dict[key] = value
except Exception as e:
pass
except Exception as e:
pass
##########################################################redis数据库操作#############################################
# redis增加和修改
def redis_add(key, value):
r.set(key, value)
# redis查询
def redis_get(key):
return r.get(key)
# redis删除
def redis_del(key):
if r.exists(key):
r.delete(key)
# redis存储map字典,字典主键唯一,重复主键只会复写
def redis_add_map(key, my_dict):
r.hmset(key, my_dict)
# redis取出map字典
def redis_get_map(key):
redis_dict = r.hgetall(key)
python_dict = {key.decode('utf-8'): value.decode('utf-8') for key, value in redis_dict.items()}
return python_dict
# redis取出map字典key
def redis_get_map_keys(key):
redis_dict = r.hgetall(key)
array = [key for key in redis_dict.keys()]
return array, redis_dict
# redis删除map字典
def redis_del_map(key):
try:
r.delete(key)
except Exception as e:
pass
#########################################################通用工具区#################################################
# 上传订阅配置
def upload_json_base(rediskey, file_content):
try:
json_dict = json.loads(file_content)
if rediskey not in specialRedisKey:
if rediskey == REDIS_KEY_M3U_WHITELIST:
dict_final = {}
for key, value in json_dict.items():
dict_final[convert(key, 'zh-tw').lower()] = value
dict_final[convert(key, 'zh-cn').lower()] = value
redis_add_map(rediskey, dict_final)
importToReloadCache(rediskey, dict_final)
else:
redis_add_map(rediskey, json_dict)
importToReloadCache(rediskey, json_dict)
else:
importToReloadCacheForSpecial(rediskey, json_dict)
return jsonify({'success': True})
except Exception as e:
print("An error occurred: ", e)
return jsonify({'success': False})
def executeProxylist(sleepSecond):
while True:
# 执行方法
chaoronghe6()
if isOpenFunction('switch35'):
# 执行方法
chaoronghe24()
chaoronghe25()
chaoronghe26()
chaoronghe27()
print("直播源定时器执行成功")
time.sleep(sleepSecond)
def executeWhitelist(sleepSecond):
while True:
if isOpenFunction('switch26'):
# 执行方法
chaoronghe2()
if isOpenFunction('switch13'):
# 执行方法
chaoronghe3()
if isOpenFunction('switch27'):
# 执行方法
chaoronghe4()
if isOpenFunction('switch28'):
# 执行方法
chaoronghe5()
if isOpenFunction('switch33'):
# 执行方法
chaoronghe9()
if isOpenFunction('switch34'):
# 执行方法
chaoronghe10()
time.sleep(sleepSecond)
def toggle_m3u(functionId, value):
global function_dict
if functionId == 'switch24':
function_dict[functionId] = str(value)
redis_add_map(REDIS_KEY_FUNCTION_DICT, function_dict)
redis_add(REDIS_KEY_UPDATE_SIMPLE_WHITE_LIST_FLAG, 1)
elif functionId == 'switch25':
function_dict[functionId] = str(value)
redis_add_map(REDIS_KEY_FUNCTION_DICT, function_dict)
elif functionId == 'switch26':
function_dict[functionId] = str(value)
redis_add_map(REDIS_KEY_FUNCTION_DICT, function_dict)
elif functionId == 'switch13':
function_dict[functionId] = str(value)
redis_add_map(REDIS_KEY_FUNCTION_DICT, function_dict)
elif functionId == 'switch27':
function_dict[functionId] = str(value)
redis_add_map(REDIS_KEY_FUNCTION_DICT, function_dict)
elif functionId == 'switch28':
function_dict[functionId] = str(value)
redis_add_map(REDIS_KEY_FUNCTION_DICT, function_dict)
elif functionId == 'switch33':
function_dict[functionId] = str(value)
redis_add_map(REDIS_KEY_FUNCTION_DICT, function_dict)
elif functionId == 'switch34':
function_dict[functionId] = str(value)
redis_add_map(REDIS_KEY_FUNCTION_DICT, function_dict)
elif functionId == 'switch35':
function_dict[functionId] = str(value)
redis_add_map(REDIS_KEY_FUNCTION_DICT, function_dict)
def executeM3u(sleepSecond):
while True:
if isOpenFunction('switch25'):
# 执行方法
chaoronghe()
time.sleep(sleepSecond)
async def checkWriteHealthM3u(url):
# 关闭白名单直播源生成
if not isOpenFunction('switch5'):
return
name = tmp_url_tvg_name_dict.get(url)
if name:
path2 = f"{secret_path}{getFileNameByTagName('healthM3u')}.m3u"
async with aiofiles.open(path2, 'a', encoding='utf-8') as f: # 异步的方式写入内容
await f.write(f'{name}{url}\n')
del tmp_url_tvg_name_dict[url]
else:
return
async def download_url(session, url, value, sem):
try:
async with sem, session.get(url) as resp: # 使用asyncio.Semaphore限制TCP连接的数量
if resp.status == 200:
path = f"{secret_path}{getFileNameByTagName('aliveM3u')}.m3u"
async with aiofiles.open(path, 'a', encoding='utf-8') as f: # 异步的方式写入内容
await f.write(f'{value}{url}\n')
await checkWriteHealthM3u(url)
except aiohttp.ClientSSLError as ssl_err:
print(f"SSL Error occurred while downloading {url}: {ssl_err}")
except Exception as e:
print(f"Error occurred while downloading {url}: {e}")
async def asynctask(m3u_dict):
sem = asyncio.Semaphore(100) # 限制TCP连接的数量为100个
async with aiohttp.ClientSession() as session:
tasks = []
for url, value in m3u_dict.items():
task = asyncio.create_task(download_url(session, url, value, sem))
tasks.append(task)
await asyncio.gather(*tasks)
def copyAndRename(source_file):
with open(source_file, 'rb') as fsrc:
return fsrc.read()
def check_file(m3u_dict):
try:
"""
检查直播源文件是否存在且没有被占用
"""
# chaoronghe24()
# chaoronghe25()
oldChinaChannelDict = redis_get_map(REDIS_KET_TMP_CHINA_CHANNEL)
if oldChinaChannelDict:
tmp_url_tvg_name_dict.update(oldChinaChannelDict)
if len(tmp_url_tvg_name_dict.keys()) > 0:
redis_add_map(REDIS_KET_TMP_CHINA_CHANNEL, tmp_url_tvg_name_dict)
path = f"{secret_path}{getFileNameByTagName('aliveM3u')}.m3u"
if os.path.exists(path):
os.remove(path)
path3 = f"{secret_path}youtube.m3u"
path4 = f"{secret_path}bilibili.m3u"
path5 = f"{secret_path}huya.m3u"
path6 = f"{secret_path}YY.m3u"
source = ''
if os.path.exists(path3):
source += copyAndRename(path3).decode()
if os.path.exists(path4):
source += '\n'
source += copyAndRename(path4).decode()
if os.path.exists(path5):
source += '\n'
source += copyAndRename(path5).decode()
if os.path.exists(path6):
source += '\n'
source += copyAndRename(path6).decode()
with open(path, 'wb') as fdst:
fdst.write(source.encode('utf-8'))
path2 = f"{secret_path}{getFileNameByTagName('healthM3u')}.m3u"
if isOpenFunction('switch5'):
if os.path.exists(path2):
os.remove(path2)
source2 = ''
if os.path.exists(path3):
source2 += copyAndRename(path3).decode()
if os.path.exists(path4):
source2 += copyAndRename(path4).decode()
if os.path.exists(path5):
source2 += copyAndRename(path5).decode()
if os.path.exists(path6):
source2 += copyAndRename(path6).decode()
with open(path2, 'wb') as fdst:
fdst.write(source2.encode('utf-8'))
# 异步缓慢检测出有效链接
if len(m3u_dict) == 0:
return
asyncio.run(asynctask(m3u_dict))
except:
pass
def checkbytes(url):
if isinstance(url, bytes):
return decode_bytes(url).strip()
else:
return url
# 判断是否需要解密
def checkToDecrydecrypt(url, redis_dict, m3u_string):
password = redis_dict.get(url)
if password:
password = password.decode()
if password != "":
blankContent = decrypt(password, m3u_string)
return blankContent
return m3u_string
# 判断是否需要解密
def checkToDecrydecrypt3(url, redis_dict, m3u_string, filenameDict):
password = redis_dict.get(url)
if password:
if password != "":
blankContent = decrypt(password, m3u_string)
thread_write_bytes_to_file(filenameDict[url], checkbytes(blankContent).encode())
else:
if isinstance(m3u_string, bytes):
thread_write_bytes_to_file(filenameDict[url], m3u_string)
else:
thread_write_bytes_to_file(filenameDict[url], m3u_string.encode())
# 判断是否需要加密
def checkToDecrydecrypt2(url, redis_dict, m3u_string, filenameDict, secretNameDict, uploadGitee,
uploadGithub, uploadWebdav):
password = redis_dict.get(url)
if password:
if password != "":
secretContent = encrypt2(m3u_string, password)
secretFileName = secretNameDict[url]
thread_write_bytes_to_file(secretFileName, secretContent)
# 加密文件上传至gitee,
if uploadGitee:
task_queue.put(os.path.basename(secretFileName))
# 加密文件上传至github,
if uploadGithub:
task_queue_github.put(os.path.basename(secretFileName))
# 加密文件上传至webdav,
if uploadWebdav:
task_queue_webdav.put(os.path.basename(secretFileName))
if isinstance(m3u_string, bytes):
thread_write_bytes_to_file(filenameDict[url], m3u_string)
else:
thread_write_bytes_to_file(filenameDict[url], m3u_string.encode('utf-8'))
def fetch_url(url, redis_dict):
try:
response = requests.get(url, timeout=5)
response.raise_for_status() # 如果响应的状态码不是 200,则引发异常
# 源文件是二进制的AES加密文件,那么通过response.text转换成字符串后,数据可能会被破坏,从而无法还原回原始数据
m3u_string = response.content
# 加密文件检测和解码
m3u_string = checkToDecrydecrypt(url, redis_dict, m3u_string)
# 转换成字符串格式返回
m3u_string = checkbytes(m3u_string)
m3u_string += "\n"
# print(f"success to fetch URL: {url}")
return m3u_string
except requests.exceptions.SSLError:
response = requests.get(url, timeout=5, verify=False)
response.raise_for_status() # 如果响应的状态码不是 200,则引发异常
# 源文件是二进制的AES加密文件,那么通过response.text转换成字符串后,数据可能会被破坏,从而无法还原回原始数据
m3u_string = response.content
# 加密文件检测和解码
m3u_string = checkToDecrydecrypt(url, redis_dict, m3u_string)
# 转换成字符串格式返回
m3u_string = checkbytes(m3u_string)
return m3u_string
except requests.exceptions.Timeout:
print("timeout error, try to get data with longer timeout:" + url)
except requests.exceptions.RequestException as e:
url = url.decode('utf-8')
response = requests.get(url, timeout=5, verify=False)
response.raise_for_status() # 如果响应的状态码不是 200,则引发异常
# 源文件是二进制的AES加密文件,那么通过response.text转换成字符串后,数据可能会被破坏,从而无法还原回原始数据
m3u_string = response.content
# 加密文件检测和解码
m3u_string = checkToDecrydecrypt(url, redis_dict, m3u_string)
# 转换成字符串格式返回
m3u_string = checkbytes(m3u_string)
# print(f"success to fetch URL: {url}")
return m3u_string
# print("other error: " + url, e)
except:
pass
def write_to_file(data, file):
with open(file, 'a', encoding='utf-8') as f:
for k, v in data:
f.write(f'{v}{k}\n')
def worker(queue, file):
while True:
data = queue.get()
if data is None:
break
write_to_file(data, file)
queue.task_done()
def write_to_file2(data, file):
with open(file, 'a', ) as f:
for line in data:
f.write(f'{line}')
def worker2(queue, file):
while True:
data = queue.get()
if data is None:
break
write_to_file2(data, file)
queue.task_done()
def download_files(urls, redis_dict):
with concurrent.futures.ThreadPoolExecutor() as executor:
# 提交下载任务并获取future对象列表
future_to_url = {executor.submit(fetch_url, url, redis_dict): url for url in urls}
# 获取各个future对象的返回值并存储在字典中
results = []
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
result = future.result()
except Exception as exc:
print('%r generated an exception: %s' % (url, exc))
else:
results.append(result)
# 将结果按照原始URL列表的顺序排序并返回它们
return "".join(results)
def fetch_url2(url, passwordDict, filenameDict, secretNameDict, uploadGitee, uploadGithub, uploadWebdav):
try:
response = requests.get(url, timeout=5)
response.raise_for_status() # 如果响应的状态码不是 200,则引发异常
# 源文件是二进制的AES加密文件,那么通过response.text转换成字符串后,数据可能会被破坏,从而无法还原回原始数据
m3u_string = response.content
# 加密文件检测和解码
checkToDecrydecrypt2(url, passwordDict, m3u_string, filenameDict, secretNameDict, uploadGitee,
uploadGithub, uploadWebdav)
except requests.exceptions.SSLError:
response = requests.get(url, timeout=5, verify=False)
response.raise_for_status() # 如果响应的状态码不是 200,则引发异常
# 源文件是二进制的AES加密文件,那么通过response.text转换成字符串后,数据可能会被破坏,从而无法还原回原始数据
m3u_string = response.content
# 加密文件检测和解码
checkToDecrydecrypt2(url, passwordDict, m3u_string, filenameDict, secretNameDict, uploadGitee,
uploadGithub, uploadWebdav)
except requests.exceptions.Timeout:
print("timeout error, try to get data with longer timeout:" + url)
except requests.exceptions.RequestException as e:
url = url.decode('utf-8')
response = requests.get(url, timeout=5, verify=False)
response.raise_for_status() # 如果响应的状态码不是 200,则引发异常
# 源文件是二进制的AES加密文件,那么通过response.text转换成字符串后,数据可能会被破坏,从而无法还原回原始数据
m3u_string = response.content
# 加密文件检测和解码
checkToDecrydecrypt2(url, passwordDict, m3u_string, filenameDict, secretNameDict, uploadGitee,
uploadGithub, uploadWebdav)
except Exception as e:
print("fetch_url2 error:", e)
pass
def fetch_url3(url, passwordDict, filenameDict):
try:
response = requests.get(url, timeout=5)
response.raise_for_status() # 如果响应的状态码不是 200,则引发异常
# 源文件是二进制的AES加密文件,那么通过response.text转换成字符串后,数据可能会被破坏,从而无法还原回原始数据
m3u_string = response.content
# 加密文件检测和解码
checkToDecrydecrypt3(url, passwordDict, m3u_string, filenameDict)
except requests.exceptions.SSLError:
response = requests.get(url, timeout=5, verify=False)
response.raise_for_status() # 如果响应的状态码不是 200,则引发异常
# 源文件是二进制的AES加密文件,那么通过response.text转换成字符串后,数据可能会被破坏,从而无法还原回原始数据
m3u_string = response.content
# 加密文件检测和解码
checkToDecrydecrypt3(url, passwordDict, m3u_string, filenameDict)
except requests.exceptions.Timeout:
print("timeout error, try to get data with longer timeout:" + url)
except requests.exceptions.RequestException as e:
url = url.decode('utf-8')
response = requests.get(url, timeout=5, verify=False)
response.raise_for_status() # 如果响应的状态码不是 200,则引发异常
# 源文件是二进制的AES加密文件,那么通过response.text转换成字符串后,数据可能会被破坏,从而无法还原回原始数据
m3u_string = response.content
# 加密文件检测和解码
checkToDecrydecrypt3(url, passwordDict, m3u_string, filenameDict)
except Exception as e:
print("fetch_url3 error:", e)
pass
#
def download_files2(urls, passwordDict, filenameDict, secretNameDict, uploadGitee, uploadGithub,
uploadWebdav):
with concurrent.futures.ThreadPoolExecutor() as executor:
# 提交下载任务并获取future对象列表
future_to_url = {
executor.submit(fetch_url2, url, passwordDict, filenameDict, secretNameDict, uploadGitee,
uploadGithub, uploadWebdav): url for
url in urls}
# 等待所有任务执行完毕
executor.shutdown(wait=True)
def download_files3(urls, passwordDict, filenameDict):
with concurrent.futures.ThreadPoolExecutor() as executor:
# 提交下载任务并获取future对象列表
future_to_url = {
executor.submit(fetch_url3, url, passwordDict, filenameDict): url for
url in urls}
# 等待所有任务执行完毕
executor.shutdown(wait=True)
# 添加一条数据进入字典
def addlist(request, rediskey):
# 获取 HTML 页面发送的 POST 请求参数
addurl = request.json.get('addurl')
name = request.json.get('name')
my_dict = {addurl: name}
redis_add_map(rediskey, my_dict)
return jsonify({'addresult': "add success"})
# update 开启m3u域名白名单加密文件上传gitee
# secretfile 开启m3u域名白名单生成加密文件
def writeTvList(fileName, secretfilename):
distribute_data(white_list_adguardhome, fileName, 10)
white_list_adguardhome.clear()
download_secert_file(fileName, secretfilename, 'm3u',
isOpenFunction('switch8'), isOpenFunction('switch7'), isOpenFunction('switch30'),
isOpenFunction('switch31'), isOpenFunction('switch32'))
# whitelist-加密上传 switch11
# whitelist-加密生成 switch12
def writeOpenclashNameServerPolicy():
if white_list_nameserver_policy and len(white_list_nameserver_policy) > 0:
# 更新redis数据库白名单三级分层字典
redis_del_map(REDIS_KEY_WHITELIST_DATA_SP)
global whitelistSpData
redis_add_map(REDIS_KEY_WHITELIST_DATA_SP, whitelistSpData)
whitelistSpData.clear()
# 通知dns服务器更新内存
redis_add(REDIS_KEY_UPDATE_WHITE_LIST_SP_FLAG, 1)
# redis_add(REDIS_KEY_UPDATE_WHITE_LIST_FLAG, 1)
# 更新redis数据库白名单
# redis_add_map(REDIS_KEY_WHITE_DOMAINS, white_list_nameserver_policy)
path = f"{secret_path}{getFileNameByTagName('whiteListDomian')}.txt"
distribute_data(white_list_nameserver_policy, path, 10)
white_list_nameserver_policy.clear()
path2 = f"{secret_path}{getFileNameByTagName('whitelistDirectRule')}.txt"
distribute_data(white_list_Direct_Rules, path2, 10)
white_list_Direct_Rules.clear()
# 白名单加密
download_secert_file(path, f"{public_path}{getFileNameByTagName('whiteListDomianSecret')}.txt", 'whitelist',
isOpenFunction('switch12'), isOpenFunction('switch11'),
isOpenFunction('switch30'), isOpenFunction('switch31'), isOpenFunction('switch32'))
def writeBlackList():
if black_list_nameserver_policy and len(black_list_nameserver_policy) > 0:
# 更新redis数据库白名单三级分层字典
redis_del_map(REDIS_KEY_BLACKLIST_DATA_SP)
global blacklistSpData
redis_add_map(REDIS_KEY_BLACKLIST_DATA_SP, blacklistSpData)
blacklistSpData.clear()
# 通知dns服务器更新内存
redis_add(REDIS_KEY_UPDATE_BLACK_LIST_SP_FLAG, 1)
# 更新redis数据库黑名单
# redis_add_map(REDIS_KEY_BLACK_DOMAINS, black_list_nameserver_policy)
# 通知dns服务器更新内存
# redis_add(REDIS_KEY_UPDATE_BLACK_LIST_FLAG, 1)
path = f"{secret_path}{getFileNameByTagName('blackListDomain')}.txt"
distribute_data(black_list_nameserver_policy, path, 10)
black_list_nameserver_policy.clear()
path2 = f"{secret_path}{getFileNameByTagName('blacklistProxyRule')}.txt"
distribute_data(black_list_Proxy_Rules, path2, 10)
black_list_Proxy_Rules.clear()
# 黑名单加密
download_secert_file(path, f"{public_path}{getFileNameByTagName('blackListDomainSecret')}.txt", 'blacklist',
isOpenFunction('switch16'),
isOpenFunction('switch17'),
isOpenFunction('switch30'), isOpenFunction('switch31'), isOpenFunction('switch32'))
def updateAdguardhomeWithelistForM3us(urls):
for url in urls:
updateAdguardhomeWithelistForM3u(url.decode("utf-8"))
def chaoronghebase2(redisKeyData, fileName, left1, right1, fileName2, left2):
old_dict = redis_get_map(redisKeyData)
if not old_dict or len(old_dict) == 0:
return "empty"
newDict = {}
newDict2 = {}
for key, value in old_dict.items():
newDict[left1 + key + right1] = ""
newDict2[left2 + key] = ''
# 同步方法写出全部配置