helper.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. const helper = {}
  2. const crypto = require('crypto');
  3. const dns = require('dns');
  4. helper.base64 = function(str){
  5. return Buffer.from(str).toString('base64')
  6. }
  7. // '&channel=rd'
  8. helper.checkChannel = function(url,key) {
  9. return url.includes(key);
  10. }
  11. helper.checkChannelListKey = function(url,key_list) {
  12. for (let index = 0; index < key_list.length; index++) {
  13. const key = key_list[index];
  14. if(!this.checkChannel(url,key)){
  15. return false
  16. }
  17. }
  18. return true
  19. }
  20. helper.capitalize_string = function(K) {
  21. // 将字符串按 "-" 分割,对每个单词首字母大写,然后重新用 "-" 连接
  22. return K.split("-")
  23. .map(word => word.charAt(0).toUpperCase() + word.slice(1))
  24. .join("-");
  25. }
  26. helper.query_raw_text = function(K) {
  27. if (!K || typeof K !== 'object') {
  28. return "";
  29. }
  30. const result = [];
  31. const replace_dict = {
  32. "'": "%27",
  33. "/": "%2F",
  34. "+": "%2B",
  35. "=": "%3D",
  36. "&": "%26",
  37. "@": "%40",
  38. "#": "%23",
  39. ";": "%3B",
  40. "?": "%3F",
  41. "%5B": "[",
  42. "%5D": "]",
  43. "%20": "+"
  44. };
  45. // 自定义编码函数
  46. function customEncode(str) {
  47. let encoded = encodeURIComponent(str);
  48. for (const [char, encoding] of Object.entries(replace_dict)) {
  49. encoded = encoded.replace(new RegExp(escapeRegExp(char), 'g'), encoding);
  50. }
  51. return encoded;
  52. }
  53. // 转义正则表达式特殊字符
  54. function escapeRegExp(string) {
  55. return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  56. }
  57. for (const [et, value] of Object.entries(K)) {
  58. if (value === null || value === undefined) {
  59. continue;
  60. }
  61. if (Array.isArray(value)) {
  62. // 处理数组
  63. for (const nt of value) {
  64. result.push(`${et}[]=${customEncode(String(nt))}`);
  65. }
  66. } else if (typeof value === 'object') {
  67. // 处理对象
  68. let nt = `${et}=${customEncode(JSON.stringify(value))}`;
  69. nt = nt.replace(/%5B/g, "[").replace(/%5D/g, "]");
  70. result.push(nt);
  71. } else {
  72. // 处理普通值
  73. result.push(`${et}=${customEncode(String(value))}`);
  74. }
  75. }
  76. return result.join("&");
  77. }
  78. helper.get_signature = function(token,K, O = 'agentd3dGiJc651gSQ28w9', H = 'https://new-media-fx.qimao.com/api') {
  79. // 构建 Authorization 对象
  80. const et = {
  81. "Authorization": token
  82. };
  83. // 获取排序后的参数字符串
  84. const nt = helper.params_sort(et);
  85. const at = helper.params_sort(K.get('data'), false);
  86. const it = helper.query_raw_text(K.get('params'));
  87. // 确定连接符
  88. const ot = K['url'].includes('?') ? '&' : '?';
  89. // 构建URL
  90. let st = K.get('url') + (it ? ot + it : '');
  91. if (H && st.startsWith(H)) {
  92. st = st.substring(H.length);
  93. }
  94. // 构建最终签名字符串
  95. const ct = `${st}&${nt}&${at}&${O}`;
  96. // 调试输出
  97. /*
  98. console.log('nt:', nt);
  99. console.log('at:', at);
  100. console.log('it:', it);
  101. console.log('ot:', ot);
  102. console.log('st:', st);
  103. console.log('ct:', ct);
  104. */
  105. return helper.sha256_hash(ct);
  106. }
  107. helper.params_sort = function(K, O = true) {
  108. if (!K || typeof K !== 'object') {
  109. return "";
  110. }
  111. // 按照键的字母顺序排序
  112. const sorted_keys = Object.keys(K).sort();
  113. const result = [];
  114. for (const et of sorted_keys) {
  115. if (K[et] !== null && K[et] !== undefined) {
  116. // 根据 O 决定是否首字母大写
  117. const nt = O ? helper.capitalize_string(et) : et;
  118. // 使用 JSON.stringify 来转换为 JSON 格式的字符串
  119. result.push(`${nt}=${JSON.stringify(K[et])}`);
  120. }
  121. }
  122. // 拼接成查询字符串
  123. return result.join("&");
  124. }
  125. helper.sha256_hash = function(data) {
  126. const crypto = require('crypto');
  127. // 创建一个 sha256 哈希对象
  128. const hash = crypto.createHash('sha256');
  129. // 更新哈希对象,传入数据
  130. hash.update(data, 'utf-8');
  131. // 获取最终的哈希值,返回一个十六进制字符串
  132. return hash.digest('hex');
  133. }
  134. helper.resolveDomain = async function(domain) {
  135. // 创建新的DNS解析器实例
  136. const resolver = new dns.promises.Resolver();
  137. // 尝试不同的DNS服务器
  138. const dnsServers = [
  139. '8.8.8.8', // Google DNS
  140. '8.8.4.4', // Google DNS备用
  141. '1.1.1.1', // Cloudflare DNS
  142. '1.0.0.1', // Cloudflare DNS备用
  143. '223.5.5.5', // 阿里DNS
  144. '223.6.6.6', // 阿里DNS备用
  145. '119.29.29.29', // 腾讯DNS
  146. '114.114.114.114' // 114 DNS
  147. ];
  148. for (const dnsServer of dnsServers) {
  149. try {
  150. console.log(`\nTrying DNS server: ${dnsServer}`);
  151. resolver.setServers([dnsServer]);
  152. const addresses = await resolver.resolve4(domain);
  153. console.log(`Success with DNS server ${dnsServer}:`);
  154. console.log('IP Addresses:', addresses);
  155. // 如果成功找到IP,尝试ping或TCP连接测试
  156. for (const ip of addresses) {
  157. console.log(`Testing connectivity to IP: ${ip}`);
  158. // 这里可以添加连接测试代码
  159. }
  160. return addresses;
  161. } catch (error) {
  162. console.error(`Failed with DNS server ${dnsServer}:`, error.message);
  163. }
  164. }
  165. throw new Error('Failed to resolve domain with all DNS servers');
  166. }
  167. helper.md5 = function(text) {
  168. return crypto.createHash('md5').update(text).digest('hex');
  169. }
  170. helper.getSign = function(distributorId,secretKey) {
  171. const params = [distributorId, secretKey, helper.getCurrentUnixTimestamp()];
  172. // 将参数数组中的每个元素转换为字符串并连接成一个单一的字符串
  173. const paramStr = params.map(String).join('');
  174. // 使用 MD5 算法生成哈希值
  175. const hash = crypto.createHash('md5');
  176. hash.update(paramStr);
  177. // 返回哈希值的十六进制表示
  178. return hash.digest('hex');
  179. }
  180. helper.getCurrentUnixTimestamp = function() {
  181. return Math.floor(Date.now() / 1000)
  182. }
  183. helper.getFqRequestOpt = function(key,sid_tt='b0390e26648a71801795b3b13c9d7d20'){
  184. const options = {
  185. url: `https://api.whbfxb.cn/api/novelsale/reader/get/content/v1/?aid=40013183&key=${key}`,
  186. // url: `https://api.whbfxb.cn/api/novelsale/reader/get/content/v1/?aid=40013183&device_brand=realme&device_platform=android&device_type=RMX2020&mp_sdk_version=3.21.0&novelsale_app_scene=023001&version_code=230&key=${key}&item_source=1&module_name=ad_link&click_id=__CLICKID__&clickid=__CLICKID__&creativetype=__CTYPE__&demand_id=0&item_id=&media_source=1&mid1=__MID1__&mid2=__MID2__&mid3=__MID3__&mid4=__MID4__&mid5=__MID5__&projectid=__PROJECT_ID__&promotionid=__PROMOTION_ID__&request_id=__REQUESTID__&book_id=&host_novelsale_app_id=40013183`,
  187. headers: {
  188. // 'cookie': `sid_tt=${sid_tt}; ` +
  189. // 'ssid_ucp_v1=1.0.0-KDU0Nzc2NjZmZjRhNTZkYWM5YTMwN2ZmNzAyODMwNjFjZmQyMzA4OTYKFQjkktCcvMz_ARDGw7W6Bhjv6CA4CBoCbHEiIDhjOTg2NjgwY2Q4NzE2YmEzMmJhMjBjM2MwMmEwOTJk; ' +
  190. // 'is_staff_user=false; ' +
  191. // 'sid_ucp_v1=1.0.0-KDU0Nzc2NjZmZjRhNTZkYWM5YTMwN2ZmNzAyODMwNjFjZmQyMzA4OTYKFQjkktCcvMz_ARDGw7W6Bhjv6CA4CBoCbHEiIDhjOTg2NjgwY2Q4NzE2YmEzMmJhMjBjM2MwMmEwOTJk; ' +
  192. // `sessionid=${sid_tt}; ` +
  193. // 'ttwid=1%7CdTM3VEQ9KwlMsV2FV6Gaxv5CBXmd5i44fjDoZV_a8g8%7C1733124580%7Cc0952b31723a2f5d5e4b9f84a1c29cb7cbde5592906de58798b92a695d3fb292; ' +
  194. // `sessionid_ss=${sid_tt}; ` +
  195. // `sid_guard=${sid_tt}%7C1733124550%7C5184000%7CFri%2C+31-Jan-2025+07%3A29%3A10+GMT; ` +
  196. // 'uid_tt=3361cf574bbdd82820371b4667fc9b91; ' +
  197. // 'passport_csrf_token=f7e1da8668d26b8509725bcf7fa95628; ' +
  198. // 'uid_tt_ss=3361cf574bbdd82820371b4667fc9b91; ' +
  199. // 'passport_csrf_token_default=f7e1da8668d26b8509725bcf7fa95628; ' +
  200. // 'store-region=cn-gs; ' +
  201. // 'store-region-src=uid; ' +
  202. // 'n_mh=9-mIeuD4wZnlYrrOvfzG3MuT6aQmCUtmr8FxV8Kl8xY',
  203. 'cookie': `sessionid=${sid_tt};`,
  204. 'user-agent': 'Mozilla/5.0 (Linux; Android 9; Mi Note 3 Build/PKQ1.181007.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.193 Mobile Safari/537.36 aweme/29.4.0 ToutiaoMicroApp/3.21.0 PluginVersion/29409006',
  205. 'content-type': 'application/json',
  206. 'referer': 'https://tmaservice.developer.toutiao.com/?appid=tt1b316d8c8401e42101&version=2.7.0'
  207. }
  208. };
  209. return options
  210. }
  211. helper.getMfFqRequestOpt = function(key,sid_tt='85486acb04e1918afbee91a9466a9fdc'){
  212. const options = {
  213. // url: `https://sdksaleapi.hubeidehuic.com/open_sdk/reader/directory/list/v1?novelsale_aid=40017686&novelsdk_aid=638505&novelsdk_version_code=230&novelsdk_device_type=V2049A&promotion_code=${key}&novelsdk_device_platform=android&version_code=290400&device_type=V2049A&sale_app_id=40017686&book_id&force_no_user_id=true&novelsdk_app_name=undefined&app_name=aweme&device_brand=vivo&device_platform=android&aid=1128&mini_app_version=2.3.0&iid=2936130190182979&device_id=825067091580075&ac=wifi&channel=douyin-huidu-gw-huidu-2940&version_name=29.4.0&os=android&ssmix=a&language=zh&os_api=31&os_version=12&manifest_version_code=290400&resolution=1080*2193&dpi=480&update_version_code=29400100&_rticket=1735289341306&first_launch_timestamp=1733382087&last_deeplink_update_version_code=0&cpu_support64=true&host_abi=arm64-v8a&is_guest_mode=0&app_type=normal&minor_status=0&appTheme=light&is_preinstall=0&need_personal_recommend=1&is_android_pad=0&is_android_fold=0&ts=1735289340&cdid=623fd2d5-5680-4aa4-95e8-78378a2ac7c5`,
  214. url: `https://sdksaleapi.hubeidehuic.com/open_sdk/reader/directory/list/v1?novelsale_aid=40017686&novelsdk_aid=638505&promotion_code=${key}`,
  215. headers: {
  216. 'Connection': 'keep-alive',
  217. 'Cookie': `sessionid=${sid_tt};`,
  218. // 'Cookie': `sid_tt=${sid_tt};ssid_ucp_v1=1.0.0-KDVhZTIzM2QyOGFlMGE1MWRhMGIzNmI1MzFmZjlmMTVlODhjYmJiOTkKFQjr19Cz7syDAxC9zrm7Bhip_CY4CBoCbGYiIDg1NDg2YWNiMDRlMTkxOGFmYmVlOTFhOTQ2NmE5ZmRj;is_staff_user=false;sid_ucp_v1=1.0.0-KDVhZTIzM2QyOGFlMGE1MWRhMGIzNmI1MzFmZjlmMTVlODhjYmJiOTkKFQjr19Cz7syDAxC9zrm7Bhip_CY4CBoCbGYiIDg1NDg2YWNiMDRlMTkxOGFmYmVlOTFhOTQ2NmE5ZmRj;sessionid=${sid_tt};sessionid_ss=${sid_tt};uid_tt=88e8f06afa51aef4fac18f5d77607921;sid_guard=${sid_tt}%7C1735288637%7C5184000%7CTue%2C+25-Feb-2025+08%3A37%3A17+GMT;passport_csrf_token=08c2b9062233b43ce0e2dd24171203b1;odin_tt=9783bbcf8f09663820810574a01f08b307b6e74c90aa3d8a3b4a80faa9a7d4b197d7e807043c111dcbabde8551e1111145cc87c65fce067f3c532bd796b3681a;uid_tt_ss=88e8f06afa51aef4fac18f5d77607921;passport_csrf_token_default=08c2b9062233b43ce0e2dd24171203b1;store-region=cn-gd;store-region-src=uid;n_mh=9-mIeuD4wZnlYrrOvfzG3MuT6aQmCUtmr8FxV8Kl8xY`,
  219. 'User-Agent': 'Mozilla/5.0 (Linux; Android 12; V2049A Build/SP1A.210812.003; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.193 Mobile Safari/537.36 aweme/29.4.0 ToutiaoMicroApp/3.21.0 PluginVersion/29409006',
  220. 'bdp-sec-uid': 'MS4wLjABAAAA1M_29BGiEjsgNHMP-bmblk-9qGkx5vN7Y3HgltXiFghmy1mfPlH3eonnTWpXANjE',
  221. 'bdp-did': '825067091580075',
  222. 'bdp-channel': 'douyin-huidu-gw-huidu-2940',
  223. 'bdp-device-timezone-offset': '28800',
  224. 'bdp-os-version': '12',
  225. 'bdp-os-name': 'Android',
  226. 'bdp-uid': '1788263942327546',
  227. 'bdp-version-code': '290400',
  228. 'bdp-device-platform': 'Android',
  229. 'bdp-aid': '1128',
  230. 'bdp-device-manufacturer': 'vivo',
  231. 'bdp-app-id': 'tt26e45059b9d1239401',
  232. 'bdp-device-model': 'V2049A',
  233. 'Content-Type': 'application/json',
  234. 'activity_now_client': '0',
  235. 'x-bd-kmsv': '1',
  236. 'x-tt-request-tag': 's=-1;p=1',
  237. 'x-tt-trace-id': '00-074e072b0d2ee64e6e8d4abbf55d0468-074e072b0d2ee64e-01',
  238. 'X-Argus': '/WluZw==',
  239. 'X-Gorgon': '8404f06a00014b0d761b864b2932a41f362b9276f3488e7700de',
  240. 'X-Helios': 'AkKvNQvLH57rNKMEdZDNQPlc89Q1Zrs974uhRy+IDcTR/y8Z',
  241. 'X-Khronos': '1735289341',
  242. 'X-Ladon': 'Z25p/Q==',
  243. 'X-Medusa': '/mluZwqBMZ0qvrJcK0OmF6qmD3/QqgABDTUpyT1IA9+MOFfCQlYXoNnb+C/rkhLwH0AzGIam8DGpfzu3MFDs3fD3Jx12yo1AKxmV8ar5cQEJ+yz2srKfjMWVZhnlSHsA8UOdYdeclol6hoyOKCcvD9k+84icb0lW39sUS4b+3c/ZdQ2g/VnINn0Bpn+V99xK211ut9hvj+pP6zAVmrqx+c86fuUyOPwBgmLtJdoya9Z5ChWFBUpxgDZiurbYqGWHG1vDjIJSnKdKLRBhRNCmsvXf7lPifdINI8I3UOlRrZJW4m/sbQ46JlozT8Dza62mFCUJaMXSVXxrPdVxq9C06cZiwamtMQw6cnQZxcI0B2R2i65Ct45ohgrJE2g4z+nZRVrpBO9ApgIXQ/sp6jPKfbkSXHFzykdk75Ktwz67Vji7puQOOUUlyeMkaBGvEaq9EZnwOyJ3VxggUaqaH5CtKfYD7lnAM4TbjtLE3SMfZZNviajoNh7ouLeyNLUab/Qd9+MBMbWsTN49BwVlOUHKn9hp56XqRNWIWyrA55jDqL6iicuXtb8Xx//kvkccyUByLEde72oGf2/IRBIAgCvfVXH/hP6wJy1f6XjL0Or6I1qX98gWSGvN9HYObpKNGm/64vtoSpHDC1JWtcuf5ZzlsA4R8XbZKS/Tn0Y9kC89pmPnHYLscWtOFoirIRJ0DXqF/PM3kB4jilEe5Y26cvQK2LaSYcuKX7iL4rWLlVrWIh7EkoFH9Un4f8hruVSbhJMWbN+dXZyNn5BHq/+rASd0pCts3fz05xX4dYlfawDeX74MhFxnKcPZu9pS2hjqDZAPmuqKo/DWWDcsO2wT0R0Fd5k/rwtXFOxQgH07cDVkeIDpLjlT3jBdxQ6X9eDfC/EP+nQv+9WgwKkeG5FXDiIbd1++iS6Mb9V4OHmUowZDopiRFqCD5GeBQTkwEe8w05QJ4Dhdu5a5l1y3CzpS/hGgwr9Sos0QrrVXJI7x6Ah1+U05c4TNHXRe8lBUPJNWUHWBXgKznDKBh1+O+Y+jqKxTyIldTfkfXP/5H13/3Hk='
  244. }
  245. };
  246. return options
  247. }
  248. helper.getHyCreateLinkOpt = function(token){
  249. const options = {
  250. url: `https://ms.zhangwenpindu.cn/manage/distribution/createLink`,
  251. headers: {
  252. 'Accept': 'application/json, text/plain, */*',
  253. 'Accept-Language': 'zh-CN,zh;q=0.9',
  254. 'Cache-Control': 'no-cache',
  255. 'Connection': 'keep-alive',
  256. 'Content-Type': 'application/x-www-form-urlencoded',
  257. 'Origin': 'https://manage.zhangwenpindu.cn',
  258. 'Pragma': 'no-cache',
  259. 'Referer': 'https://manage.zhangwenpindu.cn/',
  260. 'Sec-Fetch-Dest': 'empty',
  261. 'Sec-Fetch-Mode': 'cors',
  262. 'Sec-Fetch-Site': 'same-site',
  263. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
  264. 'sec-ch-ua': '"Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"',
  265. 'sec-ch-ua-mobile': '?0',
  266. 'sec-ch-ua-platform': '"Windows"'
  267. }
  268. };
  269. options.headers['Authorization'] = `Bearer ${token}`
  270. return options
  271. }
  272. helper.getTimeStampByHourMinute = function(timeStr,otherStr=null) {
  273. // 解析时间字符串
  274. const [hours, minutes] = timeStr.split(':').map(Number);
  275. let date;
  276. if (otherStr) {
  277. // 如果提供了日期字符串,解析完整的日期时间
  278. date = new Date(otherStr);
  279. // 设置时分秒
  280. date.setHours(hours, minutes, 0, 0);
  281. } else {
  282. // 如果没有提供日期,使用今天的日期
  283. date = new Date();
  284. date.setHours(hours, minutes, 0, 0);
  285. }
  286. return date;
  287. }
  288. helper.getLocalDate = function() {
  289. const d = new Date();
  290. const year = d.getFullYear();
  291. const month = String(d.getMonth() + 1).padStart(2, '0');
  292. const day = String(d.getDate()).padStart(2, '0');
  293. return `${year}-${month}-${day}`;
  294. }
  295. helper.getPaginationParams = function(total, pageSize = 500) {
  296. const pages = Math.ceil(total / pageSize);
  297. const result = [];
  298. for(let page = 0; page < pages; page++) {
  299. const offset = page * pageSize;
  300. const limit = Math.min(pageSize, total - offset);
  301. result.push({
  302. page,
  303. offset,
  304. limit
  305. });
  306. }
  307. return result;
  308. }
  309. helper.parseJsonP = function(jsonpStr){
  310. // 去除JSONP包装,提取纯JSON字符串
  311. const jsonStr = jsonpStr.replace(/^_aq_88765\((.*)\)$/, '$1');
  312. // 解析JSON
  313. try {
  314. const data = JSON.parse(jsonStr);
  315. // 现在可以访问解析后的数据
  316. console.log('Status:', data.state);
  317. console.log('Session:', data.sess);
  318. // 访问验证码配置
  319. const captchaConfig = data.data.comm_captcha_cfg;
  320. console.log('TDC Path:', captchaConfig.tdc_path);
  321. // 访问显示信息
  322. const showInfo = data.data.dyn_show_info;
  323. console.log('Language:', showInfo.lang);
  324. console.log('Instruction:', showInfo.instruction);
  325. // 访问背景元素配置
  326. const bgConfig = showInfo.bg_elem_cfg;
  327. const img_url_1 = bgConfig.img_url
  328. // const img_url_1 = showInfo.sprite_url.replace('img_index=0', 'img_index=1');
  329. const img_url_0 = showInfo.sprite_url
  330. console.log('Background Size:', bgConfig.size_2d);
  331. // 访问前景元素列表
  332. showInfo.fg_elem_list.forEach((elem, index) => {
  333. console.log(`Element ${index + 1}:`, {
  334. id: elem.id,
  335. size: elem.size_2d,
  336. position: elem.init_pos
  337. });
  338. });
  339. // 访问绑定配置
  340. showInfo.fg_binding_list.forEach((binding, index) => {
  341. console.log(`Binding ${index + 1}:`, {
  342. master: binding.master,
  343. slave: binding.slave,
  344. type: binding.bind_type
  345. });
  346. });
  347. return {img_url_1:img_url_1.match(/image=([^&]+)/)[1],img_url_0:img_url_0.match(/image=([^&]+)/)[1]}
  348. } catch (error) {
  349. console.error('Error parsing JSON:', error);
  350. return null
  351. }
  352. }
  353. helper.generateSign = function(map, appSecret) {
  354. const keyArray = Object.keys(map).sort();
  355. const parts = [];
  356. for (const key of keyArray) {
  357. if (key === 'sign') {
  358. continue;
  359. }
  360. const value = map[key];
  361. if (!value || value.toString().trim().length <= 0) {
  362. continue;
  363. }
  364. parts.push(`${key}=${value.toString().trim()}`);
  365. }
  366. parts.push(`key=${appSecret}`);
  367. const signStr = parts.join('&');
  368. // Using crypto module for MD5 hash
  369. const crypto = require('crypto');
  370. return crypto.createHash('md5')
  371. .update(signStr)
  372. .digest('hex')
  373. .toUpperCase();
  374. }
  375. helper.getDate7DaysBefore = function(dateString, inputFormat, outputFormat) {
  376. const inputDate = new Date(dateString);
  377. if (isNaN(inputDate.getTime())) {
  378. // 如果默认解析失败,尝试手动解析
  379. // 这里可以添加更多格式的解析逻辑
  380. throw new Error('Unsupported date format - consider using moment.js for complex formats');
  381. }
  382. const date7DaysBefore = new Date(inputDate);
  383. date7DaysBefore.setDate(date7DaysBefore.getDate() + 0);
  384. // 简单格式化函数
  385. function formatDate(date, format) {
  386. if (!format || format.toLowerCase() === 'iso') {
  387. return date.toISOString();
  388. }
  389. const pad = num => num.toString().padStart(2, '0');
  390. const year = date.getFullYear();
  391. const month = pad(date.getMonth() + 1);
  392. const day = pad(date.getDate());
  393. const hours = pad(date.getHours());
  394. const minutes = pad(date.getMinutes());
  395. const seconds = pad(date.getSeconds());
  396. return format
  397. .replace('YYYY', year)
  398. .replace('MM', month)
  399. .replace('DD', day)
  400. .replace('HH', hours)
  401. .replace('mm', minutes)
  402. .replace('ss', seconds);
  403. }
  404. return formatDate(date7DaysBefore, outputFormat);
  405. }
  406. module.exports = helper;