11#!/usr/bin/env python
22# _*_ coding:utf-8 _*_
33
4+
45import os
5- import urlparse
6- import httplib
6+ import urllib . parse
7+ import http . client
78import json
89import hashlib
910import codecs
1011from shutil import copyfile
1112import sys
1213import traceback
13- from distutils .version import LooseVersion
14- from string import Template
14+ import re # 用于正则解析 git_path 和 JS 预处理
15+ from packaging import version # 新增:用于版本比较(替换 LooseVersion)
16+ import tarfile
17+ from string import Template
18+
19+ try :
20+ import requests
21+ except ImportError :
22+ raise ImportError ("The 'requests' library is required but not installed. Please install it before running this script." )
1523
1624#https://docs.python.org/2.4/lib/httplib-examples.html
1725
26+
1827curr_path = os .path .dirname (os .path .realpath (__file__ ))
1928parent_path = os .path .realpath (os .path .join (curr_path ,".." ))
2029git_bin = "git"
2130
2231def http_request (url ,depth = 0 ):
2332if depth > 10 :
24- raise Exception ("Redirected " + depth + " times, giving up." )
25- o = urlparse .urlparse (url ,allow_fragments = True )
26- if o .scheme == 'https' :
27- conn = httplib .HTTPSConnection (o .netloc )
28- else :
29- conn = httplib .HTTPConnection (o .netloc )
30- path = o .path
31- if o .query :
32- path += '?' + o .query
33- conn .request ("GET" ,path ,"" , {"Cache-Control" :"max-age=0" })
34- response = conn .getresponse ()
35- #print response.status, response.reason
36-
37- if response .status > 300 and response .status < 400 :
38- headers = dict (response .getheaders ())
39- if headers .has_key ('location' )and headers ['location' ]!= url :
40- #print headers['location']
41- return http_request (headers ['location' ],depth + 1 )
42-
43- data = response .read ()
44- return data
33+ raise Exception ("Redirected {} times, giving up." .format (depth ))
34+ try :
35+ resp = requests .get (url ,headers = {"Cache-Control" :"max-age=0" },timeout = 10 ,allow_redirects = False )
36+ # 处理重定向
37+ if 300 < resp .status_code < 400 and "location" in resp .headers and resp .headers ["location" ]!= url :
38+ return http_request (resp .headers ["location" ],depth + 1 )
39+ return resp .content
40+ except Exception as e :
41+ print (f"[http_request] requests error:{ e } " )
42+ raise
4543
4644def work_modules ():
4745module_path = os .path .join (curr_path ,"modules.json" )
@@ -52,58 +50,101 @@ def work_modules():
5250for m in modules :
5351if "module" in m :
5452try :
55- up = sync_module (m ["module" ],m ["git_source" ])
53+ up = sync_module (m ["module" ],m ["git_source" ], m [ "branch" ] )
5654if not updated :
5755updated = up
58- except Exception , e :
56+ except Exception as e :
5957traceback .print_exc ()
6058return updated
6159
62- def sync_module (module ,git_path ):
60+ def sync_module (module ,git_path , branch ):
6361module_path = os .path .join (parent_path ,module )
6462conf_path = os .path .join (module_path ,"config.json.js" )
65- rconf = get_remote_js (git_path )
63+ rconf = get_remote_js (git_path ,branch )
64+ if rconf is None :
65+ print (f"[sync_module] Skipping module '{ module } ' due to invalid config URL." )
66+ return False # 不更新,返回 False
6667lconf = get_local_js (conf_path )
6768update = False
6869if not rconf :
69- return
70- print rconf
70+ return False # 修改:如果 rconf 无效,也返回 False
71+ print ( rconf )
7172if not lconf :
7273update = True
7374else :
74- if LooseVersion (rconf ["version" ])> LooseVersion (lconf ["version" ]):
75+ # 修改:使用 packaging.version.parse 替换 LooseVersion
76+ if version .parse (rconf ["version" ])> version .parse (lconf ["version" ]):
7577update = True
7678if update :
77- print "updating" ,git_path
79+ print ( "updating" ,git_path )
7880cmd = ""
79- tar_path = os .path .join (module_path ,"%s.tar.gz" % module );
81+ tar_path = os .path .join (module_path ,"%s.tar.gz" % module )
8082if os .path .isdir (module_path ):
81- cmd = "cd $module_path && git reset --hard && $git_bin pull && rm -f $module.tar.gz && tar -zcf $module.tar.gz $module"
83+ #cmd = "cd $module_path && $git_bin reset --hard && $git_bin clean -fdqx && $git_bin pull && rm -f $module.tar.gz && tar -zcf $module.tar.gz $module"
84+ cmd = "cd $module_path && $git_bin reset --hard && $git_bin clean -fdqx && $git_bin fetch --depth 1 && rm -f $module.tar.gz"
8285else :
83- cmd = "cd $parent_path && $git_bin clone $git_path $module_path && cd $module_path && tar -zcf $module.tar.gz $module"
86+ # 修改:添加 --depth 1 和 --branch $branch 以实现浅克隆指定分支
87+ cmd = "cd $parent_path && $git_bin clone --depth 1 --branch $branch $git_path $module_path && cd $module_path && tar -zcf $module.tar.gz $module"
8488t = Template (cmd )
85- params = {"parent_path" :parent_path ,"git_path" :git_path ,"module_path" :module_path ,"module" :module ,"git_bin" :git_bin }
89+ params = {"parent_path" :parent_path ,"git_path" :git_path ,"module_path" :module_path ,"module" :module ,"git_bin" :git_bin , "branch" : branch }
8690s = t .substitute (params )
8791os .system (s )
8892rconf ["md5" ]= md5sum (tar_path )
8993with codecs .open (conf_path ,"w" ,"utf-8" )as fw :
90- json .dump (rconf ,fw ,sort_keys = True ,indent = 4 ,ensure_ascii = False , encoding = 'utf8' )
91- os .system ("cd %s && chown -R www:www ." % module_path )
94+ json .dump (rconf ,fw ,sort_keys = True ,indent = 4 ,ensure_ascii = False )
95+ # os.system("cd %s && chown -R www:www ." % module_path)
9296return update
9397
94- def get_config_js (git_path ):
95- #https://github.com/koolshare/merlin_tunnel.git
96- #git@github.com:koolshare/merlin_tunnel.git
97-
98- if git_path .startswith ("https://" ):
99- return git_path [0 :- 4 ]+ "/raw/master/config.json.js"
98+ def get_config_js (git_path ,branch ):
99+ if not git_path :
100+ print ("[get_config_js] Warning: git_source is empty for this module." )
101+ return None
102+
103+ # 解析 git_path 以提取 owner 和 repo
104+ # 支持格式:
105+ # - https://github.com/owner/repo.git
106+ # - git@github.com:owner/repo.git
107+ # - owner/repo.git 或 owner/repo
108+ owner = None
109+ repo = None
110+
111+ # 去除可能的 .git 后缀
112+ if git_path .endswith (".git" ):
113+ git_path = git_path [:- 4 ]
114+
115+ # 正则匹配常见格式
116+ match_https = re .match (r"https?://github\.com/([^/]+)/([^/]+)" ,git_path )
117+ match_ssh = re .match (r"git@github\.com:([^/]+)/([^/]+)" ,git_path )
118+ match_short = re .match (r"([^/]+)/([^/]+)" ,git_path )
119+
120+ if match_https :
121+ owner ,repo = match_https .groups ()
122+ elif match_ssh :
123+ owner ,repo = match_ssh .groups ()
124+ elif match_short :
125+ owner ,repo = match_short .groups ()
100126else :
101- index = git_path .find (":" )
102- return "https://github.com/" + git_path [index + 1 :- 4 ]+ "/raw/master/config.json.js"
103-
104- def get_remote_js (git_path ):
105- data = http_request (get_config_js (git_path ))
106- conf = json .loads (data )
127+ print (f"[get_config_js] Error: Invalid git_source format:{ git_path } " )
128+ return None
129+
130+ # 构建正确的 GitHub raw URL
131+ raw_url = f"https://raw.githubusercontent.com/{ owner } /{ repo } /{ branch } /config.json.js"
132+ return raw_url
133+
134+ def get_remote_js (git_path ,branch ):
135+ url = get_config_js (git_path ,branch )
136+ if not url :
137+ return None
138+ data = http_request (url )
139+ if not data :
140+ print (f"[get_remote_js] Warning: No data received from{ url } " )
141+ return None
142+ try :
143+ conf = json .loads (data .decode ('utf-8' ))
144+ except Exception as e :
145+ print (f"[get_remote_js] Error decoding JSON from{ url } :{ e } " )
146+ print (f"[get_remote_js] Raw data:{ data } " )
147+ return None
107148return conf
108149
109150def get_local_js (conf_path ):
@@ -130,20 +171,39 @@ def work_parent():
130171if os .path .isdir (path ):
131172yield fname ,path
132173
174+ def parse_js_config (content ):
175+ # """辅助函数:预处理 .js 文件内容,提取纯 JSON 部分"""
176+ # 去除注释(单行 // 和多行 /* */)
177+ content = re .sub (r'//.*?$|/\*.*?\*/' ,'' ,content ,flags = re .MULTILINE | re .DOTALL )
178+ # 去除 var ... = 和结尾 ;
179+ content = re .sub (r'^\s*(var\s+\w+\s*=\s*)?\{' ,'{' ,content )
180+ content = re .sub (r';\s*$' ,'' ,content )
181+ # 如果键没有引号,添加(简单正则替换,假设键是字母数字)
182+ content = re .sub (r'(\s*)([a-zA-Z0-9_]+)\s*:' ,r'\1"\2":' ,content )
183+ return content .strip ()
184+
133185def gen_modules (modules ):
134186for module ,path in work_parent ():
135187conf = os .path .join (path ,"config.json.js" )
136188m = None
137189try :
138190with codecs .open (conf ,"r" ,"utf-8" )as fc :
139- m = json .loads (fc .read ())
191+ raw_content = fc .read ()
192+ # 预处理为纯 JSON
193+ json_content = parse_js_config (raw_content )
194+ m = json .loads (json_content )
140195if m :
141196m ["name" ]= module
142197if "tar_url" not in m :
143198m ["tar_url" ]= module + "/" + module + ".tar.gz"
144199if "home_url" not in m :
145200m ["home_url" ]= "Module_" + module + ".asp"
146- except :
201+ except json .JSONDecodeError as e :
202+ print (f"[gen_modules] JSON decode error in{ conf } :{ e } " )
203+ print (f"[gen_modules] Raw content:{ raw_content } " )
204+ # 跳过无效模块,使用默认值
205+ m = None
206+ except Exception as e :
147207traceback .print_exc ()
148208
149209if not m :
@@ -163,10 +223,9 @@ def refresh_gmodules():
163223gmodules ["md5" ]= conf ["md5" ]
164224
165225with codecs .open (os .path .join (curr_path ,"app.json.js" ),"w" ,"utf-8" )as fw :
166- json .dump (gmodules ,fw ,sort_keys = True ,indent = 4 ,ensure_ascii = False , encoding = 'utf8' )
226+ json .dump (gmodules ,fw ,sort_keys = True ,indent = 4 ,ensure_ascii = False )
167227
168228updated = work_modules ()
169229if updated :
170230refresh_gmodules ()
171- os .system ("chown -R www:www %s/softcenter/app.json.js" % parent_path )
172- os .system ("sh %s/softcenter/build.sh" % parent_path )
231+ #os.system("chown -R www:www %s/softcenter/app.json.js" % parent_path)