├── .gitignore ├── LICENSE ├── README.md ├── icon.png ├── index.php ├── modules ├── binance_price.sgmodule ├── check_futadns.sgmodule ├── disney_plus_dualsub.sgmodule ├── facebook_block_sdk.sgmodule ├── fet_checkin.sgmodule ├── fet_get_token.sgmodule ├── futadns.sgmodule ├── hiraku_info.sgmodule ├── iphone_check_store.sgmodule ├── iphone_get_model.sgmodule ├── line_ad.sgmodule ├── mcdonalds.sgmodule ├── mcdonalds_token.sgmodule ├── mcdonalds_use_point.sgmodule ├── mcdonalds_version_bypass.sgmodule ├── momo_checkin.sgmodule ├── netflix_rating.sgmodule ├── ozan_fake_uuid.sgmodule ├── ozan_get_uuid.sgmodule ├── ptt.sgmodule ├── ptt_imgur_fix.sgmodule ├── shein_checkin.sgmodule ├── shopee_auto_crop.sgmodule ├── shopee_auto_water.sgmodule ├── shopee_brand_water.sgmodule ├── shopee_checkin.sgmodule ├── surge_check.sgmodule ├── twitter_image.sgmodule └── whosis_sayings.sgmodule ├── scripts ├── binance_price_panel.js ├── binance_price_set_symbol.js ├── check_futadns.js ├── fet_checkin.js ├── fet_token.js ├── hiraku_info_panel.js ├── iphone_check_store.js ├── iphone_get_model.js ├── mcdonalds_checkin.js ├── mcdonalds_luckydraw.js ├── mcdonalds_media_checkin.js ├── mcdonalds_question.js ├── mcdonalds_set_token.js ├── mcdonalds_token.js ├── mcdonalds_version_bypass.js ├── momo_checkin.js ├── momo_checkin_info.js ├── netflix_rating.js ├── ozan_fake_uuid.js ├── ozan_get_uuid.js ├── ozan_set_uuid.js ├── shein_checkin.js ├── shein_token.js ├── shopee_auto_crop.js ├── shopee_auto_harvest.js ├── shopee_auto_water.js ├── shopee_brand_store_water.js ├── shopee_checkin.js ├── shopee_coin_lucky_draw.js ├── shopee_get_checkin.js ├── shopee_get_crop.js ├── shopee_get_crop_token.js ├── shopee_get_grocery_store_token.js ├── shopee_grocery_store_water.js ├── shopee_set_auto_crop_seed_name.js ├── shopee_set_crop_name.js ├── shopee_shipping_lucky_draw.js ├── shopee_token.js ├── shopee_update_token.js ├── shopee_water_buy_free_item.js ├── shopee_water_checkin.js ├── shopee_water_mission.js ├── shopee_water_mission_claim.js ├── shopee_water_signin_bundle.js ├── surge_check.js └── whosis_sayings_panel.js ├── style.css ├── style.js ├── telegram_rules.conf └── utils ├── mcdonalds_token ├── .eslintrc ├── config.codekit3 ├── index.html ├── js │ ├── bplist.js │ ├── jdataview.js │ ├── main-min.js │ └── main.js ├── package-lock.json └── package.json ├── shopee_crop ├── get_water_friends.php └── upload_crop_info.php └── surge_check └── index.php /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | 4 | blackmagic/* 5 | 6 | # Ignore php mysql config file 7 | configuration.php 8 | connect.php 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Black Magic Lab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 黑魔法實驗室 Surge 模組 2 | 3 | 此 repo 底下的 Surge 腳本以及模組僅供學術參考使用,本實驗室不負責任何使用腳本以及模組所造成的後果 4 | 5 | ### 安裝方式 6 | 7 | 1. 在 iOS 裝置上安裝 [Surge](https://apps.apple.com/tw/app/surge-4/id1442620678) 並購買完整版授權。 8 | 2. 開啟「Rewrite」、「MitM」、「腳本」三個開關,並安裝好 MitM 用的 SSL 證書。 9 | 3. 前往[這裡](https://kinta.ma/surge)點擊想安裝的模組功能。 10 | 4. 模組網址將會自動複製,請到 Surge 的「模組」頁面新增剛剛複製的 URL 即可。 -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Black-Magic-Lab/Surge/1551f5d021b0d0b7cd494c9044a5b529fcb9dceb/icon.png -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | Surge Modules 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 26 | 27 | 28 | 29 |

部份模組為網路上收集而來,請自行判定該模組內容與安全性,此類模組已標上原作者與原始網址。

30 |

請點擊想安裝的模組,網址會自動複製到剪貼簿。

31 |

加入 Telegram 頻道,再也不錯過更新訊息!

32 |
33 | 34 |
35 |
36 |
37 | 40 | 把 NEOHOSTS 的 basic 廣告清單轉成 Surge 新的 domain set,部份阻擋列表可能跟其他阻擋用模組重複,請留意
41 | 作者: jkgtw
42 | 來源: https://github.com/jkgtw/Surge 43 |
44 |
45 |
46 |
47 | 50 | 需啟用 MitM over HTTP/2。部份阻擋列表可能跟其他阻擋用模組重複,請留意
51 | 作者: jkgtw
52 | 來源: https://github.com/jkgtw/Surge 53 |
54 |
55 |
56 |
57 | 60 | 支援沒續約而不能開啟 MitM over HTTP/2 的授權。部份阻擋列表可能跟其他阻擋用模組重複,請留意
61 | 作者: jkgtw
62 | 來源: https://github.com/jkgtw/Surge 63 |
64 |
65 |
66 |
67 | 70 | 部份阻擋列表可能跟其他阻擋用模組重複,請留意
71 | 作者: 踢低吸
72 | 來源: https://github.com/FutaGuard/FutaFilter 73 |
74 |
75 |
76 |
77 | 80 | DNS 公共服務,背後跑的是 AdGuard Home
81 | 作者: FutaFilter
82 | 來源: https://github.com/FutaGuard/FutaFilter 83 |
84 |
85 |
86 |
87 | 90 | 阻擋第三方 App 的 Facebook SDK,但不影響 Facebook App 本身。iOS 15 以上無法將 Facebook 自家 App 加入白名單,會全部擋掉。
91 | 作者: hirakujira
92 | 來源: https://github.com/Black-Magic-Lab/Surge 93 |
94 |
95 |
96 |
97 | 100 | 阻擋 YouTube 廣告,應該是失效了
101 | 作者: Phowx
102 | 來源: https://github.com/jimmyorz/Surge 103 |
104 |
105 |
106 | 107 |
108 |
109 |
110 | 113 | 把 Ecosia 搜尋轉址到 whoogle.xuan2host.com
114 | 作者: jkgtw
115 | 來源: https://github.com/jkgtw/Surge 116 |
117 |
118 |
119 |
120 | 123 | 把 Ecosia 搜尋轉址到 whoogle.futa.gg
124 | 作者: jkgtw
125 | 來源: https://github.com/jkgtw/Surge 126 |
127 |
128 |
129 |
130 | 133 | 把 Ecosia 搜尋轉址到 kagi.com
134 | 作者: ptntp
135 | 來源: https://github.com/ptntp/self-rule 136 |
137 |
138 |
139 | 140 |
141 |
142 |
143 | 146 | 連確認畫面都不會出現了,直接跳轉
147 | 作者: hirakujira
148 | 來源: https://github.com/Black-Magic-Lab/Surge 149 |
150 |
151 |
152 |
153 | 156 | 修正 PTT 網頁版無法顯示 Imgur 圖片的問題
157 | 作者: hirakujira
158 | 來源: https://github.com/Black-Magic-Lab/Surge 159 |
160 |
161 |
162 |
163 | 166 | 解鎖天氣資料類型,包括每小時降雨、空氣品質。iOS 16 以上不可用。
167 | 作者: VirgilClyne
168 | 來源: https://github.com/VirgilClyne/iRingo 169 |
170 |
171 |
172 |
173 | 176 | 自己 hosting 把中華民國國旗找回來了!
177 | 作者: yichahucha
178 | 來源: https://github.com/yichahucha/surge 179 |
180 |
181 |
182 |
183 | 186 | 如需更換語言請自行複製後修改 second_lang 參數,預設為英文。
187 | 作者: Neurogram-R
188 | 來源: https://github.com/Neurogram-R/Surge 189 |
190 |
191 |
192 |
193 | 196 | 如需更換語言請自行複製後修改 second_lang 參數,預設為英文。
197 | 作者: Neurogram-R
198 | 來源: https://github.com/Neurogram-R/Surge 199 |
200 |
201 |
202 |
203 | 206 | 直接指定 IP,開圖就會變快。
207 | 作者: hirakujira
208 | 來源: https://github.com/Black-Magic-Lab/Surgee 209 |
210 |
211 |
212 |
213 | 216 | 每天早上自動簽到 + 開蝦幣寶箱 + 開免運寶箱。第一次使用前先到「我的」→「蝦皮實名認證」取得 token。
217 | 作者: hirakujira & jkgtw
218 | 來源: https://github.com/Black-Magic-Lab/Surge 219 |
220 |
221 |
222 |
223 | 226 | 每天自動做水滴任務,接近隔天午夜會自動把今天任務的獎勵全部用掉。每小時自動澆水,每次換作物後需先手動澆水一次,讓 Surge 紀錄目前的作物。需要同時開啟每日自動簽到以獲得 token。
227 | 作者: hirakujira
228 | 來源: https://github.com/Black-Magic-Lab/Surge 229 |
230 |
231 |
232 |
233 | 236 | v3.1.0 以下:登入後先去個人資料那邊按修改,但什麼都不要改,直接儲存就可以獲得 token。
v3.1.0 以上:根據網站教學文搭配 mcdonalds_set_token.js 手動設定 token。

237 | 作者: hirakujira
238 | 來源: https://github.com/Black-Magic-Lab/Surge 239 |
240 |
241 |
242 |
243 | 246 | 僅限 3.1.0 以下使用。
247 | 作者: hirakujira
248 | 來源: https://github.com/Black-Magic-Lab/Surge 249 |
250 |
251 |
252 |
253 | 256 | 博客來自動簽到,第一次使用先去「會員專區」→「我的優惠券」頁面取得 token
257 | 作者: jkgtw
258 | 來源: https://github.com/jkgtw/Surge 259 |
260 |
261 |
262 |
263 | 266 | 每天自動簽到,第一次使用請先手動簽到一次以取得token
267 | 作者: jkgtw
268 | 來源: https://github.com/jkgtw/Surge 269 |
270 |
271 |
272 |
273 | 276 | 每天自動簽到,第一次使用請先手動到個人資訊 → 累計點數以取得token。
277 | 作者: coreanige
278 | 來源: https://github.com/coreanige/surge 279 |
280 |
281 |
282 |
283 | 286 | 每天早上自動簽到。第一次使用前先到「天天簽到」→「活動紀錄」取得 Cookie。
287 | 作者: hirakujira
288 | 來源: https://github.com/Black-Magic-Lab/Surge 289 |
290 |
291 |
292 |
293 | 296 | 操作步驟:
1. 啟用本模組。
2. 登出「遠傳心生活」,再重新登入。
3. 登入後會跳出「成功取得 token 」通知。
4. 關閉本模組。
5. 刪除遠傳心生活 App。
6. 重新安裝 App 並登入。
未按照步驟操作會造成登入衝突導致失效。

297 | 作者: hirakujira
298 | 來源: https://github.com/Black-Magic-Lab/Surge 299 |
300 |
301 |
302 |
303 | 306 | 請先確定關閉取得 token 功能。
307 | 作者: hirakujira
308 | 來源: https://github.com/Black-Magic-Lab/Surge 309 |
310 |
311 |
312 |
313 | 316 | 利用固定戳 SpeedTest 來解除限速,每兩分鐘執行一次,請勿重複安裝類似功能模組
317 | 作者: jkgtw
318 | 來源: https://github.com/jkgtw/Surge 319 |
320 |
321 |
322 |
323 | 326 | 利用固定戳 SpeedTest 來解除限速,在「晚上八點到凌晨兩點」每兩分鐘執行一次,請勿重複安裝類似功能模組
327 | 作者: jkgtw
328 | 來源: https://github.com/jkgtw/Surge 329 |
330 |
331 |
332 |
333 | 336 | 請用非 Safari 瀏覽器,在官網將您要的機型加入購物車,獲得機型代號後即可關閉此模組。
337 | 作者: hirakujira
338 | 來源: https://github.com/Black-Magic-Lab/Surge 339 |
340 |
341 |
342 |
343 | 346 | 每分鐘檢查一次直營店庫存。
347 | 作者: hirakujira
348 | 來源: https://github.com/Black-Magic-Lab/Surge 349 |
350 |
351 |
352 |
353 | 356 | 在可以登入 Ozan 的裝置安裝此模組。開啟 Ozan 看到跳 Surge 通知後,到 Surge 腳本編輯器,選擇左下角齒輪 → $persistentStore → OzanUUID,然後複製所有內容。
357 | 作者: hirakujira
358 | 來源: https://github.com/Black-Magic-Lab/Surge 359 |
360 |
361 |
362 |
363 | 366 | 在不能登入 Ozan 的裝置安裝此模組。複製可登入裝置的 OzanUUID 後,修改 ozan_set_uuid.js 的內容,並執行腳本即可使用。
367 | 作者: hirakujira
368 | 來源: https://github.com/Black-Magic-Lab/Surge 369 |
370 |
371 |
372 | 373 |
374 |
375 |
376 | 379 | 顯示 IP、ISP、地區等網路資訊
380 | 作者: Nebulosa-Cat & hirakujira
381 | 來源: https://github.com/Nebulosa-Cat/Surge 382 |
383 |
384 |
385 |
386 | 389 | 串流媒體跨區測試
390 | 作者: @Helge_0x00 & ptntp
391 | 來源: https://github.com/ptntp/self-rule 392 |
393 |
394 |
395 |
396 | 399 | 顯示目前是否成功連上 FutaDNS
400 | 作者: hirakujira
401 | 來源: https://github.com/FutaGuard/FutaFilter 402 |
403 |
404 |
405 |
406 | 409 | 顯示最新幣安報價,請先去 binance_price_set_symbol.js 設定要顯示的幣種。
410 | 作者: hirakujira
411 | 來源: https://github.com/Black-Magic-Lab/Surge 412 |
413 |
414 |
415 |
416 | 419 | 可以看到一堆幹話喔 :)
420 | 作者: hirakujira
421 | 來源: https://github.com/Black-Magic-Lab/Surge 422 |
423 |
424 |
425 | 426 |
427 |
428 |
429 | 432 | 安裝後點擊下方的「功能檢查頁面」觀看結果
433 | 作者: hirakujira
434 | 來源: https://github.com/Black-Magic-Lab/Surge 435 |
436 |
437 |
438 |
439 | 442 | 點我觀看結果
443 | 作者: hirakujira
444 | 來源: https://github.com/Black-Magic-Lab/Surge 445 |
446 |
447 |
448 |
449 | 452 | 使用電腦取得麥當勞 App token
453 | 作者: hirakujira
454 | 來源: https://github.com/Black-Magic-Lab/Surge 455 |
456 |
457 |
458 |
459 | 460 | 461 |

所有商標之所有權歸於該商標的所有者。

462 |

這裡沒有黑魔法 (´・ω・`)

463 | 464 |
465 |
466 | 519 | 520 | 545 | 546 | -------------------------------------------------------------------------------- /modules/binance_price.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=幣安價格面板 2 | #!desc=顯示最新幣安報價,請先去「[幣安價格]設定幣種」設定要顯示的幣種。 v20230203 3 | #!system=ios 4 | 5 | [Script] 6 | [幣安價格]資訊面板=script-path=https://kinta.ma/surge/scripts/binance_price_panel.js,type=generic 7 | [幣安價格]設定幣種=script-path=https://kinta.ma/surge/scripts/binance_price_set_symbol.js,type=generic 8 | 9 | [Panel] 10 | BinancePricePanel=title="",content="",style=info,script-name=[幣安價格]資訊面板,update-interval=1 -------------------------------------------------------------------------------- /modules/check_futadns.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=FutaDNS 檢查面板 2 | #!desc=檢查是否已連上 FutaDNS 3 | #!system=ios 4 | 5 | [Panel] 6 | CheckFutaDNSPanel=title="",content="",style=info,script-name=check_futadns.js,update-interval=1 7 | 8 | [Script] 9 | check_futadns.js=script-path=https://kinta.ma/surge/scripts/check_futadns.js,type=generic -------------------------------------------------------------------------------- /modules/disney_plus_dualsub.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=Disney+ 雙語字幕 2 | #!desc=如需更換語言請自行複製後修改 second_lang 參數,預設為英文 v20220215 3 | 4 | [Script] 5 | DisneyPlus-Dualsub.js = type=http-response,pattern=https:\/\/.+media.dssott.com\/ps01\/disney\/.+(\.vtt|-all-.+\.m3u8.*),requires-body=true,max-size=307200,script-path=https://raw.githubusercontent.com/Neurogram-R/Surge/master/DisneyPlus-Dualsub.js 6 | 7 | [MITM] 8 | hostname = %APPEND% *.media.dssott.com -------------------------------------------------------------------------------- /modules/facebook_block_sdk.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=Facebook SDK 阻擋 2 | #!desc=不影響 Facebook App 本身,但會禁止第三方 App 使用 Facebook 登入。iOS 15 由於系統預設行為變動,目前無法將 Facebook 自家 App 設為白名單。 v20210927 3 | #!system=ios 4 | 5 | [Rule] 6 | AND,((NOT,((USER-AGENT,*FBAN/FBIOS;*))), (NOT,((USER-AGENT,*FBAN/FBPageAdmin;*))), (NOT,((USER-AGENT,*FBAN/MessengerLiteForiOS;*))), (NOT,((USER-AGENT,Instagram*))), (NOT,((USER-AGENT,Messenger*))), (DOMAIN,graph.facebook.com)),REJECT -------------------------------------------------------------------------------- /modules/fet_checkin.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=遠傳心生活自動簽到 2 | #!desc=請先確定關閉取得 token 功能。 v20220114 3 | 4 | [Script] 5 | [遠傳心生活]每日簽到 = type=cron, cronexp="25 0 * * *", script-path=https://kinta.ma/surge/scripts/fet_checkin.js, wake-system=1, timeout=10 -------------------------------------------------------------------------------- /modules/fet_get_token.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=遠傳心生活取得 token 2 | #!desc=操作步驟:1. 啟用本模組。2. 登出「遠傳心生活」,再重新登入。3. 登入後會跳出「成功取得 token 」通知。4. 關閉本模組。5. 刪除遠傳心生活 App。6. 重新安裝 App 並登入。未按照步驟操作會造成登入衝突導致失效。 v20230114 3 | 4 | [Script] 5 | [遠傳心生活]取得token = type=http-response, pattern=^https\:\/\/dspapi\.fetnet\.net:1443\/dsp\/token-proxy\/super-app\/token$, script-path=https://kinta.ma/surge/scripts/fet_token.js, requires-body=true 6 | 7 | [MITM] 8 | hostname = %APPEND% dspapi.fetnet.net:1443 -------------------------------------------------------------------------------- /modules/futadns.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=FutaDNS (futa.gg) 2 | #!desc=啟用 FutaDNS 3 | 4 | [General] 5 | doh-server = https://doh.futa.gg/dns-query -------------------------------------------------------------------------------- /modules/hiraku_info.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=皮樂今天翻車了嗎 2 | #!desc= 3 | #!system=ios 4 | 5 | [Panel] 6 | HirakuInfoPanel=title="",content="",style=info,script-name=hiraku_info_panel.js,update-interval=1 7 | 8 | [Script] 9 | hiraku_info_panel.js=script-path=https://kinta.ma/surge/scripts/hiraku_info_panel.js,type=generic -------------------------------------------------------------------------------- /modules/iphone_check_store.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=iPhone/iPad 直營店庫存檢查 2 | #!desc=每分鐘檢查一次直營店庫存。 v20220910 3 | 4 | [Script] 5 | cron "* * * * *" script-path=https://kinta.ma/surge/scripts/iphone_check_store.js, wake-system=1, timeout=10 -------------------------------------------------------------------------------- /modules/iphone_get_model.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=iPhone/iPad 直營店庫存檢查 - 獲得機型代碼 2 | #!desc=請用非 Safari 瀏覽器,在官網將您要的機型加入購物車,獲得機型代號後即可關閉此模組。 v20210929 3 | 4 | [Script] 5 | iphone_get_model.js = type=http-request,pattern=^https:\/\/www\.apple\.com\/tw\/shop\/buy-(iphone|ipad)\/,script-path=https://kinta.ma/surge/scripts/iphone_get_model.js,script-update-interval=-1 6 | 7 | [MITM] 8 | hostname = %APPEND% www.apple.com -------------------------------------------------------------------------------- /modules/line_ad.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=LINE 廣告阻擋 2 | #!desc=本地有快取,需一兩天才會消失,或者直接調時間。 v20200627 3 | #!system=ios 4 | 5 | [Rule] 6 | URL-REGEX,^https:\/\/a\.line\.me\/er\/la(ss|dg)\/v1\/event\/image,REJECT-TINYGIF 7 | URL-REGEX,^https:\/\/sch\.line\.me\/api\/v1\/ads$,REJECT 8 | 9 | [MITM] 10 | hostname = %APPEND% a.line.me, sch.line.me -------------------------------------------------------------------------------- /modules/mcdonalds.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=麥當勞自動簽到 2 | #!desc=每天早上自動簽到 + 參加活動。 v20220722 3 | 4 | [Script] 5 | cron "13 0 * * *" script-path=https://kinta.ma/surge/scripts/mcdonalds_checkin.js, wake-system=1, timeout=5 6 | cron "14 0 * * *" script-path=https://kinta.ma/surge/scripts/mcdonalds_luckydraw.js, wake-system=1, timeout=10 7 | cron "14 0 * * 3,6" script-path=https://kinta.ma/surge/scripts/mcdonalds_question.js, wake-system=1, timeout=10 8 | cron "15 0 * * 3,6" script-path=https://kinta.ma/surge/scripts/mcdonalds_media_checkin.js, wake-system=1, timeout=30 9 | -------------------------------------------------------------------------------- /modules/mcdonalds_token.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=麥當勞取得 token 2 | #!desc=此模組優先度必須比「麥當勞繞過版本檢查」高,才會生效。v3.1.0 以下:登入後先去個人資料那邊按修改,但什麼都不要改,直接儲存就可以獲得 token。v3.1.0 以上:根據網站教學文搭配「[麥當勞]手動設定token」腳本以設定 token。 v20220722 3 | 4 | [Script] 5 | [麥當勞]取得token = type=http-request,pattern=^https:\/\/mcdapi\.mcddailyapp\.com\.tw\/McDonaldAPI\/member\/updateRegisterInfo$,script-path=https://kinta.ma/surge/scripts/mcdonalds_token.js,script-update-interval=-1 6 | [麥當勞]手動設定token = script-path=https://kinta.ma/surge/scripts/mcdonalds_set_token.js,script-update-interval=-1 7 | 8 | [MITM] 9 | hostname = %APPEND% mcdapi.mcddailyapp.com.tw 10 | -------------------------------------------------------------------------------- /modules/mcdonalds_use_point.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=麥當勞自動簽到(花費積分) 2 | #!desc=每天早上自動簽到 + 參加活動。此版本會自動參加花費積分的活動。v20220722 3 | 4 | [Script] 5 | cron "13 0 * * *" script-path=https://kinta.ma/surge/scripts/mcdonalds_checkin.js, wake-system=1, timeout=5 6 | cron "14 0 * * *" script-path=https://kinta.ma/surge/scripts/mcdonalds_luckydraw.js, wake-system=1, timeout=10 7 | cron "14 0 * * 3,6" script-path=https://kinta.ma/surge/scripts/mcdonalds_question.js, wake-system=1, timeout=10 8 | cron "15 0 * * 3,6" script-path=https://kinta.ma/surge/scripts/mcdonalds_media_checkin.js, wake-system=1, timeout=30 -------------------------------------------------------------------------------- /modules/mcdonalds_version_bypass.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=麥當勞繞過版本檢查 2 | #!desc=僅限 3.1.0 以下使用。 v20220728 3 | 4 | [Script] 5 | mcdonalds_version_bypass.js = type=http-request,pattern=^https:\/\/mcdapi\.mcddailyapp\.com\.tw\/McDonaldAPI\/(.*),script-path=https://kinta.ma/surge/scripts/mcdonalds_version_bypass.js,script-update-interval=-1 6 | 7 | [MITM] 8 | hostname = %APPEND% mcdapi.mcddailyapp.com.tw 9 | -------------------------------------------------------------------------------- /modules/momo_checkin.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=Momo 每日簽到 2 | #!desc=每天早上自動簽到。第一次使用前先到「首頁」→「滑動 Icon」→「天天簽到」→「活動紀錄」取得 Cookie v20220309 3 | 4 | [Script] 5 | cron "16 0 * * *" script-path=https://kinta.ma/surge/scripts/momo_checkin.js, wake-system=1, timeout=10 6 | momo_checkin_info.js = type=http-request,pattern=^https:\/\/event\.momoshop\.com\.tw\/punch\.PROMO$,script-path=https://kinta.ma/surge/scripts/momo_checkin_info.js,requires-body=true,script-update-interval=-1 7 | 8 | [MITM] 9 | hostname = %APPEND% event.momoshop.com.tw -------------------------------------------------------------------------------- /modules/netflix_rating.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=Netflix IMDB 評分 2 | #!desc=在 Netflix App 顯示評分。 v20200627 3 | #!system=ios 4 | 5 | [Script] 6 | http-request ^https?://ios\.prod\.ftl\.netflix\.com/iosui/user/.+path=%5B%22videos%22%2C%\d+%22%2C%22summary%22%5D script-path=https://kinta.ma/surge/scripts/netflix_rating.js 7 | http-response ^https?://ios\.prod\.ftl\.netflix\.com/iosui/user/.+path=%5B%22videos%22%2C%\d+%22%2C%22summary%22%5D requires-body=1,script-path=https://kinta.ma/surge/scripts/netflix_rating.js 8 | 9 | [MITM] 10 | hostname = %APPEND% ios.prod.ftl.netflix.com -------------------------------------------------------------------------------- /modules/ozan_fake_uuid.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=Ozan 偽造 UUID 2 | #!desc=在原本不能登入 Ozan 的裝置安裝此模組。複製原本裝置的 OzanUUID 後,修改「[Ozan]設定新裝置UUID」的內容,並執行腳本即可使用。 v20221129 3 | 4 | [Script] 5 | [Ozan]設定新裝置UUID = script-path=https://kinta.ma/surge/scripts/ozan_set_uuid.js 6 | [Ozan]偽造新裝置UUID = type=http-request, pattern=^https://(op-prod-tr\.ozan|secure\.ozanlimited).com/api/.+, script-path=https://kinta.ma/surge/scripts/ozan_fake_uuid.js 7 | 8 | [MITM] 9 | hostname = %APPEND% op-prod-tr.ozan.com, secure.ozanlimited.com 10 | -------------------------------------------------------------------------------- /modules/ozan_get_uuid.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=Ozan 取得 UUID 2 | #!desc=在可以登入 Ozan 的裝置安裝此模組。開啟 Ozan 看到跳 Surge 通知後,到 Surge 腳本編輯器,選擇左下角齒輪 → $persistentStore → OzanUUID,然後複製所有內容。 v20221125 3 | 4 | [Script] 5 | [Ozan]取得舊裝置UUID = type=http-request, pattern=^https:\/\/(op-prod-tr\.ozan|secure\.ozanlimited)\.com\/api\/users\/me$, script-path=https://kinta.ma/surge/scripts/ozan_get_uuid.js 6 | 7 | [MITM] 8 | hostname = %APPEND% op-prod-tr.ozan.com, secure.ozanlimited.com 9 | -------------------------------------------------------------------------------- /modules/ptt.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=PTT 跳過年齡驗證 2 | #!desc=需開啟 Rewrite 跟 MITM。 v20200627 3 | #!system=ios 4 | 5 | [Header Rewrite] 6 | ^https://www.ptt.cc header-add Cookie over18=1 7 | 8 | [MITM] 9 | hostname = %APPEND% www.ptt.cc -------------------------------------------------------------------------------- /modules/ptt_imgur_fix.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=PTT Imgur 修正 2 | #!desc=修正 PTT 網頁版無法顯示 Imgur 圖片的問題 v20210816 3 | 4 | [Header Rewrite] 5 | ^https://imgur.com header-del Referer 6 | ^https://m.imgur.com header-del Referer 7 | ^https://i.imgur.com header-del Referer 8 | 9 | [MITM] 10 | hostname = %APPEND% imgur.com, m.imgur.com, i.imgur.com -------------------------------------------------------------------------------- /modules/shein_checkin.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=SHEIN 每日自動簽到 2 | #!desc=每天自動簽到,第一次使用請先手動簽到一次以取得token。 v20221126 3 | 4 | [Script] 5 | [SHEIN]每日簽到 = type=cron, cronexp="22 0 * * *", script-path=https://kinta.ma/surge/scripts/shein_checkin.js, wake-system=1, timeout=5 6 | [SHEIN]取得token = type=http-request, pattern=^https:\/\/api\-shein\.shein\.com\/h5\/check_in\?.*, script-path=https://kinta.ma/surge/scripts/shein_token.js 7 | 8 | [MITM] 9 | hostname = %APPEND% api-shein.shein.com -------------------------------------------------------------------------------- /modules/shopee_auto_crop.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=蝦蝦果園自動搶種子 2 | #!desc=使用前先手動載入「[蝦蝦果園]設定搶種子名稱」,按照提示設定作物名稱並執行後,再去隨便種一個作物以取得 token。往後當果園沒有作物時就會自動搶設定的種子。 v20230210 3 | 4 | [Script] 5 | [蝦蝦果園]自動種植 = type=cron, cronexp="0 0 * * *", script-path=https://kinta.ma/surge/scripts/shopee_auto_crop.js, wake-system=1, timeout=30 6 | [蝦蝦果園]設定搶種子名稱 = script-path=https://kinta.ma/surge/scripts/shopee_set_auto_crop_seed_name.js 7 | 8 | [MITM] 9 | hostname = %APPEND% games.shopee.tw -------------------------------------------------------------------------------- /modules/shopee_auto_water.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=蝦蝦果園自動化 2 | #!desc=每天自動做水滴任務,接近隔天午夜會自動把今天任務的獎勵全部用掉。每小時自動澆水,每次換作物後需先手動澆水一次,讓 Surge 紀錄目前的作物。道具商店水滴需先手動領一次,往後會自動領。需要同時開啟每日自動簽到以獲得 token。 v20230502 3 | 4 | [Script] 5 | [蝦蝦果園]自動收成 = type=cron, cronexp="0 * * * *", script-path=https://kinta.ma/surge/scripts/shopee_auto_harvest.js, wake-system=1, timeout=5 6 | [蝦蝦果園]打卡任務 = type=cron, cronexp="10 0,4,8 * * *", script-path=https://kinta.ma/surge/scripts/shopee_water_checkin.js, wake-system=1, timeout=5 7 | [蝦蝦果園]購買免費道具 = type=cron, cronexp="19 0,4 * * *", script-path=https://kinta.ma/surge/scripts/shopee_water_buy_free_item.js, wake-system=1, timeout=5 8 | [蝦蝦果園]自動澆水 = type=cron, cronexp="14 * * * *", script-path=https://kinta.ma/surge/scripts/shopee_auto_water.js, wake-system=1, timeout=5 9 | [蝦蝦果園]每日簽到獎勵 = type=cron, cronexp="50 23 * * *", script-path=https://kinta.ma/surge/scripts/shopee_water_signin_bundle.js, wake-system=1, timeout=5 10 | [蝦蝦果園]領取水滴任務獎勵 = type=cron, cronexp="50 23 * * *", script-path=https://kinta.ma/surge/scripts/shopee_water_mission_claim.js, wake-system=1, timeout=15 11 | [蝦蝦果園]道具商店水滴 = type=cron, cronexp="50 15,19,23 * * *", script-path=https://kinta.ma/surge/scripts/shopee_grocery_store_water.js, wake-system=1, timeout=5 12 | [蝦蝦果園]品牌商店水滴 = type=cron, cronexp="20 20,21,22,23 * * *", script-path=https://kinta.ma/surge/scripts/shopee_brand_store_water.js, wake-system=1, timeout=180 13 | [蝦蝦果園]取得作物資料 = type=http-request, pattern=^https:\/\/games\.shopee\.tw\/farm\/api\/orchard\/crop\/water(.*),script-path=https://kinta.ma/surge/scripts/shopee_get_crop.js, requires-body=true 14 | [蝦蝦果園]取得道具商店token = type=http-request, pattern=^https:\/\/games\.shopee\.tw\/farm\/api\/grocery_store\/(rn_)?claim,script-path=https://kinta.ma/surge/scripts/shopee_get_grocery_store_token.js, requires-body=true 15 | 16 | [MITM] 17 | hostname = %APPEND% games.shopee.tw -------------------------------------------------------------------------------- /modules/shopee_brand_water.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=蝦蝦果園品牌水滴 2 | #!desc=點一下品牌商店後自動獲得水滴 v20220308(未成功) 3 | 4 | [Script] 5 | [蝦蝦果園]自動獲得品牌水滴 = type=http-request, pattern=^https:\/\/mall\.shopee\.tw\/api\/v4\/shop\/get_shop_tab$, script-path=https://kinta.ma/surge/blackmagic/scripts/shopee_get_friend_crop_and_upload.js, requires-body=true 6 | 7 | [MITM] 8 | hostname = %APPEND% games.shopee.tw -------------------------------------------------------------------------------- /modules/shopee_checkin.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=蝦皮每日自動簽到 2 | #!desc=每天早上自動簽到 + 開蝦幣寶箱 + 開免運寶箱。第一次使用前先到「我的」→「蝦皮實名認證」取得 token。簽到功能必須先手動簽到一次。 v20230608.1 3 | #[蝦皮]每日簽到 = type=cron, cronexp="16 0 * * *", script-path=https://kinta.ma/surge/scripts/shopee_checkin.js, wake-system=1, timeout=5 4 | 5 | [Script] 6 | [蝦皮]每日簽到 = type=cron, cronexp="16 0 * * *", script-path=https://kinta.ma/surge/scripts/shopee_checkin.js, wake-system=1, timeout=5 7 | [蝦皮]更新token = type=cron, cronexp="10 */8 * * *", script-path=https://kinta.ma/surge/scripts/shopee_update_token.js, wake-system=1, timeout=10 8 | [蝦皮]蝦幣寶箱 = type=cron, cronexp="17 0 * * *", script-path=https://kinta.ma/surge/scripts/shopee_coin_lucky_draw.js, wake-system=1, timeout=10 9 | [蝦皮]免運寶箱 = type=cron, cronexp="17 0 * * *", script-path=https://kinta.ma/surge/scripts/shopee_shipping_lucky_draw.js, wake-system=1, timeout=10 10 | [蝦皮]取得token = type=http-request, pattern=^https:\/\/mall\.shopee\.tw\/api\/v2\/user\/profile\/get\/$, script-path=https://kinta.ma/surge/scripts/shopee_token.js 11 | [蝦皮]取得簽到資訊 = type=http-request, pattern=^https:\/\/games-dailycheckin\.shopee\.tw\/mkt\/coins\/api\/v2\/checkin_new$, script-path=https://kinta.ma/surge/scripts/shopee_get_checkin.js, requires-body=true 12 | 13 | [MITM] 14 | hostname = %APPEND% mall.shopee.tw, games-dailycheckin.shopee.tw -------------------------------------------------------------------------------- /modules/surge_check.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=Surge 功能檢查 2 | #!desc=前往 https://kinta.ma/surge,找到「功能檢查頁面」,點擊頁面觀看結果 v20220114 3 | #!system=ios 4 | 5 | [Header Rewrite] 6 | ^https:\/\/kinta\.ma\/surge\/utils\/surge_check\/$ header-add X-Hiraku-Rewrite 1 7 | 8 | [Script] 9 | [Surge功能檢查]修改請求 = type=http-request, pattern=^https:\/\/kinta\.ma\/surge\/utils\/surge_check\/$, script-path=https://kinta.ma/surge/scripts/surge_check.js 10 | [Surge功能檢查]修改回應 = type=http-response, pattern=^https:\/\/kinta\.ma\/surge\/utils\/surge_check\/$, script-path=https://kinta.ma/surge/scripts/surge_check.js, requires-body=true 11 | 12 | [MITM] 13 | hostname = %APPEND% kinta.ma -------------------------------------------------------------------------------- /modules/twitter_image.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=Twitter Image Fix 2 | #!desc=解決 Twitter 圖片無法開啟或開啟緩慢的問題 v20211007 3 | 4 | [Host] 5 | pbs.twimg.com = 23.1.106.237 6 | abs.twimg.com = 23.1.106.237 -------------------------------------------------------------------------------- /modules/whosis_sayings.sgmodule: -------------------------------------------------------------------------------- 1 | #!name=是誰在講幹話 2 | #!desc=https://github.com/tasi788/Whosis-Sayings 3 | #!system=ios 4 | 5 | [Panel] 6 | WhosisSayingsPanel=title="",content="",style=info,script-name=whosis_sayings_panel.js,update-interval=1 7 | 8 | [Script] 9 | whosis_sayings_panel.js=script-path=https://kinta.ma/surge/scripts/whosis_sayings_panel.js,type=generic -------------------------------------------------------------------------------- /scripts/binance_price_panel.js: -------------------------------------------------------------------------------- 1 | let symbols = null; 2 | let message = { 3 | 'title': '最新幣價', 4 | 'content': '', 5 | 'icon': 'bitcoinsign.circle', 6 | 'icon-color': '#EF8F1C', 7 | }; 8 | 9 | function handleError(error) { 10 | if (Array.isArray(error)) { 11 | console.log(`❌ ${error[0]} ${error[1]}`); 12 | return { 13 | title: '最新幣價', 14 | content: `❌ ${error[0]} ${error[1]}`, 15 | icon: 'simcard', 16 | 'icon-color': '#CB1B45', 17 | } 18 | } else { 19 | console.log(`❌ ${error}`); 20 | return { 21 | title: '最新幣價', 22 | content: `❌ ${error}`, 23 | icon: 'simcard', 24 | 'icon-color': '#CB1B45', 25 | } 26 | } 27 | } 28 | 29 | async function getSymbols() { 30 | return new Promise((resolve, reject) => { 31 | try { 32 | symbols = JSON.parse($persistentStore.read('BinancePriceSymbols') || JSON.stringify(['BTC', 'ETH'])); 33 | return resolve(); 34 | } catch (error) { 35 | return reject(['無法解析幣種', error]); 36 | } 37 | }) 38 | } 39 | 40 | async function fetchPrice(symbol) { 41 | return new Promise((resolve, reject) => { 42 | try { 43 | const request = { 44 | url: `https://api.binance.com/api/v3/ticker/price?symbol=${symbol}USDT`, 45 | }; 46 | $httpClient.get(request, function (error, response, data) { 47 | if (error) { 48 | return reject([`無法取得 ${symbol}/USDT 交易對`, error]); 49 | } else { 50 | if (response.status === 200) { 51 | const price = JSON.parse(data).price; 52 | message.content = `${message.content}${symbol}: $${Number(price).toFixed(2)}\n`; 53 | } 54 | return resolve(); 55 | } 56 | }); 57 | } catch (error) { 58 | return reject([`無法取得 ${symbol}/USDT 交易對`, error]); 59 | } 60 | }) 61 | } 62 | 63 | (async() => { 64 | try { 65 | console.log('ℹ️ 幣安報價資訊面板 v20230130.1'); 66 | await getSymbols(); 67 | for (const symbol of symbols) { 68 | await fetchPrice(symbol); 69 | } 70 | message.content = message.content.slice(0, -1); 71 | $done(message); 72 | } catch (error) { 73 | $done(handleError(error)); 74 | } 75 | $done(handleError('未知錯誤')); 76 | fetchPrice(0); 77 | })(); 78 | -------------------------------------------------------------------------------- /scripts/binance_price_set_symbol.js: -------------------------------------------------------------------------------- 1 | const symbols = ['BTC', 'ETH']; 2 | 3 | // 腳本呼叫幣安 API,因此請輸入幣安有上架的幣種。 4 | 5 | //================================================================ 6 | function binanceSymbolsNotify(subtitle = '', message = '') { 7 | $notification.post('最新幣價面板', subtitle, message); 8 | }; 9 | $persistentStore.write(JSON.stringify(symbols), "BinancePriceSymbols"); 10 | binanceSymbolsNotify( 11 | '設定完成 ✅', 12 | '' 13 | ) 14 | $done({}); 15 | -------------------------------------------------------------------------------- /scripts/check_futadns.js: -------------------------------------------------------------------------------- 1 | const { v4, v6 } = $network; 2 | 3 | let errorMessage = { 4 | title: 'FutaDNS', 5 | content: '無已被設定的 FutaGuard DNS 伺服器', 6 | icon: 'xmark.shield.fill', 7 | 'icon-color': '#FE6245', 8 | }; 9 | 10 | const successMessage = { 11 | title: 'FutaDNS', 12 | content: '已指定的 FutaGuard DNS 伺服器\n正在正確地運作', 13 | icon: 'checkmark.shield.fill', 14 | 'icon-color': '#1FCFB4', 15 | }; 16 | 17 | (() => { 18 | if (!v4.primaryAddress && !v6.primaryAddress) { 19 | errorMessage.content = '\n錯誤:未連上網路'; 20 | $done(errorMessage); 21 | } else { 22 | $httpClient.get('https://check.futa.gg', function (error, response, data) { 23 | if (error) { 24 | errorMessage.content += `\n錯誤: ${error}` 25 | $done(errorMessage); 26 | } 27 | if (response.status == 200) { 28 | if (data) { 29 | if (data.includes('正在正確地運作')) { 30 | $done(successMessage); 31 | } else { 32 | $done(errorMessage); 33 | } 34 | } else { 35 | errorMessage += `\n錯誤:無法連接到檢查頁面` 36 | $done(errorMessage); 37 | } 38 | } else { 39 | errorMessage.content += `\n錯誤:無法連接到檢查頁面 ${response.status}` 40 | } 41 | $done(errorMessage); 42 | }); 43 | } 44 | })() 45 | 46 | -------------------------------------------------------------------------------- /scripts/fet_token.js: -------------------------------------------------------------------------------- 1 | let showNotification = true; 2 | let config = null; 3 | 4 | function surgeNotify(subtitle = '', message = '') { 5 | $notification.post('遠傳心生活取得 token', subtitle, message, { 'url': '' }); 6 | }; 7 | 8 | function getSaveObject(key) { 9 | const string = $persistentStore.read(key); 10 | return !string || string.length === 0 ? {} : JSON.parse(string); 11 | } 12 | 13 | function isEmptyObject(obj) { 14 | return Object.keys(obj).length === 0 && obj.constructor === Object ? true : false; 15 | } 16 | 17 | function isManualRun(checkRequest = false, checkResponse = false) { 18 | if (checkRequest) { 19 | return typeof $request === 'undefined' || ($request.body && JSON.parse($request.body).foo === 'bar'); 20 | } 21 | if (checkResponse) { 22 | return typeof $response === 'undefined' || ($response.body && JSON.parse($response.body).foo === 'bar'); 23 | } 24 | return false; 25 | } 26 | 27 | async function getToken() { 28 | return new Promise((resolve, reject) => { 29 | try { 30 | if ($response.status !== 200) { 31 | return reject(['發生錯誤 ‼️', 'token 已失效或過期']); 32 | } else { 33 | const obj = JSON.parse($response.body); 34 | if (obj && obj.access_token && obj.refresh_token) { 35 | const info = { 36 | 'accessToken': obj.access_token, 37 | 'refreshToken': obj.refresh_token, 38 | 'userId': 0 39 | }; 40 | config = info; 41 | const save = $persistentStore.write(JSON.stringify(info, null, 4), 'FetSuperAppInfo'); 42 | if (!save) { 43 | return reject(['保存失敗 ‼️', '請刪除「遠傳心生活」,重新安裝並登入後再試一次。']); 44 | } else { 45 | return resolve(); 46 | } 47 | } else { 48 | return reject(['保存失敗 ‼️', '找不到 token,請刪除「遠傳心生活」,重新安裝並登入後再試一次。']); 49 | } 50 | } 51 | } catch (error) { 52 | return reject(['保存失敗 ‼️', error]); 53 | } 54 | }); 55 | } 56 | 57 | function getUserLevel() { 58 | return new Promise((resolve, reject) => { 59 | try { 60 | const request = { 61 | url: 'https://dspapi.fetnet.net:1443/dsp/api/superapp-middle/fcms/member/level?client_id=78df8f6d-eb06-4405-a0c9-b56c45335307', 62 | headers: { 63 | Authorization: 'Bearer ' + config.accessToken, 64 | } 65 | }; 66 | 67 | $httpClient.get(request, function (error, response, data) { 68 | if (error) { 69 | return reject(['保存使用者 ID 失敗 ‼️', '連線錯誤']); 70 | } else { 71 | if (response.status === 200) { 72 | const obj = JSON.parse(data); 73 | if (obj.result?.responseBody?.fetUnifyId) { 74 | config.userId = obj.result.responseBody.fetUnifyId; 75 | const save = $persistentStore.write(JSON.stringify(config, null, 4), 'FetSuperAppInfo'); 76 | if (save) { 77 | return resolve(); 78 | } else { 79 | return reject(['保存使用者 ID 失敗 ‼️', '無法儲存 ID']); 80 | } 81 | } else { 82 | return reject(['保存使用者 ID 失敗 ‼️', '找不到使用者 ID']); 83 | } 84 | } else { 85 | return reject(['保存使用者 ID 失敗 ‼️', response.status]); 86 | } 87 | } 88 | }); 89 | } catch (error) { 90 | return reject(['保存使用者 ID 失敗 ‼️', error]); 91 | } 92 | }); 93 | } 94 | 95 | (async () => { 96 | console.log('ℹ️ 遠傳心生活取得 token v20230119.2'); 97 | try { 98 | if (isManualRun(false, true)) { 99 | throw '請勿手動執行此腳本'; 100 | } 101 | 102 | await getToken(); 103 | console.log('✅ token 保存成功'); 104 | await getUserLevel(); 105 | console.log('✅ 使用者 ID 保存成功'); 106 | surgeNotify( 107 | '保存成功 🍪', 108 | '請先關閉本模組,再刪除「遠傳心生活」並重新安裝,以避免 session 衝突。' 109 | ); 110 | $done({}); 111 | } catch (error) { 112 | console.log(`❌ ${error[0]} ${error[1]}`); 113 | if (showNotification) { 114 | shopeeNotify(error[0], error[1]); 115 | } 116 | } 117 | $done({}); 118 | })(); 119 | -------------------------------------------------------------------------------- /scripts/hiraku_info_panel.js: -------------------------------------------------------------------------------- 1 | $done({ 2 | title: '皮樂今天翻車了嗎?', 3 | content: '翻車了 😫', 4 | icon: 'pc', 5 | }); -------------------------------------------------------------------------------- /scripts/iphone_check_store.js: -------------------------------------------------------------------------------- 1 | const debug = false; 2 | const model = $persistentStore.read('iPhoneModel'); 3 | 4 | var checkRequest = { 5 | url: '', 6 | headers: { 7 | 'x-ma-pcmh': 'REL-5.17.0', 8 | 'X-DeviceConfiguration': 'ss=3.00;dim=1170x2532;m=iPhone;v=iPhone14,2;vv=5.17;sv=15.3' 9 | }, 10 | } 11 | 12 | 13 | const stores = ['R713', 'R694']; 14 | 15 | function checkStore(model, index) { 16 | const store = stores[index]; 17 | checkRequest['url'] = 'https://mobileapp.apple.com/merch/p/tw/pickup/quote/' + model + '?partNumber=' + model + '&store=' + store; 18 | $httpClient.get(checkRequest, function (error, response, data) { 19 | if (error) { 20 | $notification.post('🍎 直營店庫存檢查失敗 ❌', '', '連線錯誤‼️') 21 | } else { 22 | if (response.status == 200) { 23 | let obj = JSON.parse(data); 24 | if (obj.availabilityStatus != 'NOT_AVAILABLE') { 25 | $notification.post('📱 ' + model + ' 有貨', '', obj.pickupQuote); 26 | } 27 | else { 28 | console.log('📱 ' + obj.storeNumber + ' ' + obj.pickupQuote); 29 | } 30 | if (index < stores.length - 1) { 31 | checkStore(model, index + 1); 32 | } 33 | else { 34 | $done(); 35 | } 36 | } else { 37 | $notification.post('🍎 直營店庫存檢查失敗 ❌', '', '連線錯誤‼️'); 38 | } 39 | } 40 | }); 41 | } 42 | 43 | if (Date.now() < 1663286400000) { 44 | console.log('還沒開賣'); 45 | $done(); 46 | } 47 | else if (new Date().getHours() < 8 || new Date().getHours() > 21) { 48 | console.log('不在營業時間'); 49 | $done(); 50 | } 51 | else { 52 | checkStore(model, 0); 53 | } 54 | -------------------------------------------------------------------------------- /scripts/iphone_get_model.js: -------------------------------------------------------------------------------- 1 | if ($request.url.includes('product=')) { 2 | const product = $request.url.split('product=')[1].split('&')[0]; 3 | if (product.includes('/') && $persistentStore.write(product, "iPhoneModel")) 4 | { 5 | $notification.post("已儲存欲購買機型 " + product, "", "") 6 | } 7 | } 8 | $done({}) -------------------------------------------------------------------------------- /scripts/mcdonalds_set_token.js: -------------------------------------------------------------------------------- 1 | const token = '改掉這一行'; 2 | 3 | //================================================================ 4 | let showNotification = true; 5 | 6 | function surgeNotify(subtitle = '', message = '') { 7 | $notification.post('🍟 麥當勞 token', subtitle, message, { 'url': 'mcdonalds.app://' }); 8 | }; 9 | 10 | function handleError(error) { 11 | if (Array.isArray(error)) { 12 | console.log(`❌ ${error[0]} ${error[1]}`); 13 | if (showNotification) { 14 | surgeNotify(error[0], error[1]); 15 | } 16 | } else { 17 | console.log(`❌ ${error}`); 18 | if (showNotification) { 19 | surgeNotify(error); 20 | } 21 | } 22 | } 23 | 24 | function getSaveObject(key) { 25 | const string = $persistentStore.read(key); 26 | return !string || string.length === 0 ? {} : JSON.parse(string); 27 | } 28 | 29 | async function saveToken() { 30 | return new Promise((resolve, reject) => { 31 | try { 32 | if (token.length === 0 || token === '改掉這一行') { 33 | return reject(['保存失敗 ‼️', '請先設定 token']); 34 | } 35 | let mcdonaldsInfo = getSaveObject('McdonaldsInfo'); 36 | mcdonaldsInfo.token = token; 37 | const save = $persistentStore.write(JSON.stringify(mcdonaldsInfo, null, 4), 'McdonaldsInfo'); 38 | if (!save) { 39 | return reject(['保存失敗 ‼️', '無法儲存 token']); 40 | } else { 41 | return resolve(); 42 | } 43 | } catch (error) { 44 | return reject(['保存失敗 ‼️', error]); 45 | } 46 | }); 47 | } 48 | 49 | (async () => { 50 | console.log('ℹ️ 麥當勞設定 token v20230125.1'); 51 | try { 52 | await saveToken(); 53 | console.log('✅ token 保存成功'); 54 | surgeNotify('保存成功 ✅', ''); 55 | } catch (error) { 56 | handleError(error); 57 | } 58 | $done({}); 59 | })(); 60 | -------------------------------------------------------------------------------- /scripts/mcdonalds_token.js: -------------------------------------------------------------------------------- 1 | let showNotification = true; 2 | const fakeAppVersion = '3.9.9'; 3 | 4 | function surgeNotify(subtitle = '', message = '') { 5 | $notification.post('🍟 麥當勞 token', subtitle, message, { 'url': 'mcdonalds.app://' }); 6 | }; 7 | 8 | function handleError(error) { 9 | if (Array.isArray(error)) { 10 | console.log(`❌ ${error[0]} ${error[1]}`); 11 | if (showNotification) { 12 | surgeNotify(error[0], error[1]); 13 | } 14 | } else { 15 | console.log(`❌ ${error}`); 16 | if (showNotification) { 17 | surgeNotify(error); 18 | } 19 | } 20 | } 21 | 22 | function getSaveObject(key) { 23 | const string = $persistentStore.read(key); 24 | return !string || string.length === 0 ? {} : JSON.parse(string); 25 | } 26 | 27 | function isManualRun(checkRequest = false, checkResponse = false) { 28 | if (checkRequest) { 29 | return typeof $request === 'undefined' || ($request.body && JSON.parse($request.body).foo === 'bar'); 30 | } 31 | if (checkResponse) { 32 | return typeof $response === 'undefined' || ($response.body && JSON.parse($response.body).foo === 'bar'); 33 | } 34 | return false; 35 | } 36 | 37 | async function getToken() { 38 | return new Promise((resolve, reject) => { 39 | try { 40 | const token = $request.headers['accessToken'] || $request.headers['accesstoken']; 41 | let mcdonaldsInfo = getSaveObject('McdonaldsInfo'); 42 | mcdonaldsInfo.token = token; 43 | const save = $persistentStore.write(JSON.stringify(mcdonaldsInfo, null, 4), 'McdonaldsInfo'); 44 | if (!save) { 45 | return reject(['保存失敗 ‼️', '無法儲存 token']); 46 | } else { 47 | return resolve(); 48 | } 49 | } catch (error) { 50 | return reject(['保存失敗 ‼️', error]); 51 | } 52 | }); 53 | } 54 | 55 | (async () => { 56 | console.log('ℹ️ 麥當勞自動儲存 token v20230125.1'); 57 | try { 58 | if (isManualRun(true, false)) { 59 | throw '請勿手動執行此腳本'; 60 | } 61 | await getToken(); 62 | console.log('✅ token 保存成功'); 63 | surgeNotify('保存成功 ✅', ''); 64 | } catch (error) { 65 | handleError(error); 66 | } 67 | $request.headers['version'] = fakeAppVersion; 68 | $request.headers['appVersion'] = fakeAppVersion; 69 | $done($request); 70 | })(); 71 | -------------------------------------------------------------------------------- /scripts/mcdonalds_version_bypass.js: -------------------------------------------------------------------------------- 1 | const version = '3.6.0'; 2 | const ua = 'Mcdonalds/3.6.0'; 3 | 4 | $request.headers['version'] = version; 5 | 6 | if ($request.headers['appversion']) { 7 | $request.headers['appversion'] = version; //HTTP2 8 | $request.headers['user-agent'] = $request.headers['user-agent'].replace(new RegExp('Mcdonalds/3.[0-9].[0-9]'), ua); 9 | } 10 | else { 11 | $request.headers['appVersion'] = version; 12 | $request.headers['User-Agent'] = $request.headers['User-Agent'].replace(new RegExp('Mcdonalds/3.[0-9].[0-9]'), ua); 13 | } 14 | 15 | $done($request); 16 | -------------------------------------------------------------------------------- /scripts/momo_checkin.js: -------------------------------------------------------------------------------- 1 | const momoHeaders = { 2 | 'Cookie': $persistentStore.read('momoCookie'), 3 | 'Content-Type': 'application/json;charset=utf-8', 4 | }; 5 | function momoNotify(subtitle = '', message = '') { 6 | $notification.post('🍑 Momo 每日簽到', subtitle, message, { 'url': 'momo.app://' }); 7 | }; 8 | 9 | const mainPageRequest = { 10 | url: 'https://app.momoshop.com.tw/api/moecapp/goods/getMainPageV5', 11 | headers: momoHeaders, 12 | body: { 13 | 'ccsession': '', 14 | 'custNo': '', 15 | 'ccguid': '', 16 | 'jsessionid': '', 17 | 'isIphoneX': '1' 18 | } 19 | } 20 | 21 | let eventPageRequest = { 22 | url: '', 23 | headers: momoHeaders, 24 | } 25 | 26 | let jsCodeRequest = { 27 | url: '', 28 | headers: momoHeaders, 29 | } 30 | 31 | let checkinRequest = { 32 | url: 'https://event.momoshop.com.tw/punch.PROMO', 33 | headers: { 34 | 'Cookie': $persistentStore.read('momoCookie'), 35 | 'Content-Type': 'application/json;charset=utf-8', 36 | 'User-Agent': 'momoshop', 37 | 'Referer': '', 38 | }, 39 | body: $persistentStore.read('momoBody'), 40 | }; 41 | 42 | function getEventPageUrl() { 43 | $httpClient.post(mainPageRequest, function (error, response, data) { 44 | if (error) { 45 | momoNotify( 46 | '取得活動頁面失敗 ‼️', 47 | '連線錯誤' 48 | ); 49 | $done(); 50 | } else { 51 | if (response.status === 200) { 52 | try { 53 | const obj = JSON.parse(data); 54 | if (obj.success === true) { 55 | const mainInfo = obj.mainInfo; 56 | let found = false; 57 | for (const info of mainInfo) { 58 | if (info.adInfo && info.columnType === "3") { 59 | const adInfo = info.adInfo[0]; 60 | const actionUrl = adInfo.action.actionValue; 61 | console.log('Momo 簽到活動頁面 👉' + actionUrl); 62 | found = true; 63 | checkinRequest.headers.Referer = actionUrl; 64 | eventPageRequest.url = actionUrl; 65 | eventPageRequest.headers.cookie = ''; 66 | getJavascriptUrl(); 67 | // 舊版 68 | // for (const adInfo of info.adInfo) { 69 | // if (adInfo.adTitle && adInfo.adTitle === '天天簽到') { 70 | // const actionUrl = adInfo.action.actionValue; 71 | // console.log('Momo 簽到活動頁面 👉' + actionUrl); 72 | // found = true; 73 | // eventPageRequest.url = actionUrl; 74 | // eventPageRequest.headers.cookie = ''; 75 | // getJavascriptUrl(); 76 | // } 77 | // } 78 | } 79 | } 80 | if (!found) { 81 | console.log('找不到簽到活動頁面'); 82 | $done(); 83 | } 84 | } else { 85 | momoNotify( 86 | '取得活動頁面失敗 ‼️', 87 | obj.resultMessage 88 | ); 89 | $done(); 90 | } 91 | } 92 | catch (error) { 93 | momoNotify( 94 | '取得活動頁面失敗 ‼️', 95 | error 96 | ); 97 | $done(); 98 | } 99 | } else { 100 | momoNotify( 101 | 'Cookie 已過期 ‼️', 102 | '請重新登入' 103 | ); 104 | $done(); 105 | } 106 | } 107 | }); 108 | } 109 | 110 | function getJavascriptUrl() { 111 | $httpClient.get(eventPageRequest, function (error, response, data) { 112 | if (error) { 113 | momoNotify( 114 | '取得 JS URL 失敗 ‼️', 115 | '連線錯誤' 116 | ); 117 | $done(); 118 | } else { 119 | if (response.status === 200) { 120 | try { 121 | const re = /https:\/\/(.*)\/promo-cloud-setPunch-v003\.js\?t=[0-9]{13}/i; 122 | const found = data.match(re); 123 | const url = found[0]; 124 | jsCodeRequest.url = url; 125 | console.log('活動 JS URL 👉' + url); 126 | getPromoCloudConfig(); 127 | } 128 | catch (error) { 129 | momoNotify( 130 | '取得 JS URL 失敗 ‼️', 131 | error 132 | ); 133 | $done(); 134 | } 135 | } else { 136 | momoNotify( 137 | '取得 JS URL 失敗 ‼️', 138 | response.status 139 | ); 140 | $done(); 141 | } 142 | } 143 | }); 144 | } 145 | 146 | function getPromoCloudConfig() { 147 | $httpClient.get(jsCodeRequest, function (error, response, data) { 148 | if (error) { 149 | momoNotify( 150 | '取得活動 ID 失敗 ‼️', 151 | '連線錯誤' 152 | ); 153 | $done(); 154 | } else { 155 | if (response.status === 200) { 156 | try { 157 | const pNoRe = /punchConfig\.pNo(.*)"(.*)"/i; 158 | const pNo = data.match(pNoRe)[2]; 159 | console.log('Momo 活動 ID 👉' + pNo); 160 | let body = JSON.parse(checkinRequest.body); 161 | body.pNo = pNo; 162 | checkinRequest.body = body; 163 | checkIn(); 164 | } 165 | catch (error) { 166 | momoNotify( 167 | '取得活動 ID 失敗 ‼️', 168 | error 169 | ); 170 | $done(); 171 | } 172 | } else { 173 | momoNotify( 174 | 'Cookie 已過期 ‼️', 175 | '請重新登入' 176 | ); 177 | $done(); 178 | } 179 | } 180 | }); 181 | } 182 | 183 | function checkIn() { 184 | $httpClient.post(checkinRequest, function (error, response, data) { 185 | if (error) { 186 | momoNotify( 187 | '簽到失敗 ‼️', 188 | '連線錯誤' 189 | ); 190 | } else { 191 | if (response.status === 200) { 192 | const obj = JSON.parse(data); 193 | if (obj.data.status === 'OK') { 194 | momoNotify( 195 | '今日簽到成功 ✅', 196 | '' 197 | ); 198 | } else if (obj.data.status === 'RA') { 199 | console.log('本日已簽到'); 200 | // momoNotify( 201 | // '簽到失敗 ‼️', 202 | // '本日已簽到' 203 | // ); 204 | } else if (obj.data.status === 'D') { 205 | momoNotify( 206 | '簽到失敗 ‼️', 207 | '活動已到期' 208 | ); 209 | } else if (obj.data.status === 'MAX') { 210 | momoNotify( 211 | '簽到失敗 ‼️', 212 | '簽到人數達到上限' 213 | ); 214 | } else if (obj.data.status === 'EPN2') { 215 | momoNotify( 216 | '簽到失敗 ‼️', 217 | '活動不存在' 218 | ); 219 | } else { 220 | momoNotify( 221 | '簽到失敗 ‼️', 222 | obj.data.status 223 | ); 224 | } 225 | } else { 226 | momoNotify( 227 | 'Cookie 已過期 ‼️', 228 | '請重新登入' 229 | ); 230 | } 231 | } 232 | $done(); 233 | }); 234 | } 235 | 236 | getEventPageUrl(); 237 | -------------------------------------------------------------------------------- /scripts/momo_checkin_info.js: -------------------------------------------------------------------------------- 1 | function momoNotify(subtitle = '', message = '') { 2 | $notification.post('🍑 Momo token', subtitle, message, { 'url': 'momo.app://' }); 3 | }; 4 | 5 | if ($request.method === 'POST') { 6 | const cookie = $request.headers['Cookie'] || $request.headers['cookie']; 7 | if (cookie && $request.body) { 8 | try { 9 | let body = JSON.parse($request.body); 10 | if (body.doAction === 'list') { 11 | body.pNo = ''; 12 | body.doAction = 'reg'; 13 | const saveCookie = $persistentStore.write(cookie, 'momoCookie'); 14 | const saveBody = $persistentStore.write(JSON.stringify(body), 'momoBody'); 15 | if (!(saveCookie && saveBody)) { 16 | momoNotify( 17 | '保存失敗 ‼️', 18 | '請稍後嘗試' 19 | ); 20 | } else { 21 | momoNotify( 22 | '保存成功 🍪', 23 | '' 24 | ); 25 | } 26 | } 27 | } catch (error) { 28 | momoNotify( 29 | '保存失敗 ‼️', 30 | error 31 | ); 32 | } 33 | } else { 34 | momoNotify( 35 | '保存失敗 ‼️', 36 | '請重新登入' 37 | ); 38 | } 39 | } 40 | $done({}) 41 | -------------------------------------------------------------------------------- /scripts/netflix_rating.js: -------------------------------------------------------------------------------- 1 | /* 2 | README:https://github.com/yichahucha/surge/tree/master 3 | */ 4 | 5 | const $tool = new Tool() 6 | const consoleLog = false; 7 | const imdbApikeyCacheKey = "ImdbApikeyCacheKey"; 8 | const netflixTitleCacheKey = "NetflixTitleCacheKey"; 9 | 10 | if (!$tool.isResponse) { 11 | let url = $request.url; 12 | const urlDecode = decodeURIComponent(url); 13 | const videos = urlDecode.match(/"videos","(\d+)"/); 14 | const videoID = videos[1]; 15 | const map = getTitleMap(); 16 | const title = map[videoID]; 17 | const isEnglish = url.match(/languages=en/) ? true : false; 18 | if (!title && !isEnglish) { 19 | const currentSummary = urlDecode.match(/\["videos","(\d+)","current","summary"\]/); 20 | if (currentSummary) { 21 | url = url.replace("&path=" + encodeURIComponent(currentSummary[0]), ""); 22 | } 23 | url = url.replace(/&languages=(.*?)&/, "&languages=en-US&"); 24 | } 25 | url += "&path=" + encodeURIComponent(`[${videos[0]},"details"]`); 26 | $done({ url }); 27 | } else { 28 | var IMDbApikeys = IMDbApikeys(); 29 | var IMDbApikey = $tool.read(imdbApikeyCacheKey); 30 | if (!IMDbApikey) updateIMDbApikey(); 31 | let obj = JSON.parse($response.body); 32 | if (consoleLog) console.log("Netflix Original Body:\n" + $response.body); 33 | if (typeof(obj.paths[0][1]) == "string") { 34 | const videoID = obj.paths[0][1]; 35 | const video = obj.value.videos[videoID]; 36 | const map = getTitleMap(); 37 | let title = map[videoID]; 38 | if (!title) { 39 | title = video.summary.title; 40 | setTitleMap(videoID, title, map); 41 | } 42 | let year = null; 43 | let type = video.summary.type; 44 | if (type == "show") { 45 | type = "series"; 46 | } 47 | if (type == "movie") { 48 | year = video.details.releaseYear; 49 | } 50 | delete video.details; 51 | const requestRatings = async () => { 52 | const IMDb = await requestIMDbRating(title, year, type); 53 | const Douban = await requestDoubanRating(IMDb.id); 54 | const IMDbrating = IMDb.msg.rating; 55 | const tomatoes = IMDb.msg.tomatoes; 56 | const country = IMDb.msg.country; 57 | const doubanRating = Douban.rating; 58 | const message = `${country}\n${IMDbrating}\n${doubanRating}${tomatoes.length > 0 ? "\n" + tomatoes + "\n" : "\n"}`; 59 | return message; 60 | } 61 | let msg = ""; 62 | requestRatings() 63 | .then(message => msg = message) 64 | .catch(error => msg = error + "\n") 65 | .finally(() => { 66 | let summary = obj.value.videos[videoID].summary; 67 | summary["supplementalMessage"] = `${msg}${summary && summary.supplementalMessage ? "\n" + summary.supplementalMessage : ""}`; 68 | if (consoleLog) console.log("Netflix Modified Body:\n" + JSON.stringify(obj)); 69 | $done({ body: JSON.stringify(obj) }); 70 | }); 71 | } else { 72 | $done({}); 73 | } 74 | } 75 | 76 | function getTitleMap() { 77 | const map = $tool.read(netflixTitleCacheKey); 78 | return map ? JSON.parse(map) : {}; 79 | } 80 | 81 | function setTitleMap(id, title, map) { 82 | map[id] = title; 83 | $tool.write(JSON.stringify(map), netflixTitleCacheKey); 84 | } 85 | 86 | function requestDoubanRating(imdbId) { 87 | return new Promise(function (resolve, reject) { 88 | const url = "https://api.douban.com/v2/movie/imdb/" + imdbId + "?apikey=0df993c66c0c636e29ecbb5344252a4a"; 89 | if (consoleLog) console.log("Netflix Douban Rating URL:\n" + url); 90 | $tool.get(url, function (error, response, data) { 91 | if (!error) { 92 | if (consoleLog) console.log("Netflix Douban Rating Data:\n" + data); 93 | if (response.status == 200) { 94 | const obj = JSON.parse(data); 95 | const rating = get_douban_rating_message(obj); 96 | resolve({ rating }); 97 | } else { 98 | resolve({ rating: "Douban: " + errorTip().noData }); 99 | } 100 | } else { 101 | if (consoleLog) console.log("Netflix Douban Rating Error:\n" + error); 102 | resolve({ rating: "Douban: " + errorTip().error }); 103 | } 104 | }); 105 | }); 106 | } 107 | 108 | function requestIMDbRating(title, year, type) { 109 | return new Promise(function (resolve, reject) { 110 | let url = "https://www.omdbapi.com/?t=" + encodeURI(title) + "&apikey=" + IMDbApikey; 111 | if (year) url += "&y=" + year; 112 | if (type) url += "&type=" + type; 113 | if (consoleLog) console.log("Netflix IMDb Rating URL:\n" + url); 114 | $tool.get(url, function (error, response, data) { 115 | if (!error) { 116 | if (consoleLog) console.log("Netflix IMDb Rating Data:\n" + data); 117 | if (response.status == 200) { 118 | const obj = JSON.parse(data); 119 | if (obj.Response != "False") { 120 | const id = obj.imdbID; 121 | const msg = get_IMDb_message(obj); 122 | resolve({ id, msg }); 123 | } else { 124 | reject(errorTip().noData); 125 | } 126 | } else if (response.status == 401) { 127 | if (IMDbApikeys.length > 1) { 128 | updateIMDbApikey(); 129 | requestIMDbRating(title, year, type); 130 | } else { 131 | reject(errorTip().noData); 132 | } 133 | } else { 134 | reject(errorTip().noData); 135 | } 136 | } else { 137 | if (consoleLog) console.log("Netflix IMDb Rating Error:\n" + error); 138 | reject(errorTip().error); 139 | } 140 | }); 141 | }); 142 | } 143 | 144 | function updateIMDbApikey() { 145 | if (IMDbApikey) IMDbApikeys.splice(IMDbApikeys.indexOf(IMDbApikey), 1); 146 | const index = Math.floor(Math.random() * IMDbApikeys.length); 147 | IMDbApikey = IMDbApikeys[index]; 148 | $tool.write(IMDbApikey, imdbApikeyCacheKey); 149 | } 150 | 151 | function get_IMDb_message(data) { 152 | let rating_message = "IMDb: ⭐️ N/A"; 153 | let tomatoes_message = ""; 154 | let country_message = ""; 155 | let ratings = data.Ratings; 156 | if (ratings.length > 0) { 157 | const imdb_source = ratings[0]["Source"]; 158 | if (imdb_source == "Internet Movie Database") { 159 | const imdb_votes = data.imdbVotes; 160 | const imdb_rating = ratings[0]["Value"]; 161 | rating_message = "IMDb: ⭐️ " + imdb_rating + " " + imdb_votes; 162 | if (data.Type == "movie") { 163 | if (ratings.length > 1) { 164 | const source = ratings[1]["Source"]; 165 | if (source == "Rotten Tomatoes") { 166 | const tomatoes = ratings[1]["Value"]; 167 | tomatoes_message = "Tomatoes: 🍅 " + tomatoes; 168 | } 169 | } 170 | } 171 | } 172 | } 173 | country_message = get_country_message(data.Country); 174 | return { rating: rating_message, tomatoes: tomatoes_message, country: country_message } 175 | } 176 | 177 | function get_douban_rating_message(data) { 178 | const average = data.rating.average; 179 | const numRaters = data.rating.numRaters; 180 | const rating_message = `Douban: ⭐️ ${average.length > 0 ? average + "/10" : "N/A"} ${numRaters == 0 ? "" : parseFloat(numRaters).toLocaleString()}`; 181 | return rating_message; 182 | } 183 | 184 | function get_country_message(data) { 185 | const country = data; 186 | const countrys = country.split(", "); 187 | let emoji_country = ""; 188 | countrys.forEach(item => { 189 | emoji_country += countryEmoji(item) + " " + item + ", "; 190 | }); 191 | return emoji_country.slice(0, -2); 192 | } 193 | 194 | function errorTip() { 195 | return { noData: "⭐️ N/A", error: "❌ N/A" } 196 | } 197 | 198 | function IMDbApikeys() { 199 | const apikeys = [ 200 | "f75e0253", "d8bb2d6b", 201 | "ae64ce8d", "7218d678", 202 | "b2650e38", "8c4a29ab", 203 | "9bd135c2", "953dbabe", 204 | "1a66ef12", "3e7ea721", 205 | "457fc4ff", "d2131426", 206 | "9cc1a9b7", "e53c2c11", 207 | "f6dfce0e", "b9db622f", 208 | "e6bde2b9", "d324dbab", 209 | "d7904fa3", "aeaf88b9", 210 | "4e89234e",]; 211 | return apikeys; 212 | } 213 | 214 | function countryEmoji(name) { 215 | const emojiMap = { 216 | "Chequered": "🏁", 217 | "Triangular": "🚩", 218 | "Crossed": "🎌", 219 | "Black": "🏴", 220 | "White": "🏳", 221 | "Rainbow": "🏳️‍🌈", 222 | "Pirate": "🏴‍☠️", 223 | "Ascension Island": "🇦🇨", 224 | "Andorra": "🇦🇩", 225 | "United Arab Emirates": "🇦🇪", 226 | "Afghanistan": "🇦🇫", 227 | "Antigua & Barbuda": "🇦🇬", 228 | "Anguilla": "🇦🇮", 229 | "Albania": "🇦🇱", 230 | "Armenia": "🇦🇲", 231 | "Angola": "🇦🇴", 232 | "Antarctica": "🇦🇶", 233 | "Argentina": "🇦🇷", 234 | "American Samoa": "🇦🇸", 235 | "Austria": "🇦🇹", 236 | "Australia": "🇦🇺", 237 | "Aruba": "🇦🇼", 238 | "Åland Islands": "🇦🇽", 239 | "Azerbaijan": "🇦🇿", 240 | "Bosnia & Herzegovina": "🇧🇦", 241 | "Barbados": "🇧🇧", 242 | "Bangladesh": "🇧🇩", 243 | "Belgium": "🇧🇪", 244 | "Burkina Faso": "🇧🇫", 245 | "Bulgaria": "🇧🇬", 246 | "Bahrain": "🇧🇭", 247 | "Burundi": "🇧🇮", 248 | "Benin": "🇧🇯", 249 | "St. Barthélemy": "🇧🇱", 250 | "Bermuda": "🇧🇲", 251 | "Brunei": "🇧🇳", 252 | "Bolivia": "🇧🇴", 253 | "Caribbean Netherlands": "🇧🇶", 254 | "Brazil": "🇧🇷", 255 | "Bahamas": "🇧🇸", 256 | "Bhutan": "🇧🇹", 257 | "Bouvet Island": "🇧🇻", 258 | "Botswana": "🇧🇼", 259 | "Belarus": "🇧🇾", 260 | "Belize": "🇧🇿", 261 | "Canada": "🇨🇦", 262 | "Cocos (Keeling) Islands": "🇨🇨", 263 | "Congo - Kinshasa": "🇨🇩", 264 | "Congo": "🇨🇩", 265 | "Central African Republic": "🇨🇫", 266 | "Congo - Brazzaville": "🇨🇬", 267 | "Switzerland": "🇨🇭", 268 | "Côte d’Ivoire": "🇨🇮", 269 | "Cook Islands": "🇨🇰", 270 | "Chile": "🇨🇱", 271 | "Cameroon": "🇨🇲", 272 | "China": "🇨🇳", 273 | "Colombia": "🇨🇴", 274 | "Clipperton Island": "🇨🇵", 275 | "Costa Rica": "🇨🇷", 276 | "Cuba": "🇨🇺", 277 | "Cape Verde": "🇨🇻", 278 | "Curaçao": "🇨🇼", 279 | "Christmas Island": "🇨🇽", 280 | "Cyprus": "🇨🇾", 281 | "Czechia": "🇨🇿", 282 | "Czech Republic": "🇨🇿", 283 | "Germany": "🇩🇪", 284 | "Diego Garcia": "🇩🇬", 285 | "Djibouti": "🇩🇯", 286 | "Denmark": "🇩🇰", 287 | "Dominica": "🇩🇲", 288 | "Dominican Republic": "🇩🇴", 289 | "Algeria": "🇩🇿", 290 | "Ceuta & Melilla": "🇪🇦", 291 | "Ecuador": "🇪🇨", 292 | "Estonia": "🇪🇪", 293 | "Egypt": "🇪🇬", 294 | "Western Sahara": "🇪🇭", 295 | "Eritrea": "🇪🇷", 296 | "Spain": "🇪🇸", 297 | "Ethiopia": "🇪🇹", 298 | "European Union": "🇪🇺", 299 | "Finland": "🇫🇮", 300 | "Fiji": "🇫🇯", 301 | "Falkland Islands": "🇫🇰", 302 | "Micronesia": "🇫🇲", 303 | "Faroe Islands": "🇫🇴", 304 | "France": "🇫🇷", 305 | "Gabon": "🇬🇦", 306 | "United Kingdom": "🇬🇧", 307 | "UK": "🇬🇧", 308 | "Grenada": "🇬🇩", 309 | "Georgia": "🇬🇪", 310 | "French Guiana": "🇬🇫", 311 | "Guernsey": "🇬🇬", 312 | "Ghana": "🇬🇭", 313 | "Gibraltar": "🇬🇮", 314 | "Greenland": "🇬🇱", 315 | "Gambia": "🇬🇲", 316 | "Guinea": "🇬🇳", 317 | "Guadeloupe": "🇬🇵", 318 | "Equatorial Guinea": "🇬🇶", 319 | "Greece": "🇬🇷", 320 | "South Georgia & South Sandwich Is lands": "🇬🇸", 321 | "Guatemala": "🇬🇹", 322 | "Guam": "🇬🇺", 323 | "Guinea-Bissau": "🇬🇼", 324 | "Guyana": "🇬🇾", 325 | "Hong Kong SAR China": "🇭🇰", 326 | "Hong Kong": "🇭🇰", 327 | "Heard & McDonald Islands": "🇭🇲", 328 | "Honduras": "🇭🇳", 329 | "Croatia": "🇭🇷", 330 | "Haiti": "🇭🇹", 331 | "Hungary": "🇭🇺", 332 | "Canary Islands": "🇮🇨", 333 | "Indonesia": "🇮🇩", 334 | "Ireland": "🇮🇪", 335 | "Israel": "🇮🇱", 336 | "Isle of Man": "🇮🇲", 337 | "India": "🇮🇳", 338 | "British Indian Ocean Territory": "🇮🇴", 339 | "Iraq": "🇮🇶", 340 | "Iran": "🇮🇷", 341 | "Iceland": "🇮🇸", 342 | "Italy": "🇮🇹", 343 | "Jersey": "🇯🇪", 344 | "Jamaica": "🇯🇲", 345 | "Jordan": "🇯🇴", 346 | "Japan": "🇯🇵", 347 | "Kenya": "🇰🇪", 348 | "Kyrgyzstan": "🇰🇬", 349 | "Cambodia": "🇰🇭", 350 | "Kiribati": "🇰🇮", 351 | "Comoros": "🇰🇲", 352 | "St. Kitts & Nevis": "🇰🇳", 353 | "North Korea": "🇰🇵", 354 | "South Korea": "🇰🇷", 355 | "Kuwait": "🇰🇼", 356 | "Cayman Islands": "🇰🇾", 357 | "Kazakhstan": "🇰🇿", 358 | "Laos": "🇱🇦", 359 | "Lebanon": "🇱🇧", 360 | "St. Lucia": "🇱🇨", 361 | "Liechtenstein": "🇱🇮", 362 | "Sri Lanka": "🇱🇰", 363 | "Liberia": "🇱🇷", 364 | "Lesotho": "🇱🇸", 365 | "Lithuania": "🇱🇹", 366 | "Luxembourg": "🇱🇺", 367 | "Latvia": "🇱🇻", 368 | "Libya": "🇱🇾", 369 | "Morocco": "🇲🇦", 370 | "Monaco": "🇲🇨", 371 | "Moldova": "🇲🇩", 372 | "Montenegro": "🇲🇪", 373 | "St. Martin": "🇲🇫", 374 | "Madagascar": "🇲🇬", 375 | "Marshall Islands": "🇲🇭", 376 | "North Macedonia": "🇲🇰", 377 | "Mali": "🇲🇱", 378 | "Myanmar (Burma)": "🇲🇲", 379 | "Mongolia": "🇲🇳", 380 | "Macau Sar China": "🇲🇴", 381 | "Northern Mariana Islands": "🇲🇵", 382 | "Martinique": "🇲🇶", 383 | "Mauritania": "🇲🇷", 384 | "Montserrat": "🇲🇸", 385 | "Malta": "🇲🇹", 386 | "Mauritius": "🇲🇺", 387 | "Maldives": "🇲🇻", 388 | "Malawi": "🇲🇼", 389 | "Mexico": "🇲🇽", 390 | "Malaysia": "🇲🇾", 391 | "Mozambique": "🇲🇿", 392 | "Namibia": "🇳🇦", 393 | "New Caledonia": "🇳🇨", 394 | "Niger": "🇳🇪", 395 | "Norfolk Island": "🇳🇫", 396 | "Nigeria": "🇳🇬", 397 | "Nicaragua": "🇳🇮", 398 | "Netherlands": "🇳🇱", 399 | "Norway": "🇳🇴", 400 | "Nepal": "🇳🇵", 401 | "Nauru": "🇳🇷", 402 | "Niue": "🇳🇺", 403 | "New Zealand": "🇳🇿", 404 | "Oman": "🇴🇲", 405 | "Panama": "🇵🇦", 406 | "Peru": "🇵🇪", 407 | "French Polynesia": "🇵🇫", 408 | "Papua New Guinea": "🇵🇬", 409 | "Philippines": "🇵🇭", 410 | "Pakistan": "🇵🇰", 411 | "Poland": "🇵🇱", 412 | "St. Pierre & Miquelon": "🇵🇲", 413 | "Pitcairn Islands": "🇵🇳", 414 | "Puerto Rico": "🇵🇷", 415 | "Palestinian Territories": "🇵🇸", 416 | "Portugal": "🇵🇹", 417 | "Palau": "🇵🇼", 418 | "Paraguay": "🇵🇾", 419 | "Qatar": "🇶🇦", 420 | "Réunion": "🇷🇪", 421 | "Romania": "🇷🇴", 422 | "Serbia": "🇷🇸", 423 | "Russia": "🇷🇺", 424 | "Rwanda": "🇷🇼", 425 | "Saudi Arabia": "🇸🇦", 426 | "Solomon Islands": "🇸🇧", 427 | "Seychelles": "🇸🇨", 428 | "Sudan": "🇸🇩", 429 | "Sweden": "🇸🇪", 430 | "Singapore": "🇸🇬", 431 | "St. Helena": "🇸🇭", 432 | "Slovenia": "🇸🇮", 433 | "Svalbard & Jan Mayen": "🇸🇯", 434 | "Slovakia": "🇸🇰", 435 | "Sierra Leone": "🇸🇱", 436 | "San Marino": "🇸🇲", 437 | "Senegal": "🇸🇳", 438 | "Somalia": "🇸🇴", 439 | "Suriname": "🇸🇷", 440 | "South Sudan": "🇸🇸", 441 | "São Tomé & Príncipe": "🇸🇹", 442 | "El Salvador": "🇸🇻", 443 | "Sint Maarten": "🇸🇽", 444 | "Syria": "🇸🇾", 445 | "Swaziland": "🇸🇿", 446 | "Tristan Da Cunha": "🇹🇦", 447 | "Turks & Caicos Islands": "🇹🇨", 448 | "Chad": "🇹🇩", 449 | "French Southern Territories": "🇹🇫", 450 | "Togo": "🇹🇬", 451 | "Thailand": "🇹🇭", 452 | "Tajikistan": "🇹🇯", 453 | "Tokelau": "🇹🇰", 454 | "Timor-Leste": "🇹🇱", 455 | "Turkmenistan": "🇹🇲", 456 | "Tunisia": "🇹🇳", 457 | "Tonga": "🇹🇴", 458 | "Turkey": "🇹🇷", 459 | "Trinidad & Tobago": "🇹🇹", 460 | "Tuvalu": "🇹🇻", 461 | "Taiwan": "🇹🇼", 462 | "Tanzania": "🇹🇿", 463 | "Ukraine": "🇺🇦", 464 | "Uganda": "🇺🇬", 465 | "U.S. Outlying Islands": "🇺🇲", 466 | "United Nations": "🇺🇳", 467 | "United States": "🇺🇸", 468 | "USA": "🇺🇸", 469 | "Uruguay": "🇺🇾", 470 | "Uzbekistan": "🇺🇿", 471 | "Vatican City": "🇻🇦", 472 | "St. Vincent & Grenadines": "🇻🇨", 473 | "Venezuela": "🇻🇪", 474 | "British Virgin Islands": "🇻🇬", 475 | "U.S. Virgin Islands": "🇻🇮", 476 | "Vietnam": "🇻🇳", 477 | "Vanuatu": "🇻🇺", 478 | "Wallis & Futuna": "🇼🇫", 479 | "Samoa": "🇼🇸", 480 | "Kosovo": "🇽🇰", 481 | "Yemen": "🇾🇪", 482 | "Mayotte": "🇾🇹", 483 | "South Africa": "🇿🇦", 484 | "Zambia": "🇿🇲", 485 | "Zimbabwe": "🇿🇼", 486 | "England": "🏴󠁧󠁢󠁥󠁮󠁧󠁿", 487 | "Scotland": "🏴󠁧󠁢󠁳󠁣󠁴󠁿", 488 | "Wales": "🏴󠁧󠁢󠁷󠁬󠁳󠁿", 489 | } 490 | return emojiMap[name] ? emojiMap[name] : emojiMap["Chequered"]; 491 | } 492 | 493 | function Tool() { 494 | _node = (() => { 495 | if (typeof require == "function") { 496 | const request = require('request') 497 | return ({ request }) 498 | } else { 499 | return (null) 500 | } 501 | })() 502 | _isSurge = typeof $httpClient != "undefined" 503 | _isQuanX = typeof $task != "undefined" 504 | this.isSurge = _isSurge 505 | this.isQuanX = _isQuanX 506 | this.isResponse = typeof $response != "undefined" 507 | this.notify = (title, subtitle, message) => { 508 | if (_isQuanX) $notify(title, subtitle, message) 509 | if (_isSurge) $notification.post(title, subtitle, message) 510 | if (_node) console.log(JSON.stringify({ title, subtitle, message })); 511 | } 512 | this.write = (value, key) => { 513 | if (_isQuanX) return $prefs.setValueForKey(value, key) 514 | if (_isSurge) return $persistentStore.write(value, key) 515 | } 516 | this.read = (key) => { 517 | if (_isQuanX) return $prefs.valueForKey(key) 518 | if (_isSurge) return $persistentStore.read(key) 519 | } 520 | this.get = (options, callback) => { 521 | if (_isQuanX) { 522 | if (typeof options == "string") options = { url: options } 523 | options["method"] = "GET" 524 | $task.fetch(options).then(response => { callback(null, _status(response), response.body) }, reason => callback(reason.error, null, null)) 525 | } 526 | if (_isSurge) $httpClient.get(options, (error, response, body) => { callback(error, _status(response), body) }) 527 | if (_node) _node.request(options, (error, response, body) => { callback(error, _status(response), body) }) 528 | } 529 | this.post = (options, callback) => { 530 | if (_isQuanX) { 531 | if (typeof options == "string") options = { url: options } 532 | options["method"] = "POST" 533 | $task.fetch(options).then(response => { callback(null, _status(response), response.body) }, reason => callback(reason.error, null, null)) 534 | } 535 | if (_isSurge) $httpClient.post(options, (error, response, body) => { callback(error, _status(response), body) }) 536 | if (_node) _node.request.post(options, (error, response, body) => { callback(error, _status(response), body) }) 537 | } 538 | _status = (response) => { 539 | if (response) { 540 | if (response.status) { 541 | response["statusCode"] = response.status 542 | } else if (response.statusCode) { 543 | response["status"] = response.statusCode 544 | } 545 | } 546 | return response 547 | } 548 | } -------------------------------------------------------------------------------- /scripts/ozan_fake_uuid.js: -------------------------------------------------------------------------------- 1 | let fakeUuid = ''; 2 | 3 | function surgeNotify(subtitle = '', message = '') { 4 | $notification.post('Ozan 偽造 UUID', subtitle, message, { 'url': '' }); 5 | }; 6 | 7 | function handleError(error) { 8 | if (Array.isArray(error)) { 9 | console.log(`❌ ${error[0]} ${error[1]}`); 10 | if (showNotification) { 11 | surgeNotify(error[0], error[1]); 12 | } 13 | } else { 14 | console.log(`❌ ${error}`); 15 | if (showNotification) { 16 | surgeNotify(error); 17 | } 18 | } 19 | } 20 | 21 | function isManualRun(checkRequest = false, checkResponse = false) { 22 | if (checkRequest) { 23 | return typeof $request === 'undefined' || ($request.body && JSON.parse($request.body).foo === 'bar'); 24 | } 25 | if (checkResponse) { 26 | return typeof $response === 'undefined' || ($response.body && JSON.parse($response.body).foo === 'bar'); 27 | } 28 | return false; 29 | } 30 | 31 | async function preCheck() { 32 | return new Promise((resolve, reject) => { 33 | fakeUuid = $persistentStore.read('OzanFakeUUID') || ''; 34 | if (!fakeUuid.length || fakeUuid === '取代這幾個字') { 35 | return reject(['檢查失敗 ‼️', '找不到 OzanFakeUUID']); 36 | } 37 | return resolve(); 38 | }); 39 | } 40 | 41 | async function replaceUuid() { 42 | return new Promise((resolve, reject) => { 43 | try { 44 | const regex = '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'; 45 | if ($request.headers['X-DEVICE-CODE']) { 46 | $request.headers['X-DEVICE-CODE'] = fakeUuid; 47 | $request.headers['User-Agent'] = $request.headers['User-Agent'].replace(new RegExp(regex, 'g'), fakeUuid); 48 | } 49 | else { 50 | $request.headers['x-device-code'] = fakeUuid; 51 | $request.headers['user-agent'] = $request.headers['user-agent'].replace(new RegExp(regex, 'g'), fakeUuid); 52 | } 53 | return resolve(); 54 | } catch (error) { 55 | return reject(['偽造 UUID 失敗 ‼️', error]); 56 | } 57 | }); 58 | } 59 | 60 | (async () => { 61 | if (isManualRun(true, false)) { 62 | const error = '❌ 請勿手動執行此腳本'; 63 | console.log(error); 64 | $done($request); 65 | return; 66 | } 67 | try { 68 | await preCheck(); 69 | await replaceUuid(); 70 | } catch (error) { 71 | handleError(error); 72 | } 73 | $done($request); 74 | })(); 75 | -------------------------------------------------------------------------------- /scripts/ozan_get_uuid.js: -------------------------------------------------------------------------------- 1 | function ozanNotify(subtitle = '', message = '') { 2 | $notification.post('Ozan 取得 UUID', subtitle, message); 3 | }; 4 | 5 | function isLoon() { 6 | return 'undefined' !== typeof $loon; 7 | } 8 | 9 | const uuid = $request.headers['X-DEVICE-CODE'] || $request.headers['x-device-code']; 10 | if (uuid) { 11 | let writeData = $persistentStore.write(uuid, 'OzanUUID'); 12 | if (!writeData) { 13 | ozanNotify( 14 | '保存失敗 ‼️', 15 | '請稍後嘗試' 16 | ); 17 | } else { 18 | if (isLoon()) { 19 | ozanNotify( 20 | 'UUID 保存成功 ✅', 21 | '請到「腳本」→「數據持久化」→「讀取指定數據」,輸入 OzanUUID,然後複製所有內容' 22 | ); 23 | } else { 24 | ozanNotify( 25 | 'UUID 保存成功 ✅', 26 | '請打開腳本編輯器,選擇左下角齒輪 → $persistentStore → OzanUUID,然後複製所有內容' 27 | ); 28 | } 29 | } 30 | } else { 31 | ozanNotify( 32 | '保存失敗 ‼️', 33 | '請重新登入' 34 | ); 35 | } 36 | 37 | $done($request); 38 | -------------------------------------------------------------------------------- /scripts/ozan_set_uuid.js: -------------------------------------------------------------------------------- 1 | const uuid = '取代這幾個字'; // ← 把原本登入裝置取得的 OzanUUID 複製到這裡 2 | 3 | //================================================================ 4 | function ozanNotify(subtitle='', message='') { 5 | $notification.post('Ozan 設定 UUID', subtitle, message); 6 | }; 7 | $persistentStore.write(uuid, 'OzanFakeUUID'); 8 | ozanNotify( 9 | '偽造 UUID', 10 | '成功設定 UUID: ' + uuid + ' ✅' 11 | ); 12 | $done(); -------------------------------------------------------------------------------- /scripts/shein_checkin.js: -------------------------------------------------------------------------------- 1 | let showNotification = true; 2 | let token = null; 3 | 4 | function surgeNotify(subtitle = '', message = '') { 5 | $notification.post('SHEIN 簽到', subtitle, message, { 6 | 'url': '' 7 | }); 8 | }; 9 | 10 | function handleError(error) { 11 | if (Array.isArray(error)) { 12 | console.log(`❌ ${error[0]} ${error[1]}`); 13 | if (showNotification) { 14 | surgeNotify(error[0], error[1]); 15 | } 16 | } else { 17 | console.log(`❌ ${error}`); 18 | if (showNotification) { 19 | surgeNotify(error); 20 | } 21 | } 22 | } 23 | 24 | async function preCheck() { 25 | return new Promise((resolve, reject) => { 26 | const sheinToken = $persistentStore.read('SheinToken'); 27 | if (!sheinToken || sheinToken.length === 0) { 28 | return reject(['檢查失敗 ‼️', '找不到 token']); 29 | } 30 | token = sheinToken; 31 | return resolve(); 32 | }); 33 | } 34 | 35 | async function checkIn() { 36 | return new Promise((resolve, reject) => { 37 | try { 38 | const request = { 39 | url: 'https://api-shein.shein.com/h5/check_in/check', 40 | headers: { 41 | 'token': token, 42 | 'siteuid': 'iosshtw', 43 | } 44 | }; 45 | $httpClient.get(request, function (error, response, data) { 46 | if (error) { 47 | return reject(['簽到失敗 ‼️', '連線錯誤']); 48 | } else { 49 | if (response.status === 200) { 50 | const obj = JSON.parse(data); 51 | if (obj.info) { 52 | if (obj.info.check_success === 1) { 53 | return resolve(obj.info.daily_reward); 54 | } else if (obj.info.check_success === 2) { 55 | showNotification = false; 56 | return reject(['簽到失敗 ‼️', '今日已簽到']); 57 | } else { 58 | return reject(['簽到失敗 ‼️', 'Error: ' + obj.info]); 59 | } 60 | } else { 61 | return reject(['簽到失敗 ‼️', 'Error: ' + obj.info]); 62 | } 63 | } else { 64 | return reject(['簽到失敗 ‼️', response.status]); 65 | } 66 | } 67 | }); 68 | } catch (error) { 69 | return reject(['簽到失敗 ‼️', error]); 70 | } 71 | }); 72 | } 73 | 74 | (async () => { 75 | console.log('ℹ️ SHEIN 自動簽到 v20230215.1'); 76 | try { 77 | await preCheck(); 78 | console.log('✅ 檢查成功'); 79 | await checkIn(); 80 | console.log('✅ 簽到成功'); 81 | 82 | surgeNotify('簽到成功 ✅', ''); 83 | } catch (error) { 84 | handleError(error); 85 | } 86 | $done(); 87 | })(); 88 | -------------------------------------------------------------------------------- /scripts/shein_token.js: -------------------------------------------------------------------------------- 1 | let showNotification = true; 2 | 3 | function surgeNotify(subtitle = '', message = '') { 4 | $notification.post('SHEIN token', subtitle, message, { 'url': '' }); 5 | }; 6 | 7 | function handleError(error) { 8 | if (Array.isArray(error)) { 9 | console.log(`❌ ${error[0]} ${error[1]}`); 10 | if (showNotification) { 11 | surgeNotify(error[0], error[1]); 12 | } 13 | } else { 14 | console.log(`❌ ${error}`); 15 | if (showNotification) { 16 | surgeNotify(error); 17 | } 18 | } 19 | } 20 | 21 | function isManualRun(checkRequest = false, checkResponse = false) { 22 | if (checkRequest) { 23 | return !$request || ($request.body && JSON.parse($request.body).foo === 'bar'); 24 | } 25 | if (checkResponse) { 26 | return !$response || ($response.body && JSON.parse($response.body).foo === 'bar'); 27 | } 28 | return false; 29 | } 30 | 31 | async function getToken() { 32 | return new Promise((resolve, reject) => { 33 | try { 34 | const token = $request.headers['token'] || $request.headers['Token']; 35 | if (token) { 36 | const save = $persistentStore.write(token, 'SheinToken'); 37 | if (save) { 38 | return resolve(); 39 | } else { 40 | return reject(['保存失敗 ‼️', '無法儲存 token']); 41 | } 42 | } else { 43 | return reject(['保存失敗 ‼️', '無法取得 token']); 44 | } 45 | } catch (error) { 46 | return reject(['保存失敗 ‼️', error]); 47 | } 48 | }); 49 | } 50 | 51 | (async () => { 52 | console.log('ℹ️ SHEIN 取得 token v20230115.1'); 53 | try { 54 | if (isManualRun(true, false)) { 55 | const error = '❌ 請勿手動執行此腳本'; 56 | console.log(error); 57 | surgeNotify(error, ''); 58 | $done({}); 59 | return; 60 | } 61 | 62 | await getToken(); 63 | console.log('✅ token 保存成功'); 64 | surgeNotify( 65 | '保存成功 🍪', 66 | '' 67 | ); 68 | } catch (error) { 69 | handleError(error); 70 | } 71 | $done({}); 72 | })(); 73 | -------------------------------------------------------------------------------- /scripts/shopee_auto_crop.js: -------------------------------------------------------------------------------- 1 | let showNotification = true; 2 | let config = null; 3 | let createCropRequest = null; 4 | 5 | function surgeNotify(subtitle = '', message = '') { 6 | $notification.post('🍤 蝦蝦果園自動種植', subtitle, message, { 'url': 'shopeetw://' }); 7 | } 8 | 9 | function handleError(error) { 10 | if (Array.isArray(error)) { 11 | console.log(`❌ ${error[0]} ${error[1]}`); 12 | if (showNotification) { 13 | surgeNotify(error[0], error[1]); 14 | } 15 | } else { 16 | console.log(`❌ ${error}`); 17 | if (showNotification) { 18 | surgeNotify(error); 19 | } 20 | } 21 | } 22 | 23 | function getSaveObject(key) { 24 | const string = $persistentStore.read(key); 25 | return !string || string.length === 0 ? {} : JSON.parse(string); 26 | } 27 | 28 | function isEmptyObject(obj) { 29 | return Object.keys(obj).length === 0 && obj.constructor === Object ? true : false; 30 | } 31 | 32 | function cookieToString(cookieObject) { 33 | let string = ''; 34 | for (const [key, value] of Object.entries(cookieObject)) { 35 | string += `${key}=${value};` 36 | } 37 | return string; 38 | } 39 | 40 | async function delay(seconds) { 41 | console.log(`⏰ 等待 ${seconds} 秒...`); 42 | return new Promise((resolve) => { 43 | setTimeout(() => { 44 | resolve(); 45 | }, seconds * 1000); 46 | }); 47 | } 48 | 49 | async function preCheck() { 50 | return new Promise((resolve, reject) => { 51 | const shopeeInfo = getSaveObject('ShopeeInfo'); 52 | if (isEmptyObject(shopeeInfo)) { 53 | return reject(['檢查失敗 ‼️', '找不到 token']); 54 | } 55 | 56 | const shopeeFarmInfo = getSaveObject('ShopeeFarmInfo'); 57 | if (isEmptyObject(shopeeFarmInfo)) { 58 | return reject(['檢查失敗 ‼️', '找不到蝦蝦果園資料']); 59 | } 60 | 61 | const shopeeHeaders = { 62 | 'Cookie': cookieToString(shopeeInfo.token), 63 | 'Content-Type': 'application/json', 64 | } 65 | 66 | const autoCropSeedName = $persistentStore.read('ShopeeAutoCropSeedName') || ''; 67 | 68 | if (!shopeeFarmInfo.currentCrop || !shopeeFarmInfo.currentCrop.s || shopeeFarmInfo.currentCrop.s.length < 64) { 69 | return reject(['檢查失敗 ‼️', '請先種植任意種子以取得 token']); 70 | } 71 | if (!autoCropSeedName || !autoCropSeedName.length) { 72 | return reject(['檢查失敗 ‼️', '沒有指定作物名稱']); 73 | } 74 | 75 | config = { 76 | shopeeInfo: shopeeInfo, 77 | shopeeFarmInfo: shopeeFarmInfo, 78 | shopeeHeaders: shopeeHeaders, 79 | autoCropSeedNames: autoCropSeedName.split(','), 80 | } 81 | return resolve(); 82 | }); 83 | } 84 | 85 | async function getSeedList() { 86 | return new Promise((resolve, reject) => { 87 | try { 88 | const request = { 89 | url: `https://games.shopee.tw/farm/api/orchard/crop/meta/get?t=${new Date().getTime()}`, 90 | headers: config.shopeeHeaders, 91 | }; 92 | $httpClient.get(request, function (error, response, data) { 93 | if (error) { 94 | return reject(['取得種子列表失敗 ‼️', '請重新登入']); 95 | } 96 | else { 97 | if (response.status === 200) { 98 | const obj = JSON.parse(data); 99 | if (obj.msg === 'success') { 100 | const cropMetas = obj.data.cropMetas; 101 | let found = false; 102 | let haveSeed = true; 103 | for (const cropName of config.autoCropSeedNames) { 104 | for (const crop of cropMetas) { 105 | // console.log(`🔍 找到「${crop.name}」種子`); 106 | if (crop.name.includes(cropName)) { 107 | if (crop.config.startTime < new Date().getTime() && crop.config.endTime > new Date().getTime()) { 108 | found = true; 109 | if (crop.totalNum <= crop.curNum) { 110 | haveSeed = false; 111 | console.log(`❌「${crop.name}」已經被搶購一空!`); 112 | } 113 | else { 114 | createCropRequest = { 115 | url: `https://games.shopee.tw/farm/api/orchard/crop/create?t=${new Date().getTime()}`, 116 | headers: config.shopeeHeaders, 117 | body: { 118 | metaId: crop.id, 119 | s: config.shopeeFarmInfo.currentCrop.s, 120 | } 121 | } 122 | return resolve(crop.name); 123 | } 124 | } 125 | } 126 | } 127 | } 128 | if (found === false) { 129 | return reject(['取得種子失敗 ‼️', `今天沒有${config.autoCropSeedNames.join('或')}的種子`]); 130 | } 131 | if (haveSeed === false) { 132 | return reject(['取得種子失敗 ‼️', `今天的${config.autoCropSeedNames.join('和')}種子已經被搶購一空!`]); 133 | } 134 | } else { 135 | return reject(['取得種子列表失敗 ‼️', `錯誤代號:${obj.code},訊息:${obj.msg}`]); 136 | } 137 | } else { 138 | return reject(['取得種子列表失敗 ‼️', response.status]); 139 | } 140 | } 141 | }); 142 | } catch (error) { 143 | return reject(['取得種子列表失敗 ‼️', error]); 144 | } 145 | }); 146 | } 147 | 148 | async function createCrop() { 149 | return new Promise((resolve, reject) => { 150 | try { 151 | $httpClient.post(createCropRequest, function (error, response, data) { 152 | if (error) { 153 | return reject(['自動種植失敗 ‼️', '連線錯誤']); 154 | } 155 | else { 156 | if (response.status === 200) { 157 | const obj = JSON.parse(data); 158 | if (obj.msg === 'success') { 159 | const cropId = obj.data.crop.id; 160 | let shopeeFarmInfo = getSaveObject('ShopeeFarmInfo'); 161 | shopeeFarmInfo.currentCrop.cropId = cropId; 162 | const save = $persistentStore.write(JSON.stringify(shopeeFarmInfo, null, 4), 'ShopeeFarmInfo'); 163 | if (!save) { 164 | return reject(['保存失敗 ‼️', '無法儲存作物資料']); 165 | } 166 | return resolve(); 167 | } else if (obj.code === 409003) { 168 | return reject(['自動種植失敗 ‼️', `目前有正在種的作物「${obj.data.crop.meta.name}」`]); 169 | } else if (obj.code === 409009) { 170 | return reject(['自動種植失敗 ‼️', `尚未開放種植「${obj.data.crop.meta.name}」`]); 171 | } else { 172 | return reject(['自動種植失敗 ‼️', `錯誤代號:${obj.code},訊息:${obj.msg}`]); 173 | } 174 | } else { 175 | return reject(['自動種植失敗 ‼️', response.status]); 176 | } 177 | } 178 | }); 179 | } catch (error) { 180 | return reject(['自動種植失敗 ‼️', error]); 181 | } 182 | }); 183 | } 184 | 185 | (async () => { 186 | console.log('ℹ️ 蝦蝦果園自動種植 v20230206.2'); 187 | try { 188 | await preCheck(); 189 | console.log('✅ 檢查成功'); 190 | await delay(0.5); 191 | const cropName = await getSeedList(); 192 | console.log('✅ 取得種子成功'); 193 | await createCrop(); 194 | surgeNotify( 195 | '自動種植成功 🌱', 196 | `正在種植「${cropName}」` 197 | ); 198 | } catch (error) { 199 | handleError(error); 200 | } 201 | $done(); 202 | })(); 203 | -------------------------------------------------------------------------------- /scripts/shopee_auto_harvest.js: -------------------------------------------------------------------------------- 1 | let showNotification = true; 2 | let config = null; 3 | 4 | function surgeNotify(subtitle = '', message = '') { 5 | $notification.post('🍤 蝦蝦果園自動收成', subtitle, message, { 'url': 'shopeetw://' }); 6 | }; 7 | 8 | function handleError(error) { 9 | if (Array.isArray(error)) { 10 | console.log(`❌ ${error[0]} ${error[1]}`); 11 | if (showNotification) { 12 | surgeNotify(error[0], error[1]); 13 | } 14 | } else { 15 | console.log(`❌ ${error}`); 16 | if (showNotification) { 17 | surgeNotify(error); 18 | } 19 | } 20 | } 21 | 22 | function getSaveObject(key) { 23 | const string = $persistentStore.read(key); 24 | return !string || string.length === 0 ? {} : JSON.parse(string); 25 | } 26 | 27 | function isEmptyObject(obj) { 28 | return Object.keys(obj).length === 0 && obj.constructor === Object ? true : false; 29 | } 30 | 31 | function cookieToString(cookieObject) { 32 | let string = ''; 33 | for (const [key, value] of Object.entries(cookieObject)) { 34 | string += `${key}=${value};` 35 | } 36 | return string; 37 | } 38 | 39 | async function preCheck() { 40 | return new Promise((resolve, reject) => { 41 | const shopeeInfo = getSaveObject('ShopeeInfo'); 42 | if (isEmptyObject(shopeeInfo)) { 43 | return reject(['檢查失敗 ‼️', '找不到 token']); 44 | } 45 | 46 | const shopeeFarmInfo = getSaveObject('ShopeeFarmInfo'); 47 | if (isEmptyObject(shopeeFarmInfo)) { 48 | return reject(['檢查失敗 ‼️', '沒有蝦蝦果園資料']); 49 | } 50 | 51 | const shopeeHeaders = { 52 | 'Cookie': cookieToString(shopeeInfo.token), 53 | 'Content-Type': 'application/json', 54 | } 55 | config = { 56 | shopeeInfo: shopeeInfo, 57 | shopeeFarmInfo: shopeeFarmInfo, 58 | shopeeHeaders: shopeeHeaders, 59 | } 60 | return resolve(); 61 | }); 62 | } 63 | 64 | async function harvest() { 65 | return new Promise((resolve, reject) => { 66 | try { 67 | if (!config.shopeeFarmInfo.currentCrop || config.shopeeFarmInfo.currentCrop.cropId === 0) { 68 | showNotification = false; 69 | return reject(['收成失敗 ‼️', '目前沒有作物']); 70 | } 71 | 72 | const request = { 73 | url: 'https://games.shopee.tw/farm/api/orchard/crop/harvest', 74 | headers: config.shopeeHeaders, 75 | body: config.shopeeFarmInfo.currentCrop, 76 | }; 77 | 78 | $httpClient.post(request, function (error, response, data) { 79 | if (error) { 80 | return reject(['收成失敗 ‼️', '連線錯誤']); 81 | } else { 82 | if (response.status === 200) { 83 | const obj = JSON.parse(data); 84 | if (obj.code === 0) { 85 | const cropName = obj.data.crop.meta.name; 86 | const ctaUrl = obj.data.reward.rewardItems[0].itemExtraData.ctaUrl; 87 | if (ctaUrl !== '') { 88 | return resolve(cropName); 89 | } else { 90 | showNotification = false; 91 | 92 | // 刪除作物資料 93 | let shopeeFarmInfo = getSaveObject('ShopeeFarmInfo'); 94 | shopeeFarmInfo.currentCrop.cropId = 0; 95 | const save = $persistentStore.write(JSON.stringify(shopeeFarmInfo, null, 4), 'ShopeeFarmInfo'); 96 | if (!save) { 97 | console.log('⚠️ 儲存失敗,無法更新作物資料'); 98 | } 99 | 100 | return reject(['收成失敗 ‼️', `已經收成過 ${cropName} 了`]); 101 | } 102 | } 103 | else if (obj.code === 409004) { 104 | showNotification = false; 105 | return reject(['收成失敗 ‼️', '作物尚未達到收成階段']); 106 | } else if (obj.code === 404000) { 107 | return reject(['收成失敗 ‼️', '找不到作物']); 108 | } else { 109 | return reject(['收成失敗 ‼️', `錯誤代號:${obj.code},訊息:${obj.msg}`]); 110 | } 111 | } else { 112 | return reject(['收成失敗 ‼️', response.status]); 113 | } 114 | } 115 | }); 116 | } catch (error) { 117 | return reject(['收成失敗 ‼️', error]); 118 | } 119 | }); 120 | } 121 | 122 | (async () => { 123 | console.log('ℹ️ 蝦蝦果園自動收成 v20230210.1'); 124 | try { 125 | await preCheck(); 126 | console.log('✅ 檢查成功'); 127 | const cropName = await harvest(); 128 | console.log('✅ 收成成功'); 129 | console.log(`ℹ️ 獲得 ${cropName}`); 130 | surgeNotify( 131 | '收成成功 ✅', 132 | `獲得 ${cropName} 🌳` 133 | ); 134 | } catch (error) { 135 | handleError(error); 136 | } 137 | $done(); 138 | })(); 139 | -------------------------------------------------------------------------------- /scripts/shopee_auto_water.js: -------------------------------------------------------------------------------- 1 | let showNotification = true; 2 | let config = null; 3 | 4 | function surgeNotify(subtitle = '', message = '') { 5 | $notification.post('🍤 蝦蝦果園自動澆水', subtitle, message, { 'url': 'shopeetw://' }); 6 | }; 7 | 8 | function handleError(error) { 9 | if (Array.isArray(error)) { 10 | console.log(`❌ ${error[0]} ${error[1]}`); 11 | if (showNotification) { 12 | surgeNotify(error[0], error[1]); 13 | } 14 | } else { 15 | console.log(`❌ ${error}`); 16 | if (showNotification) { 17 | surgeNotify(error); 18 | } 19 | } 20 | } 21 | 22 | function getSaveObject(key) { 23 | const string = $persistentStore.read(key); 24 | return !string || string.length === 0 ? {} : JSON.parse(string); 25 | } 26 | 27 | function isEmptyObject(obj) { 28 | return Object.keys(obj).length === 0 && obj.constructor === Object ? true : false; 29 | } 30 | 31 | function cookieToString(cookieObject) { 32 | let string = ''; 33 | for (const [key, value] of Object.entries(cookieObject)) { 34 | string += `${key}=${value};` 35 | } 36 | return string; 37 | } 38 | 39 | async function preCheck() { 40 | return new Promise((resolve, reject) => { 41 | const shopeeInfo = getSaveObject('ShopeeInfo'); 42 | if (isEmptyObject(shopeeInfo)) { 43 | return reject(['檢查失敗 ‼️', '找不到 token']); 44 | } 45 | 46 | const shopeeFarmInfo = getSaveObject('ShopeeFarmInfo'); 47 | if (isEmptyObject(shopeeFarmInfo)) { 48 | return reject(['檢查失敗 ‼️', '沒有蝦蝦果園資料']); 49 | } 50 | 51 | const shopeeHeaders = { 52 | 'Cookie': cookieToString(shopeeInfo.token), 53 | 'Content-Type': 'application/json', 54 | } 55 | config = { 56 | shopeeInfo: shopeeInfo, 57 | shopeeFarmInfo: shopeeFarmInfo, 58 | shopeeHeaders: shopeeHeaders, 59 | } 60 | return resolve(); 61 | }); 62 | } 63 | 64 | async function deleteOldData() { 65 | return new Promise((resolve, reject) => { 66 | try { 67 | $persistentStore.write(null, 'ShopeeAutoCropName'); 68 | $persistentStore.write(null, 'ShopeeCrop'); 69 | $persistentStore.write(null, 'ShopeeCropState'); 70 | $persistentStore.write(null, 'ShopeeCropName'); 71 | $persistentStore.write(null, 'ShopeeCropToken'); 72 | $persistentStore.write(null, 'ShopeeGroceryStoreToken'); 73 | 74 | let shopeeFarmInfo = getSaveObject('ShopeeFarmInfo'); 75 | delete shopeeFarmInfo['autoCropSeedName']; 76 | const save = $persistentStore.write(JSON.stringify(shopeeFarmInfo, null, 4), 'ShopeeFarmInfo'); 77 | if (!save) { 78 | return reject(['保存失敗 ‼️', '無法更新作物資料']); 79 | } else { 80 | return resolve(); 81 | } 82 | 83 | return resolve(); 84 | } catch (error) { 85 | return reject(['刪除舊資料發生錯誤 ‼️', error]); 86 | } 87 | }); 88 | } 89 | 90 | // async function updatePersistentStore() { 91 | // return new Promise((resolve, reject) => { 92 | // try { 93 | // let shopeeFarmInfo = getSaveObject('ShopeeFarmInfo'); 94 | // const currentCrop = JSON.parse($persistentStore.read('ShopeeCrop')) || {}; 95 | // const autoCropSeedName = $persistentStore.read('ShopeeCropName') || ''; 96 | // const groceryStoreToken = $persistentStore.read('ShopeeGroceryStoreToken') || ''; 97 | 98 | // if (!shopeeFarmInfo.currentCrop) { 99 | // shopeeFarmInfo.currentCrop = currentCrop; 100 | // } 101 | // if (!shopeeFarmInfo.autoCropSeedName) { 102 | // shopeeFarmInfo.autoCropSeedName = autoCropSeedName; 103 | // } 104 | // if (!shopeeFarmInfo.groceryStoreToken) { 105 | // shopeeFarmInfo.groceryStoreToken = groceryStoreToken; 106 | // } 107 | 108 | // const save = $persistentStore.write(JSON.stringify(shopeeFarmInfo, null, 4), 'ShopeeFarmInfo'); 109 | // if (!save) { 110 | // return reject(['保存失敗 ‼️', '無法更新作物資料']); 111 | // } else { 112 | // return resolve(); 113 | // } 114 | // } catch (error) { 115 | // return reject(['更新儲存資料失敗 ‼️', error]); 116 | // } 117 | // }); 118 | // } 119 | 120 | async function water() { 121 | return new Promise((resolve, reject) => { 122 | try { 123 | if (!config.shopeeFarmInfo.currentCrop || config.shopeeFarmInfo.currentCrop.cropId === 0) { 124 | showNotification = false; 125 | return reject(['澆水失敗 ‼️', '目前沒有作物']); 126 | } 127 | 128 | const waterRequest = { 129 | url: 'https://games.shopee.tw/farm/api/orchard/crop/water?t=' + new Date().getTime(), 130 | headers: config.shopeeHeaders, 131 | body: config.shopeeFarmInfo.currentCrop, 132 | }; 133 | 134 | $httpClient.post(waterRequest, function (error, response, data) { 135 | if (error) { 136 | return reject(['澆水失敗 ‼️', '連線錯誤']); 137 | } else { 138 | if (response.status === 200) { 139 | const obj = JSON.parse(data); 140 | if (obj.code === 0) { 141 | const useNumber = obj.data.useNumber; 142 | const state = obj.data.crop.state; 143 | const exp = obj.data.crop.exp; 144 | const levelExp = obj.data.crop.meta.config.levelConfig[state.toString()].exp; 145 | const remain = levelExp - exp; 146 | return resolve({ 147 | state: state, 148 | useNumber: useNumber, 149 | remain: remain, 150 | }); 151 | } else if (obj.code === 409000) { 152 | showNotification = false; 153 | return reject(['澆水失敗 ‼️', '水壺目前沒水']); 154 | } else if (obj.code === 403005) { 155 | return reject(['澆水失敗 ‼️', '作物狀態錯誤,請先手動澆水一次']); 156 | } else if (obj.code === 409004) { 157 | return reject(['澆水失敗 ‼️', '作物狀態錯誤,請檢查是否已收成']); 158 | } else { 159 | return reject(['澆水失敗 ‼️', `錯誤代號:${obj.code},訊息:${obj.msg}`]); 160 | } 161 | } else { 162 | return reject(['澆水失敗 ‼️', response.status]); 163 | } 164 | } 165 | }); 166 | } catch (error) { 167 | return reject(['澆水失敗 ‼️', error]); 168 | } 169 | }); 170 | } 171 | 172 | (async () => { 173 | console.log('ℹ️ 蝦蝦果園自動澆水 v20230210.1'); 174 | try { 175 | await preCheck(); 176 | console.log('✅ 檢查成功'); 177 | await deleteOldData(); 178 | console.log('✅ 刪除舊資料成功'); 179 | // await updatePersistentStore(); 180 | // console.log('✅ 更新儲存資料成功'); 181 | const result = await water(); 182 | console.log('✅ 澆水成功'); 183 | 184 | if (result.state === 3) { 185 | console.log(`本次澆了: ${result.useNumber} 滴水 💧,剩餘 ${result.remain} 滴水收成`); 186 | } else { 187 | console.log(`本次澆了: ${result.useNumber} 滴水 💧,剩餘 ${result.remain} 滴水成長至下一階段`); 188 | } 189 | 190 | if (result.remain === 0) { 191 | surgeNotify( 192 | '澆水成功 ✅', 193 | '種植完畢,作物可以收成啦 🌳' 194 | ); 195 | } 196 | } catch (error) { 197 | handleError(error); 198 | } 199 | $done(); 200 | })(); 201 | -------------------------------------------------------------------------------- /scripts/shopee_brand_store_water.js: -------------------------------------------------------------------------------- 1 | let showNotification = true; 2 | let config = null; 3 | 4 | function surgeNotify(subtitle = '', message = '') { 5 | $notification.post('🍤 蝦蝦果園品牌商店水滴', subtitle, message, { 6 | 'url': 'shopeetw://' 7 | }); 8 | }; 9 | 10 | function handleError(error) { 11 | if (Array.isArray(error)) { 12 | console.log(`❌ ${error[0]} ${error[1]}`); 13 | if (showNotification) { 14 | surgeNotify(error[0], error[1]); 15 | } 16 | } else { 17 | console.log(`❌ ${error}`); 18 | if (showNotification) { 19 | surgeNotify(error); 20 | } 21 | } 22 | } 23 | 24 | function getSaveObject(key) { 25 | const string = $persistentStore.read(key); 26 | return !string || string.length === 0 ? {} : JSON.parse(string); 27 | } 28 | 29 | function isEmptyObject(obj) { 30 | return Object.keys(obj).length === 0 && obj.constructor === Object ? true : false; 31 | } 32 | 33 | function cookieToString(cookieObject) { 34 | let string = ''; 35 | for (const [key, value] of Object.entries(cookieObject)) { 36 | string += `${key}=${value};` 37 | } 38 | return string; 39 | } 40 | 41 | async function preCheck() { 42 | return new Promise((resolve, reject) => { 43 | const shopeeInfo = getSaveObject('ShopeeInfo'); 44 | if (isEmptyObject(shopeeInfo)) { 45 | return reject(['檢查失敗 ‼️', '找不到 token']); 46 | } 47 | 48 | const shopeeFarmInfo = getSaveObject('ShopeeFarmInfo'); 49 | if (isEmptyObject(shopeeFarmInfo)) { 50 | return reject(['檢查失敗 ‼️', '沒有蝦蝦果園資料']); 51 | } 52 | 53 | const shopeeHeaders = { 54 | 'Cookie': cookieToString(shopeeInfo.token), 55 | 'Content-Type': 'application/json', 56 | } 57 | config = { 58 | shopeeInfo: shopeeInfo, 59 | shopeeFarmInfo: shopeeFarmInfo, 60 | shopeeHeaders: shopeeHeaders, 61 | } 62 | return resolve(); 63 | }); 64 | } 65 | 66 | function getActivityId(url) { 67 | let activityId = ''; 68 | const re = url.includes('taskId=') ? /taskId=(.*)&/i : /taskId%3D(.*)%26/i; 69 | const found = url.match(re); 70 | activityId = found[1]; 71 | return activityId; 72 | } 73 | 74 | async function getBrandList() { 75 | return new Promise((resolve, reject) => { 76 | try { 77 | const request = { 78 | url: 'https://games.shopee.tw/farm/api/brands_ads/task/list', 79 | headers: config.shopeeHeaders, 80 | }; 81 | 82 | $httpClient.get(request, function (error, response, data) { 83 | if (error) { 84 | return reject(['取得品牌商店列表失敗 ‼️', '連線錯誤']); 85 | } else { 86 | if (response.status === 200) { 87 | const obj = JSON.parse(data); 88 | if (obj.code === 0) { 89 | let brandStores = []; 90 | const tasks = obj.data.userTasks.concat(obj.data.shopAdsTask); 91 | for (const store of tasks) { 92 | if (store.taskFinishStatus < 3) { 93 | const storeInfo = store.taskInfo 94 | const storeUserName = store.rcmd_shop_info ? store.rcmd_shop_info.shop_user_name : storeInfo.taskName; 95 | const moduleId = store.taskInfo.moduleId.toString(); 96 | const taskId = getActivityId(storeInfo.ctaUrl); 97 | brandStores.push({ 98 | 'shop_id': store.shopAdsRcmdShopInfo ? store.shopAdsRcmdShopInfo.rcmdShopInfo.shopId : 0, 99 | 'storeName': storeInfo.taskName, 100 | 'task_id': taskId, 101 | 'module_id': moduleId, 102 | 'brandName': storeUserName, 103 | 'waterValue': storeInfo.prizeValue, 104 | }); 105 | } 106 | } 107 | if (!brandStores.length) { 108 | return reject(['取得品牌商店列表失敗 ‼️', '今天沒有品牌商店水滴活動']); 109 | } else { 110 | return resolve(brandStores); 111 | } 112 | } else { 113 | return reject(['取得品牌商店列表失敗 ‼️', `錯誤代號:${obj.code},訊息:${obj.msg}`]); 114 | } 115 | } else { 116 | return reject(['取得品牌商店列表失敗 ‼️', response.status]); 117 | } 118 | } 119 | }); 120 | } catch (error) { 121 | return reject(['取得品牌商店列表失敗 ‼️', error]); 122 | } 123 | }); 124 | } 125 | 126 | async function toBrandWater() { 127 | const brandStores = await getBrandList(); 128 | 129 | let totalClaimedWater = 0; 130 | if (brandStores.length > 0) { 131 | for (const store of brandStores) { 132 | const token = await getBrandToken(store); 133 | await delay(parseInt(config.brandDelay) + 0.1); 134 | let new_store = await componentReport(store, token); 135 | await delay(1); 136 | await claim(new_store); 137 | totalClaimedWater += parseInt(store.waterValue); 138 | } 139 | let new_water = await toBrandWater(); 140 | return totalClaimedWater + new_water; 141 | } else { 142 | return 0; 143 | } 144 | } 145 | 146 | async function getBrandToken(store) { 147 | return new Promise((resolve, reject) => { 148 | try { 149 | const request = { 150 | url: `https://games.shopee.tw/gameplatform/api/v3/task/browse/${store.task_id}?module_id=${store.module_id}`, 151 | headers: config.shopeeHeaders, 152 | }; 153 | 154 | $httpClient.get(request, function (error, response, data) { 155 | if (error) { 156 | return reject([`取得 ${store.brandName} token 失敗 ‼️`, '連線錯誤']); 157 | } else { 158 | if (response.status === 200) { 159 | const obj = JSON.parse(data); 160 | if (obj.code === 0) { 161 | console.log(`ℹ️ 取得 ${store.brandName} token 成功`); 162 | const assetsConfig = JSON.parse(obj.data.user_task.task.assets_config); 163 | config.brandDelay = assetsConfig.completion_time; 164 | return resolve(obj.data.report_token); 165 | } else { 166 | return reject([`取得 ${store.brandName} token 失敗 ‼️`, `錯誤代號:${obj.code},訊息:${obj.msg}`]); 167 | } 168 | } else { 169 | return reject([`取得 ${store.brandName} token 失敗 ‼️`, response.status]); 170 | } 171 | } 172 | }); 173 | } catch (error) { 174 | return reject([`取得 ${store.brandName} token 失敗 ‼️`, error]); 175 | } 176 | }); 177 | } 178 | 179 | async function claim(store) { 180 | return new Promise((resolve, reject) => { 181 | try { 182 | const requestId = `__game_platform_task__${store.shop_id}_${config.shopeeInfo.token.SPC_U}_${Math.floor(new Date().getTime())}`; 183 | const claimPayload = { 184 | 'task_id': store.task_id, 185 | 'request_id': requestId, 186 | 'module_id': store.module_id, 187 | }; 188 | const request = { 189 | url: 'https://games.shopee.tw/farm/api/brands_ads/task/claim', 190 | headers: config.shopeeHeaders, 191 | body: JSON.stringify(claimPayload), 192 | }; 193 | $httpClient.post(request, function (error, response, data) { 194 | if (error) { 195 | return reject([`取得品牌商店 ${store.brandName} 水滴失敗 ‼️`, '連線錯誤']); 196 | } else { 197 | if (response.status === 200) { 198 | const obj = JSON.parse(data); 199 | if (obj.code === 0) { 200 | console.log(`ℹ️ 取得品牌商店 ${store.brandName} 水滴成功`); 201 | return resolve(); 202 | } else if (obj.code === 409004) { 203 | return reject([`取得品牌商店 ${store.brandName} 水滴失敗 ‼️`, '作物狀態錯誤,請檢查是否已收成']); 204 | } else if (obj.code === 420101) { 205 | console.log(`❌ 取得品牌商店 ${store.brandName} 水滴失敗 ‼️ 今天已領過`); 206 | return resolve(); 207 | } else { 208 | return reject([`取得 ${store.brandName} 水滴失敗 ‼️`, `錯誤代號:${obj.code},訊息:${obj.msg}`]); 209 | } 210 | } else { 211 | return reject([`取得品牌商店 ${store.brandName} 活動 ID 失敗 ‼️`, response.status]); 212 | } 213 | } 214 | }); 215 | } catch (error) { 216 | return reject([`取得品牌商店 ${store.brandName} 活動 ID 失敗 ‼️`, error]); 217 | } 218 | }); 219 | } 220 | 221 | async function componentReport(store, token) { 222 | return new Promise((resolve, reject) => { 223 | try { 224 | const request = { 225 | url: 'https://games.shopee.tw/gameplatform/api/v3/task/component/report', 226 | headers: config.shopeeHeaders, 227 | body: JSON.stringify({ 228 | 'report_token': token, 229 | }) 230 | }; 231 | $httpClient.post(request, function (error, response, data) { 232 | if (error) { 233 | return reject([`取得品牌商店 ${store.brandName} 水滴失敗 ‼️`, '連線錯誤']); 234 | } else { 235 | if (response.status === 200) { 236 | const obj = JSON.parse(data); 237 | if (obj.code === 150004) { 238 | return reject([`取得品牌商店 ${store.brandName} 水滴失敗 ‼️`, '無法解密 token']); 239 | } 240 | store.shop_id = store.shop_id !== 0 ? store.shop_id : obj.data.user_task.rcmd_shop_info ? obj.data.user_task.rcmd_shop_info.shop_id : 0; 241 | store.task_id = obj.data.user_task.task.id; 242 | store.module_id = obj.data.user_task.task.module_id; 243 | return resolve(store); 244 | } else { 245 | return reject([`取得品牌商店 ${store.brandName} 活動 ID 失敗 ‼️`, response.statusCode]); 246 | } 247 | } 248 | }); 249 | } catch (error) { 250 | return reject([`取得品牌商店 ${store.brandName} 活動 ID 失敗 ‼️`, error]); 251 | } 252 | }); 253 | } 254 | 255 | async function delay(seconds) { 256 | console.log(`⏰ 等待 ${seconds} 秒`); 257 | return new Promise((resolve) => { 258 | setTimeout(() => { 259 | resolve(); 260 | }, seconds * 1000); 261 | }); 262 | } 263 | 264 | (async () => { 265 | console.log('ℹ️ 蝦蝦果園自動澆水 v20230501.1'); 266 | try { 267 | await preCheck(); 268 | console.log('✅ 檢查成功'); 269 | 270 | let totalClaimedWater = await toBrandWater(); 271 | if (totalClaimedWater > 0) { 272 | surgeNotify( 273 | '領取成功 ✅', 274 | `本次共領取了 ${totalClaimedWater} 滴水 💧` 275 | ); 276 | } else { 277 | handleError(['取得品牌商店列表失敗 ‼️', '今天沒有品牌商店水滴活動']) 278 | } 279 | } catch (error) { 280 | handleError(error); 281 | } 282 | $done(); 283 | })(); 284 | -------------------------------------------------------------------------------- /scripts/shopee_checkin.js: -------------------------------------------------------------------------------- 1 | let showNotification = true; 2 | let config = null; 3 | 4 | function shopeeNotify(subtitle = '', message = '') { 5 | $notification.post('🍤 蝦皮每日簽到', subtitle, message, { 'url': 'shopeetw://' }); 6 | }; 7 | 8 | function handleError(error) { 9 | if (Array.isArray(error)) { 10 | console.log(`❌ ${error[0]} ${error[1]}`); 11 | if (showNotification) { 12 | shopeeNotify(error[0], error[1]); 13 | } 14 | } else { 15 | console.log(`❌ ${error}`); 16 | if (showNotification) { 17 | shopeeNotify(error); 18 | } 19 | } 20 | } 21 | 22 | function getSaveObject(key) { 23 | const string = $persistentStore.read(key); 24 | return !string || string.length === 0 ? {} : JSON.parse(string); 25 | } 26 | 27 | function isEmptyObject(obj) { 28 | return Object.keys(obj).length === 0 && obj.constructor === Object ? true : false; 29 | } 30 | 31 | function cookieToString(cookieObject) { 32 | let string = ''; 33 | for (const [key, value] of Object.entries(cookieObject)) { 34 | string += `${key}=${value};` 35 | } 36 | return string; 37 | } 38 | 39 | async function preCheck() { 40 | return new Promise((resolve, reject) => { 41 | const shopeeInfo = getSaveObject('ShopeeInfo'); 42 | if (isEmptyObject(shopeeInfo)) { 43 | return reject(['檢查失敗 ‼️', '沒有新版 token']); 44 | } 45 | const shopeeHeaders = { 46 | 'User-Agent': 'iOS app iPhone Shopee', 47 | 'Cookie': cookieToString(shopeeInfo.token), 48 | 'Content-Type': 'application/json', 49 | } 50 | config = { 51 | shopeeInfo: shopeeInfo, 52 | shopeeHeaders: shopeeHeaders, 53 | } 54 | return resolve(); 55 | }); 56 | } 57 | 58 | async function checkin() { 59 | return new Promise((resolve, reject) => { 60 | try { 61 | if (!config.shopeeInfo.checkinPayload) { 62 | return reject(['簽到失敗 ‼️', '請先手動簽到一次']); 63 | } 64 | 65 | const request = { 66 | url: 'https://games-dailycheckin.shopee.tw/mkt/coins/api/v2/checkin_new', 67 | headers: config.shopeeHeaders, 68 | body: config.shopeeInfo.checkinPayload 69 | }; 70 | 71 | $httpClient.post(request, function (error, response, data) { 72 | if (error) { 73 | return reject(['簽到失敗 ‼️', '連線錯誤']); 74 | } else { 75 | if (response.status === 200) { 76 | const obj = JSON.parse(data); 77 | if (obj.data.success) { 78 | return resolve({ 79 | checkInDay: obj.data.check_in_day, 80 | coins: obj.data.increase_coins, 81 | }); 82 | } else { 83 | showNotification = false; 84 | return reject(['簽到失敗 ‼️', '本日已簽到']); 85 | } 86 | } else { 87 | return reject(['簽到失敗 ‼️', response.status]); 88 | } 89 | } 90 | }); 91 | } catch (error) { 92 | return reject(['簽到失敗 ‼️', error]); 93 | } 94 | }); 95 | } 96 | 97 | (async () => { 98 | console.log('ℹ️ 蝦皮每日簽到 v20231004.1'); 99 | try { 100 | await preCheck(); 101 | console.log('✅ 檢查成功'); 102 | const result = await checkin(); 103 | console.log('✅ 簽到成功'); 104 | console.log(`ℹ️ 目前已連續簽到 ${result.checkInDay} 天,今日已領取 ${result.coins}`); 105 | shopeeNotify( 106 | `簽到成功,目前已連續簽到 ${result.checkInDay} 天`, 107 | `今日已領取 ${result.coins} 💰💰💰` 108 | ); 109 | } catch (error) { 110 | handleError(error); 111 | } 112 | $done(); 113 | })(); 114 | -------------------------------------------------------------------------------- /scripts/shopee_coin_lucky_draw.js: -------------------------------------------------------------------------------- 1 | let showNotification = true; 2 | let config = null; 3 | let getIdRequest = null; 4 | let luckyDrawRequest = null; 5 | 6 | function surgeNotify(subtitle = '', message = '') { 7 | $notification.post('🍤 蝦幣寶箱', subtitle, message, { 'url': 'shopeetw://' }); 8 | }; 9 | 10 | function handleError(error) { 11 | if (Array.isArray(error)) { 12 | console.log(`❌ ${error[0]} ${error[1]}`); 13 | if (showNotification) { 14 | surgeNotify(error[0], error[1]); 15 | } 16 | } else { 17 | console.log(`❌ ${error}`); 18 | if (showNotification) { 19 | surgeNotify(error); 20 | } 21 | } 22 | } 23 | 24 | function getSaveObject(key) { 25 | const string = $persistentStore.read(key); 26 | return !string || string.length === 0 ? {} : JSON.parse(string); 27 | } 28 | 29 | function isEmptyObject(obj) { 30 | return Object.keys(obj).length === 0 && obj.constructor === Object ? true : false; 31 | } 32 | 33 | function cookieToString(cookieObject) { 34 | let string = ''; 35 | for (const [key, value] of Object.entries(cookieObject)) { 36 | string += `${key}=${value};` 37 | } 38 | return string; 39 | } 40 | 41 | async function preCheck() { 42 | return new Promise((resolve, reject) => { 43 | const shopeeInfo = getSaveObject('ShopeeInfo'); 44 | if (isEmptyObject(shopeeInfo)) { 45 | return reject(['檢查失敗 ‼️', '沒有新版 token']); 46 | } 47 | const shopeeHeaders = { 48 | 'Cookie': cookieToString(shopeeInfo.token), 49 | 'Content-Type': 'application/json', 50 | } 51 | config = { 52 | shopeeInfo: shopeeInfo, 53 | shopeeHeaders: shopeeHeaders, 54 | } 55 | return resolve(); 56 | }); 57 | } 58 | 59 | async function eventListGetActivity() { 60 | return new Promise((resolve, reject) => { 61 | try { 62 | const request = { 63 | url: 'https://mall.shopee.tw/api/v4/banner/batch_list', 64 | headers: config.shopeeHeaders, 65 | body: { 66 | types: [{ 'type': 'coin_carousel' }, { 'type': 'coin_square' }], 67 | }, 68 | }; 69 | 70 | $httpClient.post(request, function (error, response, data) { 71 | if (error) { 72 | return reject(['無法取得活動列表 ‼️', '連線錯誤']); 73 | } else { 74 | if (response.status == 200) { 75 | const obj = JSON.parse(data); 76 | const bannerSets = obj.data.banners; 77 | let foundId = false; 78 | for (const bannerSet of bannerSets) { 79 | for (const banner of bannerSet.banners) { 80 | const title = banner.navigate_params.navbar.title; 81 | const url = banner.navigate_params.url; 82 | // console.log(`活動名稱: ${title},網址: ${url}`); 83 | if (title.includes('蝦幣寶箱') || title.includes('天天領蝦幣')) { 84 | const re = /activity\/(.*)\??/i; 85 | const found = url.match(re); 86 | const activityId = found[1]; 87 | foundId = true; 88 | console.log(`✅ 找到蝦幣寶箱活動,活動名稱: ${title},活動頁面 ID: ${activityId}`); 89 | 90 | // 取得活動代碼 91 | getIdRequest = { 92 | url: `https://games.shopee.tw/gameplatform/api/v1/game/activity/${activityId}/settings?appid=E9VFyxwmtgjnCR8uhL&basic=false`, 93 | headers: config.shopeeHeaders, 94 | }; 95 | 96 | // 真正領取蝦幣 97 | luckyDrawRequest = { 98 | url: '', 99 | headers: config.shopeeHeaders, 100 | body: { 101 | request_id: (Math.random() * 10 ** 20).toFixed(0).substring(0, 16), 102 | app_id: 'E9VFyxwmtgjnCR8uhL', 103 | activity_code: activityId, 104 | source: 0, 105 | }, 106 | }; 107 | return resolve(); 108 | } 109 | } 110 | } 111 | if (!foundId) { 112 | return resolve(); 113 | } 114 | } else { 115 | return reject(['無法取得活動列表 ‼️', response.status]); 116 | } 117 | } 118 | }); 119 | } catch (error) { 120 | return reject(['無法取得活動列表 ‼️', error]); 121 | } 122 | }); 123 | } 124 | 125 | async function iframeListGetActivity() { 126 | return new Promise((resolve, reject) => { 127 | try { 128 | const request = { 129 | url: 'https://mall.shopee.tw/api/v4/market_coin/get_iframe_list?region=TW&offset=0&limit=10', 130 | headers: config.shopeeHeaders, 131 | }; 132 | $httpClient.get(request, function (error, response, data) { 133 | if (error) { 134 | return reject(['無法取得活動列表 ‼️', '連線錯誤']); 135 | } else { 136 | if (response.status === 200) { 137 | const obj = JSON.parse(data); 138 | let foundEvent = false; 139 | const iframeList = obj.data.iframe_list; 140 | for (const iframe of iframeList) { 141 | // console.log(`活動名稱: ${iframe.title},網址: ${iframe.url}`); 142 | if ((iframe.title.includes('蝦幣')) && iframe.url.includes('luckydraw')) { 143 | foundEvent = true; 144 | const re = /activity\/(.*)\??/i; 145 | let found = iframe.url.match(re); 146 | if (!found) { 147 | const re = /activity=(.*)&/i; 148 | found = iframe.url.match(re); 149 | } 150 | const activityId = found[1]; 151 | console.log(`ℹ️ 在 iframe 找到蝦幣寶箱活動,活動名稱: ${iframe.title},活動頁面 ID: ${activityId}`); 152 | 153 | // 取得活動代碼 154 | getIdRequest = { 155 | url: `https://games.shopee.tw/gameplatform/api/v1/game/activity/${activityId}/settings?appid=E9VFyxwmtgjnCR8uhL&basic=false`, 156 | headers: config.shopeeHeaders, 157 | }; 158 | 159 | // 真正領取蝦幣寶箱 160 | luckyDrawRequest = { 161 | url: '', 162 | headers: config.shopeeHeaders, 163 | body: { 164 | request_id: (Math.random() * 10 ** 20).toFixed(0).substring(0, 16), 165 | app_id: 'E9VFyxwmtgjnCR8uhL', 166 | activity_code: activityId, 167 | source: 0, 168 | }, 169 | }; 170 | 171 | return resolve(); 172 | } 173 | } 174 | if (!foundEvent) { 175 | return reject(['無法取得活動列表 ‼️', '找不到免運寶箱活動']); 176 | } 177 | } else { 178 | return reject(['無法取得活動列表 ‼️', response.status]); 179 | } 180 | } 181 | }); 182 | } catch (error) { 183 | return reject(['無法取得活動列表 ‼️', error]); 184 | } 185 | }); 186 | } 187 | 188 | async function coinLuckyDrawGetId() { 189 | return new Promise((resolve, reject) => { 190 | try { 191 | $httpClient.get(getIdRequest, function (error, response, data) { 192 | if (error) { 193 | return reject(['活動代碼查詢失敗 ‼️', '連線錯誤']); 194 | } else { 195 | if (response.status === 200) { 196 | const obj = JSON.parse(data); 197 | if (obj.msg === 'success') { 198 | const code = obj.data.basic.event_code; 199 | console.log(`✅ 活動代碼: ${code}`); 200 | luckyDrawRequest.url = `https://games.shopee.tw/luckydraw/api/v1/lucky/event/${code}`; 201 | return resolve(); 202 | } else { 203 | return reject(['活動代碼查詢失敗 ‼️', obj.msg]); 204 | } 205 | } else { 206 | return reject(['活動代碼查詢失敗 ‼️', response.status]); 207 | } 208 | } 209 | }); 210 | } catch (error) { 211 | return reject(['活動代碼查詢失敗 ‼️', error]); 212 | } 213 | }); 214 | } 215 | 216 | async function coinLuckyDraw() { 217 | return new Promise((resolve, reject) => { 218 | try { 219 | $httpClient.post(luckyDrawRequest, function (error, response, data) { 220 | if (error) { 221 | return reject(['領取失敗 ‼️', '連線錯誤']); 222 | } else { 223 | if (response.status == 200) { 224 | const obj = JSON.parse(data); 225 | if (obj.msg === 'success') { 226 | const packageName = obj.data.package_name; 227 | return resolve(packageName); 228 | } 229 | else if (obj.code === 102000) { 230 | showNotification = false; 231 | return reject(['領取失敗 ‼️', '每日只能領一次']); 232 | } else if (obj.msg === 'expired' || obj.msg === 'event already end') { 233 | return reject(['領取失敗 ‼️', '活動已過期。請嘗試更新模組或腳本,或等待作者更新。']); 234 | } else { 235 | return reject(['領取失敗 ‼️', `錯誤代號:${obj.code},訊息:${obj.msg}`]); 236 | } 237 | } else { 238 | return reject(['領取失敗 ‼️', response.status]); 239 | } 240 | } 241 | }); 242 | } catch (error) { 243 | return reject(['領取失敗 ‼️', error]); 244 | } 245 | }); 246 | } 247 | 248 | (async () => { 249 | console.log('ℹ️ 蝦幣寶箱 v20230301.1'); 250 | try { 251 | await preCheck(); 252 | console.log('✅ 檢查成功'); 253 | await eventListGetActivity(); 254 | console.log('✅ banner 取得活動列表'); 255 | if (!getIdRequest) { 256 | console.log('⚠️ 在 banner 找不到蝦幣寶箱活動,繼續嘗試搜尋 iframe'); 257 | await iframeListGetActivity(); 258 | console.log('✅ iframe 取得活動列表'); 259 | } 260 | await coinLuckyDrawGetId(); 261 | console.log('✅ 取得活動代碼'); 262 | const reward = await coinLuckyDraw(); 263 | console.log('✅ 領取成功'); 264 | console.log(`ℹ️ 獲得 👉 ${reward} 💎`); 265 | surgeNotify( 266 | '領取成功 ✅', 267 | `獲得 👉 ${reward} 💎` 268 | ); 269 | } catch (error) { 270 | handleError(error); 271 | } 272 | $done(); 273 | })(); 274 | -------------------------------------------------------------------------------- /scripts/shopee_get_checkin.js: -------------------------------------------------------------------------------- 1 | let showNotification = true; 2 | 3 | function surgeNotify(subtitle = '', message = '') { 4 | $notification.post('🍤 蝦皮簽到 token', subtitle, message, { 'url': 'shopeetw://' }); 5 | }; 6 | 7 | function handleError(error) { 8 | if (Array.isArray(error)) { 9 | console.log(`❌ ${error[0]} ${error[1]}`); 10 | if (showNotification) { 11 | surgeNotify(error[0], error[1]); 12 | } 13 | } else { 14 | console.log(`❌ ${error}`); 15 | if (showNotification) { 16 | surgeNotify(error); 17 | } 18 | } 19 | } 20 | 21 | function getSaveObject(key) { 22 | const string = $persistentStore.read(key); 23 | return !string || string.length === 0 ? {} : JSON.parse(string); 24 | } 25 | 26 | function isManualRun(checkRequest = false, checkResponse = false) { 27 | if (checkRequest) { 28 | return typeof $request === 'undefined' || ($request.body && JSON.parse($request.body).foo === 'bar'); 29 | } 30 | if (checkResponse) { 31 | return typeof $response === 'undefined' || ($response.body && JSON.parse($response.body).foo === 'bar'); 32 | } 33 | return false; 34 | } 35 | 36 | async function getCheckinPayload() { 37 | return new Promise((resolve, reject) => { 38 | try { 39 | const payload = JSON.parse($request.body); 40 | if (payload) { 41 | let shopeeInfo = getSaveObject('ShopeeInfo'); 42 | shopeeInfo.checkinPayload = payload; 43 | const save = $persistentStore.write(JSON.stringify(shopeeInfo, null, 4), 'ShopeeInfo'); 44 | if (!save) { 45 | return reject(['保存失敗 ‼️', '無法儲存簽到資料']); 46 | } else { 47 | return resolve(); 48 | } 49 | } else { 50 | return reject(['保存失敗 ‼️', '請重新登入']); 51 | } 52 | } catch (error) { 53 | return reject(['保存失敗 ‼️', error]); 54 | } 55 | }); 56 | } 57 | 58 | (async () => { 59 | console.log('ℹ️ 蝦皮取得簽到資料 v20230608.1'); 60 | try { 61 | if (isManualRun(true, false)) { 62 | throw '請勿手動執行此腳本'; 63 | } 64 | 65 | await getCheckinPayload(); 66 | console.log('✅ 簽到資料保存成功'); 67 | surgeNotify('保存成功 🍪', ''); 68 | } catch (error) { 69 | handleError(error); 70 | return; 71 | } 72 | $done({}); 73 | })(); 74 | -------------------------------------------------------------------------------- /scripts/shopee_get_crop.js: -------------------------------------------------------------------------------- 1 | function surgeNotify(subtitle = '', message = '') { 2 | $notification.post('🍤 蝦蝦果園作物資料', subtitle, message, { 'url': 'shopeetw://' }); 3 | }; 4 | 5 | function handleError(error) { 6 | if (Array.isArray(error)) { 7 | console.log(`❌ ${error[0]} ${error[1]}`); 8 | if (showNotification) { 9 | surgeNotify(error[0], error[1]); 10 | } 11 | } else { 12 | console.log(`❌ ${error}`); 13 | if (showNotification) { 14 | surgeNotify(error); 15 | } 16 | } 17 | } 18 | 19 | function isManualRun(checkRequest = false, checkResponse = false) { 20 | if (checkRequest) { 21 | return typeof $request === 'undefined' || ($request.body && JSON.parse($request.body).foo === 'bar'); 22 | } 23 | if (checkResponse) { 24 | return typeof $response === 'undefined' || ($response.body && JSON.parse($response.body).foo === 'bar'); 25 | } 26 | return false; 27 | } 28 | 29 | function getSaveObject(key) { 30 | const string = $persistentStore.read(key); 31 | return !string || string.length === 0 ? {} : JSON.parse(string); 32 | } 33 | 34 | async function getCropData() { 35 | return new Promise((resolve, reject) => { 36 | try { 37 | const body = JSON.parse($request.body); 38 | if (body && body.cropId && body.resourceId && body.s) { 39 | let shopeeFarmInfo = getSaveObject('ShopeeFarmInfo'); 40 | shopeeFarmInfo.currentCrop = body; 41 | const save = $persistentStore.write(JSON.stringify(shopeeFarmInfo, null, 4), 'ShopeeFarmInfo'); 42 | if (!save) { 43 | return reject(['保存失敗 ‼️', '無法儲存作物資料']); 44 | } 45 | return resolve(); 46 | } else { 47 | return reject(['作物資料儲存失敗 ‼️', '請重新獲得 Cookie 後再嘗試']); 48 | } 49 | } catch (error) { 50 | return reject(['保存失敗 ‼️', error]); 51 | } 52 | }); 53 | } 54 | 55 | (async () => { 56 | console.log('ℹ️ 蝦蝦果園作物資料 v20230206.1'); 57 | try { 58 | if (isManualRun(true, false)) { 59 | throw '請勿手動執行此腳本'; 60 | } 61 | await getCropData(); 62 | console.log('✅ 作物資料保存成功'); 63 | surgeNotify(`作物資料保存成功 🌱`, ''); 64 | 65 | } catch (error) { 66 | handleError(error); 67 | } 68 | $done({}); 69 | })(); 70 | -------------------------------------------------------------------------------- /scripts/shopee_get_crop_token.js: -------------------------------------------------------------------------------- 1 | function shopeeNotify(subtitle = '', message = '') { 2 | $notification.post('🍤 蝦蝦果園作物 token', subtitle, message, { 'url': 'shopeetw://' }); 3 | }; 4 | 5 | const body = JSON.parse($request.body); 6 | if (body && body.s) { 7 | const saveCropToken = $persistentStore.write(body.s, 'ShopeeCropToken'); 8 | if (!saveCropToken) { 9 | shopeeNotify( 10 | '保存失敗 ‼️', 11 | '請稍後嘗試' 12 | ); 13 | } else { 14 | shopeeNotify( 15 | '保存成功 🌱', 16 | '此動作只需執行一次,更改目標作物不必重新獲得 token。' 17 | ); 18 | } 19 | } else { 20 | shopeeNotify( 21 | 'Cookie 已過期 ‼️', 22 | '請重新登入' 23 | ); 24 | } 25 | $done({}); 26 | -------------------------------------------------------------------------------- /scripts/shopee_get_grocery_store_token.js: -------------------------------------------------------------------------------- 1 | let showNotification = true; 2 | 3 | function surgeNotify(subtitle = '', message = '') { 4 | $notification.post('🍤 蝦蝦果園道具商店 token', subtitle, message, { 'url': 'shopeetw://' }); 5 | }; 6 | 7 | function handleError(error) { 8 | if (Array.isArray(error)) { 9 | console.log(`❌ ${error[0]} ${error[1]}`); 10 | if (showNotification) { 11 | surgeNotify(error[0], error[1]); 12 | } 13 | } else { 14 | console.log(`❌ ${error}`); 15 | if (showNotification) { 16 | surgeNotify(error); 17 | } 18 | } 19 | } 20 | 21 | function getSaveObject(key) { 22 | const string = $persistentStore.read(key); 23 | return !string || string.length === 0 ? {} : JSON.parse(string); 24 | } 25 | 26 | function isManualRun(checkRequest = false, checkResponse = false) { 27 | if (checkRequest) { 28 | return typeof $request === 'undefined' || ($request.body && JSON.parse($request.body).foo === 'bar'); 29 | } 30 | if (checkResponse) { 31 | return typeof $response === 'undefined' || ($response.body && JSON.parse($response.body).foo === 'bar'); 32 | } 33 | return false; 34 | } 35 | 36 | async function getToken() { 37 | return new Promise((resolve, reject) => { 38 | try { 39 | const body = JSON.parse($request.body); 40 | if (body && body.s) { 41 | let shopeeFarmInfo = getSaveObject('ShopeeFarmInfo'); 42 | shopeeFarmInfo.groceryStoreToken = body.s; 43 | const save = $persistentStore.write(JSON.stringify(shopeeFarmInfo, null, 4), 'ShopeeFarmInfo'); 44 | if (!save) { 45 | return reject(['保存失敗 ‼️', '無法儲存 token']); 46 | } else { 47 | return resolve(); 48 | } 49 | } else { 50 | return reject(['作物資料儲存失敗 ‼️', '請重新獲得 Cookie 後再嘗試']); 51 | } 52 | } catch (error) { 53 | return reject(['保存失敗 ‼️', error]); 54 | } 55 | }); 56 | } 57 | 58 | (async () => { 59 | console.log('ℹ️ 蝦蝦果園取得道具商店 token v20230125.1'); 60 | try { 61 | if (isManualRun(true, false)) { 62 | throw '請勿手動執行此腳本'; 63 | } 64 | await getToken(); 65 | console.log(`✅ 道具商店 token 保存成功`); 66 | surgeNotify('保存成功 ✅', ''); 67 | } catch (error) { 68 | handleError(error); 69 | } 70 | $done({}); 71 | })(); 72 | -------------------------------------------------------------------------------- /scripts/shopee_grocery_store_water.js: -------------------------------------------------------------------------------- 1 | let showNotification = true; 2 | let config = null; 3 | 4 | function surgeNotify(subtitle = '', message = '') { 5 | $notification.post('🍤 蝦蝦果園道具商店水滴', subtitle, message, { 'url': 'shopeetw://' }); 6 | }; 7 | 8 | function handleError(error) { 9 | if (Array.isArray(error)) { 10 | console.log(`❌ ${error[0]} ${error[1]}`); 11 | if (showNotification) { 12 | surgeNotify(error[0], error[1]); 13 | } 14 | } else { 15 | console.log(`❌ ${error}`); 16 | if (showNotification) { 17 | surgeNotify(error); 18 | } 19 | } 20 | } 21 | 22 | function getSaveObject(key) { 23 | const string = $persistentStore.read(key); 24 | return !string || string.length === 0 ? {} : JSON.parse(string); 25 | } 26 | 27 | function isEmptyObject(obj) { 28 | return Object.keys(obj).length === 0 && obj.constructor === Object ? true : false; 29 | } 30 | 31 | function cookieToString(cookieObject) { 32 | let string = ''; 33 | for (const [key, value] of Object.entries(cookieObject)) { 34 | string += `${key}=${value};` 35 | } 36 | return string; 37 | } 38 | 39 | async function preCheck() { 40 | return new Promise((resolve, reject) => { 41 | const shopeeInfo = getSaveObject('ShopeeInfo'); 42 | if (isEmptyObject(shopeeInfo)) { 43 | return reject(['檢查失敗 ‼️', '找不到 token']); 44 | } 45 | 46 | const shopeeFarmInfo = getSaveObject('ShopeeFarmInfo'); 47 | if (isEmptyObject(shopeeFarmInfo) || !shopeeFarmInfo.groceryStoreToken.length) { 48 | return reject(['檢查失敗 ‼️', '請先在道具商店領取一次水滴,以儲存 token']); 49 | } 50 | const shopeeGroceryStoreToken = shopeeFarmInfo?.groceryStoreToken; 51 | 52 | const shopeeHeaders = { 53 | 'Cookie': cookieToString(shopeeInfo.token), 54 | 'Content-Type': 'application/json', 55 | }; 56 | 57 | config = { 58 | shopeeInfo: shopeeInfo, 59 | shopeeHeaders: shopeeHeaders, 60 | groceryStoreToken: shopeeGroceryStoreToken 61 | } 62 | return resolve(); 63 | }); 64 | } 65 | 66 | async function claimGroceryStoreWater() { 67 | return new Promise((resolve, reject) => { 68 | try { 69 | const request = { 70 | url: 'https://games.shopee.tw/farm/api/grocery_store/rn_claim', 71 | headers: config.shopeeHeaders, 72 | body: { 73 | s: config.groceryStoreToken 74 | } 75 | }; 76 | $httpClient.post(request, function (error, response, data) { 77 | if (error) { 78 | return reject(['領取失敗 ‼️', '連線錯誤']); 79 | } else { 80 | if (response.status === 200) { 81 | const obj = JSON.parse(data); 82 | if (obj.msg === 'success') { 83 | return resolve(); 84 | } 85 | else if (obj.code === 430007) { 86 | return reject(['領取失敗 ‼️', '超過每日領取上限']); 87 | } 88 | else if (obj.code === 409004) { 89 | return reject(['領取失敗 ‼️', '請檢查作物是否已經收成']); 90 | } 91 | else { 92 | return reject(['領取失敗 ‼️', `錯誤代號:${obj.code},訊息:${obj.msg}`]); 93 | } 94 | } else { 95 | surgeNotify( 96 | 'Cookie 已過期 ‼️', 97 | '請重新登入' 98 | ); 99 | } 100 | } 101 | $done(); 102 | }); 103 | } catch (error) { 104 | return reject(['領取失敗 ‼️', error]); 105 | } 106 | }); 107 | } 108 | 109 | (async () => { 110 | console.log('ℹ️ 蝦蝦果園道具商店水滴 v20230807.1'); 111 | try { 112 | await preCheck(); 113 | console.log('✅ 檢查成功'); 114 | await claimGroceryStoreWater(); 115 | console.log('✅ 領取成功'); 116 | surgeNotify( 117 | '領取成功 ✅', 118 | '' 119 | ); 120 | } catch (error) { 121 | handleError(error); 122 | } 123 | $done(); 124 | })(); 125 | -------------------------------------------------------------------------------- /scripts/shopee_set_auto_crop_seed_name.js: -------------------------------------------------------------------------------- 1 | const cropName = '改掉這一行'; // ← 把大布丁改成想要的種子關鍵字,改完之後按「執行」即可。例如:大布丁、口香糖、養樂多、豆漿、牛奶糖、4蝦幣... 2 | 3 | //================================================================ 4 | let showNotification = true; 5 | 6 | function surgeNotify(subtitle = '', message = '') { 7 | $notification.post('🍤 蝦蝦果園', subtitle, message, { 'url': 'shopeetw://' }); 8 | }; 9 | 10 | function handleError(error) { 11 | if (Array.isArray(error)) { 12 | console.log(`❌ ${error[0]} ${error[1]}`); 13 | if (showNotification) { 14 | surgeNotify(error[0], error[1]); 15 | } 16 | } else { 17 | console.log(`❌ ${error}`); 18 | if (showNotification) { 19 | surgeNotify(error); 20 | } 21 | } 22 | } 23 | 24 | function getSaveObject(key) { 25 | const string = $persistentStore.read(key); 26 | return !string || string.length === 0 ? {} : JSON.parse(string); 27 | } 28 | 29 | async function saveAutoCropName() { 30 | return new Promise((resolve, reject) => { 31 | try { 32 | if (cropName.length === 0 || cropName === '改掉這一行') { 33 | return reject(['保存失敗 ‼️', '請先設定目標作物']); 34 | } 35 | const save = $persistentStore.write(cropName, 'ShopeeAutoCropSeedName'); 36 | if (!save) { 37 | return reject(['保存失敗 ‼️', '無法儲存目標作物']); 38 | } else { 39 | return resolve(); 40 | } 41 | } catch (error) { 42 | return reject(['保存失敗 ‼️', error]); 43 | } 44 | }); 45 | } 46 | 47 | (async () => { 48 | console.log('ℹ️ 蝦蝦果園設定自動種植作物 v20230125.1'); 49 | try { 50 | await saveAutoCropName(); 51 | console.log(`✅ 目標作物「${cropName}」保存成功`); 52 | surgeNotify('目標作物保存成功 ✅', `未來將自動種植「${cropName}」🌱`); 53 | } catch (error) { 54 | handleError(error); 55 | } 56 | $done({}); 57 | })(); 58 | -------------------------------------------------------------------------------- /scripts/shopee_set_crop_name.js: -------------------------------------------------------------------------------- 1 | const cropName = '改掉這一行'; // ← 把大布丁改成想要的種子關鍵字,改完之後按「執行」即可。例如:大布丁、口香糖、養樂多、豆漿、牛奶糖、4蝦幣... 2 | 3 | //================================================================ 4 | let showNotification = true; 5 | 6 | function surgeNotify(subtitle = '', message = '') { 7 | $notification.post('🍤 蝦蝦果園', subtitle, message, { 'url': 'shopeetw://' }); 8 | }; 9 | 10 | function handleError(error) { 11 | if (Array.isArray(error)) { 12 | console.log(`❌ ${error[0]} ${error[1]}`); 13 | if (showNotification) { 14 | surgeNotify(error[0], error[1]); 15 | } 16 | } else { 17 | console.log(`❌ ${error}`); 18 | if (showNotification) { 19 | surgeNotify(error); 20 | } 21 | } 22 | } 23 | 24 | function getSaveObject(key) { 25 | const string = $persistentStore.read(key); 26 | return !string || string.length === 0 ? {} : JSON.parse(string); 27 | } 28 | 29 | async function saveAutoCropName() { 30 | return new Promise((resolve, reject) => { 31 | try { 32 | if (cropName.length === 0 || cropName === '改掉這一行') { 33 | return reject(['保存失敗 ‼️', '請先設定目標作物']); 34 | } 35 | let shopeeFarmInfo = getSaveObject('ShopeeFarmInfo'); 36 | shopeeFarmInfo.autoCropSeedName = cropName; 37 | const save = $persistentStore.write(JSON.stringify(shopeeFarmInfo, null, 4), 'ShopeeFarmInfo'); 38 | if (!save) { 39 | return reject(['保存失敗 ‼️', '無法儲存目標作物']); 40 | } else { 41 | return resolve(); 42 | } 43 | } catch (error) { 44 | return reject(['保存失敗 ‼️', error]); 45 | } 46 | }); 47 | } 48 | 49 | (async () => { 50 | console.log('ℹ️ 蝦蝦果園設定自動種植作物 v20230125.1'); 51 | try { 52 | await saveAutoCropName(); 53 | console.log(`✅ 目標作物「${cropName}」保存成功`); 54 | surgeNotify('目標作物保存成功 ✅', `未來將自動種植「${cropName}」🌱`); 55 | } catch (error) { 56 | handleError(error); 57 | } 58 | $done({}); 59 | })(); 60 | -------------------------------------------------------------------------------- /scripts/shopee_shipping_lucky_draw.js: -------------------------------------------------------------------------------- 1 | let showNotification = true; 2 | let config = null; 3 | let getIdRequest = null; 4 | let luckyDrawRequest = null; 5 | 6 | function surgeNotify(subtitle = '', message = '') { 7 | $notification.post('🍤 蝦皮免運寶箱', subtitle, message, { 'url': 'shopeetw://' }); 8 | }; 9 | 10 | function handleError(error) { 11 | if (Array.isArray(error)) { 12 | console.log(`❌ ${error[0]} ${error[1]}`); 13 | if (showNotification) { 14 | surgeNotify(error[0], error[1]); 15 | } 16 | } else { 17 | console.log(`❌ ${error}`); 18 | if (showNotification) { 19 | surgeNotify(error); 20 | } 21 | } 22 | } 23 | 24 | function getSaveObject(key) { 25 | const string = $persistentStore.read(key); 26 | return !string || string.length === 0 ? {} : JSON.parse(string); 27 | } 28 | 29 | function isEmptyObject(obj) { 30 | return Object.keys(obj).length === 0 && obj.constructor === Object ? true : false; 31 | } 32 | 33 | function cookieToString(cookieObject) { 34 | let string = ''; 35 | for (const [key, value] of Object.entries(cookieObject)) { 36 | string += `${key}=${value};` 37 | } 38 | return string; 39 | } 40 | 41 | async function preCheck() { 42 | return new Promise((resolve, reject) => { 43 | const shopeeInfo = getSaveObject('ShopeeInfo'); 44 | if (isEmptyObject(shopeeInfo)) { 45 | return reject(['檢查失敗 ‼️', '沒有新版 token']); 46 | } 47 | const shopeeHeaders = { 48 | 'Cookie': cookieToString(shopeeInfo.token), 49 | 'Content-Type': 'application/json', 50 | } 51 | config = { 52 | shopeeInfo: shopeeInfo, 53 | shopeeHeaders: shopeeHeaders, 54 | } 55 | return resolve(); 56 | }); 57 | } 58 | 59 | async function eventListGetActivity() { 60 | return new Promise((resolve, reject) => { 61 | try { 62 | const request = { 63 | url: 'https://mall.shopee.tw/api/v4/banner/batch_list', 64 | headers: config.shopeeHeaders, 65 | body: { 66 | 'types': [{ 'type': 'coin_carousel' }, { 'type': 'coin_square' }] 67 | }, 68 | }; 69 | 70 | $httpClient.post(request, function (error, response, data) { 71 | if (error) { 72 | return reject(['無法取得活動列表 ‼️', '連線錯誤']); 73 | } else { 74 | if (response.status == 200) { 75 | const obj = JSON.parse(data); 76 | const bannerSets = obj.data.banners; 77 | let foundEvent = false; 78 | for (const bannerSet of bannerSets) { 79 | for (const banner of bannerSet.banners) { 80 | const title = banner.navigate_params.navbar.title; 81 | const url = banner.navigate_params.url; 82 | // console.log(`活動名稱: ${title},網址: ${url}`); 83 | if (title.includes('抽免運券') || title.includes('免運寶箱')) { 84 | foundEvent = true; 85 | const re = /activity\/(.*)\??/i; 86 | let found = url.match(re); 87 | if (!found) { 88 | const re = /activity=(.*)&/i; 89 | found = url.match(re); 90 | } 91 | if (!found) { 92 | const re = /activity=(.*)/i; 93 | found = url.match(re); 94 | } 95 | const activityId = found[1]; 96 | console.log(`ℹ️ 在 banner 找到蝦幣寶箱活動,活動名稱: ${title},活動頁面 ID: ${activityId}`); 97 | 98 | // 取得活動代碼 99 | getIdRequest = { 100 | url: `https://games.shopee.tw/gameplatform/api/v1/game/activity/${activityId}/settings?appid=E9VFyxwmtgjnCR8uhL&basic=false`, 101 | headers: config.shopeeHeaders, 102 | }; 103 | 104 | // 真正領取免運寶箱 105 | luckyDrawRequest = { 106 | url: '', 107 | headers: config.shopeeHeaders, 108 | body: { 109 | request_id: (Math.random() * 10 ** 20).toFixed(0).substring(0, 16), 110 | app_id: 'E9VFyxwmtgjnCR8uhL', 111 | activity_code: activityId, 112 | source: 0, 113 | }, 114 | }; 115 | return resolve(); 116 | } 117 | } 118 | } 119 | if (!foundEvent) { 120 | return resolve(); 121 | } 122 | } else { 123 | return reject(['無法取得活動列表 ‼️', response.status]); 124 | } 125 | } 126 | }); 127 | } catch (error) { 128 | return reject(['無法取得活動列表 ‼️', error]); 129 | } 130 | }); 131 | } 132 | 133 | async function iframeListGetActivity() { 134 | return new Promise((resolve, reject) => { 135 | try { 136 | const request = { 137 | url: 'https://mall.shopee.tw/api/v4/market_coin/get_iframe_list?region=TW&offset=0&limit=10', 138 | headers: config.shopeeHeaders, 139 | }; 140 | $httpClient.get(request, function (error, response, data) { 141 | if (error) { 142 | return reject(['無法取得活動列表 ‼️', '連線錯誤']); 143 | } else { 144 | if (response.status === 200) { 145 | const obj = JSON.parse(data); 146 | let foundEvent = false; 147 | const iframeList = obj.data.iframe_list; 148 | for (const iframe of iframeList) { 149 | // console.log(`活動名稱: ${iframe.title},網址: ${iframe.url}`); 150 | if ((iframe.title.includes('免運') || iframe.title.includes('玩遊戲天天抽')) && iframe.url.includes('luckydraw')) { 151 | foundEvent = true; 152 | const re = /activity\/(.*)\??/i; 153 | let found = iframe.url.match(re); 154 | if (!found) { 155 | const re = /activity=(.*)&/i; 156 | found = iframe.url.match(re); 157 | } 158 | const activityId = found[1]; 159 | console.log(`ℹ️ 在 iframe 找到免運寶箱活動,活動名稱: ${iframe.title},活動頁面 ID: ${activityId}`); 160 | 161 | // 取得活動代碼 162 | getIdRequest = { 163 | url: `https://games.shopee.tw/gameplatform/api/v1/game/activity/${activityId}/settings?appid=E9VFyxwmtgjnCR8uhL&basic=false`, 164 | headers: config.shopeeHeaders, 165 | }; 166 | 167 | // 真正領取免運寶箱 168 | luckyDrawRequest = { 169 | url: '', 170 | headers: config.shopeeHeaders, 171 | body: { 172 | request_id: (Math.random() * 10 ** 20).toFixed(0).substring(0, 16), 173 | app_id: 'E9VFyxwmtgjnCR8uhL', 174 | activity_code: activityId, 175 | source: 0, 176 | }, 177 | }; 178 | 179 | return resolve(); 180 | } 181 | } 182 | if (!foundEvent) { 183 | return reject(['無法取得活動列表 ‼️', '找不到免運寶箱活動']); 184 | } 185 | } else { 186 | return reject(['無法取得活動列表 ‼️', response.status]); 187 | } 188 | } 189 | }); 190 | } catch (error) { 191 | return reject(['無法取得活動列表 ‼️', error]); 192 | } 193 | }); 194 | } 195 | 196 | // 獲得免運寶箱 ID 197 | async function shippingLuckyDrawGetId() { 198 | return new Promise((resolve, reject) => { 199 | try { 200 | $httpClient.get(getIdRequest, function (error, response, data) { 201 | if (error) { 202 | return reject(['活動代碼查詢失敗 ‼️', '連線錯誤']); 203 | } else { 204 | if (response.status === 200) { 205 | 206 | const obj = JSON.parse(data); 207 | if (obj.msg === 'success') { 208 | const code = obj.data.basic.event_code; 209 | console.log(`ℹ️ 活動代碼: ${code}`); 210 | luckyDrawRequest.url = `https://games.shopee.tw/luckydraw/api/v1/lucky/event/${code}`; 211 | return resolve(); 212 | } else { 213 | return reject(['活動代碼查詢失敗 ‼️', obj.msg]); 214 | } 215 | } else { 216 | return reject(['活動代碼查詢失敗 ‼️', response.status]); 217 | } 218 | } 219 | }); 220 | } catch (error) { 221 | return reject(['活動代碼查詢失敗 ‼️', error]); 222 | } 223 | }); 224 | } 225 | 226 | async function shippingLuckyDraw() { 227 | return new Promise((resolve, reject) => { 228 | try { 229 | $httpClient.post(luckyDrawRequest, function (error, response, data) { 230 | if (error) { 231 | return reject(['領取失敗 ‼️', '連線錯誤']); 232 | } else { 233 | if (response.status === 200) { 234 | const obj = JSON.parse(data); 235 | if (obj.msg === 'success') { 236 | const packageName = obj.data.package_name; 237 | return resolve(packageName); 238 | } else if (obj.code === 102000) { 239 | showNotification = false; 240 | return reject(['領取失敗 ‼️', '每日只能領一次']); 241 | } else if (obj.msg === 'expired' || obj.msg === 'event already end') { 242 | return reject(['領取失敗 ‼️', '活動已過期。請嘗試更新模組或腳本,或等待作者更新。']); 243 | } else { 244 | return reject(['領取失敗 ‼️', `錯誤代號:${obj.code},訊息:${obj.msg}`]); 245 | } 246 | } else { 247 | return reject(['領取失敗 ‼️', response.status]); 248 | } 249 | } 250 | }); 251 | } catch (error) { 252 | return reject(['領取失敗 ‼️', error]); 253 | } 254 | }); 255 | } 256 | 257 | (async () => { 258 | console.log('ℹ️ 蝦皮免運寶箱 v20230301.2'); 259 | try { 260 | await preCheck(); 261 | console.log('✅ 檢查成功'); 262 | await eventListGetActivity(); 263 | console.log('✅ banner 取得活動列表'); 264 | if (!getIdRequest) { 265 | console.log('⚠️ 在 banner 找不到免運寶箱活動,繼續嘗試搜尋 iframe'); 266 | await iframeListGetActivity(); 267 | console.log('✅ iframe 取得活動列表'); 268 | } 269 | await shippingLuckyDrawGetId(); 270 | console.log('✅ 取得活動代碼'); 271 | const reward = await shippingLuckyDraw(); 272 | console.log('✅ 領取成功'); 273 | console.log(`ℹ️ 獲得 👉 ${reward} 💎`); 274 | surgeNotify( 275 | '領取成功 ✅', 276 | `獲得 👉 ${reward} 💎` 277 | ); 278 | } catch (error) { 279 | handleError(error); 280 | } 281 | $done(); 282 | })(); 283 | -------------------------------------------------------------------------------- /scripts/shopee_token.js: -------------------------------------------------------------------------------- 1 | let showNotification = true; 2 | 3 | function surgeNotify(subtitle = '', message = '') { 4 | $notification.post('🍤 蝦皮 token', subtitle, message, { 'url': 'shopeetw://' }); 5 | }; 6 | 7 | function handleError(error) { 8 | if (Array.isArray(error)) { 9 | console.log(`❌ ${error[0]} ${error[1]}`); 10 | if (showNotification) { 11 | surgeNotify(error[0], error[1]); 12 | } 13 | } else { 14 | console.log(`❌ ${error}`); 15 | if (showNotification) { 16 | surgeNotify(error); 17 | } 18 | } 19 | } 20 | 21 | function getSaveObject(key) { 22 | const string = $persistentStore.read(key); 23 | return !string || string.length === 0 ? {} : JSON.parse(string); 24 | } 25 | 26 | function isEmptyObject(obj) { 27 | return Object.keys(obj).length === 0 && obj.constructor === Object ? true : false; 28 | } 29 | 30 | function parseCookie(cookieString) { 31 | return cookieString 32 | .split(';') 33 | .map(v => v.split('=')) 34 | .filter((v) => v.length > 1) 35 | .reduce((acc, v) => { 36 | let value = decodeURIComponent(v[1].trim()); 37 | for (let index = 2; index < v.length; index++) { 38 | if (v[index] === '') { 39 | value += '='; 40 | } 41 | } 42 | acc[decodeURIComponent(v[0].trim())] = value; 43 | return acc; 44 | }, {}); 45 | } 46 | 47 | function isManualRun(checkRequest = false, checkResponse = false) { 48 | if (checkRequest) { 49 | return typeof $request === 'undefined' || ($request.body && JSON.parse($request.body).foo === 'bar'); 50 | } 51 | if (checkResponse) { 52 | return typeof $response === 'undefined' || ($response.body && JSON.parse($response.body).foo === 'bar'); 53 | } 54 | return false; 55 | } 56 | 57 | async function getToken() { 58 | return new Promise((resolve, reject) => { 59 | try { 60 | const cookie = $request.headers['Cookie'] || $request.headers['cookie']; 61 | if (cookie) { 62 | const cookieObject = parseCookie(cookie); 63 | let shopeeInfo = getSaveObject('ShopeeInfo'); 64 | const tokenInfo = { 65 | SPC_EC: cookieObject.SPC_EC, 66 | SPC_R_T_ID: cookieObject.SPC_R_T_ID, 67 | SPC_R_T_IV: cookieObject.SPC_R_T_IV, 68 | SPC_SI: cookieObject.SPC_SI, 69 | SPC_ST: cookieObject.SPC_ST, 70 | SPC_T_ID: cookieObject.SPC_T_ID, 71 | SPC_T_IV: cookieObject.SPC_T_IV, 72 | SPC_F: cookieObject.SPC_F, 73 | SPC_U: cookieObject.SPC_U, 74 | } 75 | shopeeInfo.token = tokenInfo; 76 | shopeeInfo.userName = cookieObject.username; 77 | shopeeInfo.shopeeToken = cookieObject.shopee_token; 78 | 79 | const save = $persistentStore.write(JSON.stringify(shopeeInfo, null, 4), 'ShopeeInfo'); 80 | if (!save) { 81 | return reject(['保存失敗 ‼️', '無法儲存 token']); 82 | } else { 83 | return resolve(); 84 | } 85 | } else { 86 | return reject(['保存失敗 ‼️', '請重新登入']); 87 | } 88 | } catch (error) { 89 | return reject(['保存失敗 ‼️', error]); 90 | } 91 | }); 92 | } 93 | 94 | (async () => { 95 | console.log('ℹ️ 蝦皮取得 token v20230213.1'); 96 | try { 97 | if (isManualRun(true, false)) { 98 | throw '請勿手動執行此腳本'; 99 | } 100 | 101 | await getToken(); 102 | console.log('✅ token 保存成功'); 103 | surgeNotify('保存成功 🍪', ''); 104 | } catch (error) { 105 | handleError(error); 106 | } 107 | $done({}); 108 | })(); 109 | -------------------------------------------------------------------------------- /scripts/shopee_update_token.js: -------------------------------------------------------------------------------- 1 | let showNotification = true; 2 | let config = null; 3 | 4 | function surgeNotify(subtitle = '', message = '') { 5 | $notification.post('🍤 蝦皮更新 token', subtitle, message, { 'url': 'shopeetw://' }); 6 | }; 7 | 8 | function handleError(error) { 9 | if (Array.isArray(error)) { 10 | console.log(`❌ ${error[0]} ${error[1]}`); 11 | if (showNotification) { 12 | surgeNotify(error[0], error[1]); 13 | } 14 | } else { 15 | console.log(`❌ ${error}`); 16 | if (showNotification) { 17 | surgeNotify(error); 18 | } 19 | } 20 | } 21 | 22 | function getSaveObject(key) { 23 | const string = $persistentStore.read(key); 24 | return !string || string.length === 0 ? {} : JSON.parse(string); 25 | } 26 | 27 | function isEmptyObject(obj) { 28 | return Object.keys(obj).length === 0 && obj.constructor === Object ? true : false; 29 | } 30 | 31 | function parseCookie(cookieString) { 32 | return cookieString 33 | .split(';') 34 | .map(v => v.split('=')) 35 | .filter((v) => v.length > 1) 36 | .reduce((acc, v) => { 37 | let value = decodeURIComponent(v[1].trim()); 38 | for (let index = 2; index < v.length; index++) { 39 | if (v[index] === '') { 40 | value += '='; 41 | } 42 | } 43 | acc[decodeURIComponent(v[0].trim())] = value; 44 | return acc; 45 | }, {}); 46 | } 47 | 48 | function cookieToString(cookieObject) { 49 | let string = ''; 50 | for (const [key, value] of Object.entries(cookieObject)) { 51 | // SPC_EC 另外加入 52 | if (key !== 'SPC_EC') { 53 | string += `${key}=${value};` 54 | } 55 | } 56 | return string; 57 | } 58 | 59 | async function updateSpcEc() { 60 | return new Promise((resolve, reject) => { 61 | let shopeeInfo = getSaveObject('ShopeeInfo'); 62 | if (isEmptyObject(shopeeInfo)) { 63 | return reject(['取得 token 失敗 ‼️', '找不到儲存的 token']); 64 | } 65 | 66 | const request = { 67 | url: 'https://mall.shopee.tw/api/v4/client/refresh', 68 | headers: { 69 | 'Cookie': `shopee_token=${shopeeInfo.shopeeToken};`, 70 | 'Content-Type': 'application/json', 71 | }, 72 | }; 73 | 74 | try { 75 | $httpClient.get(request, function (error, response, data) { 76 | if (error) { 77 | return reject(['更新 SPC_EC 失敗 ‼️', '連線錯誤']); 78 | } else { 79 | if (response.status == 200) { 80 | const obj = JSON.parse(data); 81 | if (obj.error) { 82 | return reject(['更新 SPC_EC 失敗 ‼️', '請重新取得 token']); 83 | } 84 | const cookie = response.headers['Set-Cookie'] || response.headers['set-cookie']; 85 | if (cookie) { 86 | const filteredCookie = cookie.replaceAll('HttpOnly;', '').replaceAll('Secure,', ''); 87 | const cookieObject = parseCookie(filteredCookie); 88 | return resolve(cookieObject.SPC_EC); 89 | } else { 90 | return reject(['更新 SPC_EC 失敗 ‼️', '找不到回應的 token']); 91 | } 92 | } else { 93 | return reject(['更新 SPC_EC 失敗 ‼️', response.status]); 94 | } 95 | } 96 | }); 97 | } catch (error) { 98 | return reject(['更新 SPC_EC 失敗 ‼️', error]); 99 | } 100 | }); 101 | } 102 | 103 | async function updateCookie(spcEc) { 104 | return new Promise((resolve, reject) => { 105 | try { 106 | let shopeeInfo = getSaveObject('ShopeeInfo'); 107 | if (isEmptyObject(shopeeInfo)) { 108 | return reject(['取得 token 失敗 ‼️', '找不到儲存的 token']); 109 | } 110 | 111 | const request = { 112 | url: 'https://shopee.tw/api/v2/user/account_info?from_wallet=false&skip_address=1&need_cart=1', 113 | headers: { 114 | 'Cookie': `${cookieToString(shopeeInfo.token)}SPC_EC=${spcEc};shopee_token=${shopeeInfo.shopeeToken};`, 115 | }, 116 | }; 117 | 118 | $httpClient.get(request, function (error, response, data) { 119 | if (error) { 120 | return reject(['更新 token 失敗 ‼️', '連線錯誤']); 121 | } else { 122 | if (response.status == 200) { 123 | const obj = JSON.parse(data); 124 | if (obj.error) { 125 | return reject(['更新 token 失敗 ‼️', '請重新取得 token']); 126 | } 127 | const cookie = response.headers['Set-Cookie'] || response.headers['set-cookie']; 128 | if (cookie) { 129 | const filteredCookie = cookie.replaceAll('HttpOnly;', '').replaceAll('Secure,', ''); 130 | const cookieObject = parseCookie(filteredCookie); 131 | const tokenInfo = { 132 | SPC_EC: spcEc, 133 | SPC_R_T_ID: cookieObject.SPC_R_T_ID, 134 | SPC_R_T_IV: cookieObject.SPC_R_T_IV, 135 | SPC_SI: cookieObject.SPC_SI, 136 | SPC_ST: cookieObject.SPC_ST, 137 | SPC_T_ID: cookieObject.SPC_T_ID, 138 | SPC_T_IV: cookieObject.SPC_T_IV, 139 | SPC_F: cookieObject.SPC_F, 140 | SPC_U: cookieObject.SPC_U, 141 | }; 142 | if (shopeeInfo.token.SPC_EC === tokenInfo.SPC_EC) { 143 | console.log('⚠️ SPC_EC 新舊內容一致,並未更新'); 144 | } 145 | if (shopeeInfo.token.SPC_R_T_ID === tokenInfo.SPC_R_T_ID) { 146 | console.log('⚠️ SPC_R_T_ID 新舊內容一致,並未更新'); 147 | } 148 | if (shopeeInfo.token.SPC_R_T_IV === tokenInfo.SPC_R_T_IV) { 149 | console.log('⚠️ SPC_R_T_IV 新舊內容一致,並未更新'); 150 | } 151 | if (shopeeInfo.token.SPC_SI === tokenInfo.SPC_SI) { 152 | console.log('⚠️ SPC_SI 新舊內容一致,並未更新'); 153 | } 154 | if (shopeeInfo.token.SPC_ST === tokenInfo.SPC_ST) { 155 | console.log('⚠️ SPC_ST 新舊內容一致,並未更新'); 156 | } 157 | if (shopeeInfo.token.SPC_T_ID === tokenInfo.SPC_T_ID) { 158 | console.log('⚠️ SPC_T_ID 新舊內容一致,並未更新'); 159 | } 160 | if (shopeeInfo.token.SPC_T_IV === tokenInfo.SPC_T_IV) { 161 | console.log('⚠️ SPC_T_IV 新舊內容一致,並未更新'); 162 | } 163 | 164 | shopeeInfo.token = tokenInfo; 165 | const save = $persistentStore.write(JSON.stringify(shopeeInfo, null, 4), 'ShopeeInfo'); 166 | if (!save) { 167 | return reject(['保存失敗 ‼️', '無法儲存 token']); 168 | } else { 169 | return resolve(); 170 | } 171 | } else { 172 | return reject(['更新 token 失敗 ‼️', '找不到回傳的 token']); 173 | } 174 | } else { 175 | return reject(['更新 token 失敗 ‼️', response.status]) 176 | } 177 | } 178 | }); 179 | } catch (error) { 180 | return reject(['更新 token 失敗 ‼️', error]); 181 | } 182 | }); 183 | } 184 | 185 | async function deleteOldKeys() { 186 | return new Promise((resolve, reject) => { 187 | try { 188 | $persistentStore.write(null, 'CSRFTokenSP'); 189 | $persistentStore.write(null, 'CookieSP'); 190 | $persistentStore.write(null, 'SPC_EC'); 191 | $persistentStore.write(null, 'ShopeeToken'); 192 | $persistentStore.write(null, 'Shopee_SPC_U'); 193 | return resolve(); 194 | } catch (error) { 195 | return reject(['刪除舊的 key 失敗 ‼️', error]); 196 | } 197 | }); 198 | } 199 | 200 | (async () => { 201 | console.log('ℹ️ 蝦皮更新 token v20230131.1'); 202 | try { 203 | await deleteOldKeys(); 204 | console.log('✅ 刪除舊的 key 成功'); 205 | const spcEc = await updateSpcEc(); 206 | console.log('✅ SPC_EC 更新成功'); 207 | await updateCookie(spcEc); 208 | console.log('✅ token 更新成功'); 209 | $done(); 210 | } catch (error) { 211 | handleError(error); 212 | } 213 | $done(); 214 | })(); 215 | -------------------------------------------------------------------------------- /scripts/shopee_water_buy_free_item.js: -------------------------------------------------------------------------------- 1 | let showNotification = true; 2 | let config = null; 3 | let buyFreeItemRequest = null; 4 | 5 | function surgeNotify(subtitle = '', message = '') { 6 | $notification.post('🍤 蝦蝦果園免費道具', subtitle, message, { 'url': 'shopeetw://' }); 7 | }; 8 | 9 | function handleError(error) { 10 | if (Array.isArray(error)) { 11 | console.log(`❌ ${error[0]} ${error[1]}`); 12 | if (showNotification) { 13 | surgeNotify(error[0], error[1]); 14 | } 15 | } else { 16 | console.log(`❌ ${error}`); 17 | if (showNotification) { 18 | surgeNotify(error); 19 | } 20 | } 21 | } 22 | 23 | function getSaveObject(key) { 24 | const string = $persistentStore.read(key); 25 | return !string || string.length === 0 ? {} : JSON.parse(string); 26 | } 27 | 28 | function isEmptyObject(obj) { 29 | return Object.keys(obj).length === 0 && obj.constructor === Object ? true : false; 30 | } 31 | 32 | function cookieToString(cookieObject) { 33 | let string = ''; 34 | for (const [key, value] of Object.entries(cookieObject)) { 35 | string += `${key}=${value};` 36 | } 37 | return string; 38 | } 39 | 40 | async function preCheck() { 41 | return new Promise((resolve, reject) => { 42 | const shopeeInfo = getSaveObject('ShopeeInfo'); 43 | if (isEmptyObject(shopeeInfo)) { 44 | return reject(['檢查失敗 ‼️', '找不到 token']); 45 | } 46 | const shopeeHeaders = { 47 | 'Cookie': cookieToString(shopeeInfo.token), 48 | 'Content-Type': 'application/json', 49 | } 50 | config = { 51 | shopeeInfo: shopeeInfo, 52 | shopeeHeaders: shopeeHeaders, 53 | } 54 | return resolve(); 55 | }); 56 | } 57 | 58 | async function getWaterStoreItem() { 59 | return new Promise((resolve, reject) => { 60 | try { 61 | const waterStoreItemListRequest = { 62 | // Type=1 道具商店 63 | // Type=2 免費水滴商店 64 | url: `https://games.shopee.tw/farm/api/prop/list?storeType=1&typeId=&isShowRevivalPotion=true&t=${new Date().getTime()}`, 65 | headers: config.shopeeHeaders, 66 | }; 67 | $httpClient.get(waterStoreItemListRequest, function (error, response, data) { 68 | if (error) { 69 | return reject(['取得道具列表失敗 ‼️', '連線錯誤']); 70 | } else { 71 | if (response.status === 200) { 72 | const obj = JSON.parse(data); 73 | if (obj.msg === 'success') { 74 | const props = obj.data.props; 75 | let found = false; 76 | for (const prop of props) { 77 | if (prop.price === 0) { 78 | found = true; 79 | if (prop.buyNum < prop.buyLimit) { 80 | buyFreeItemRequest = { 81 | url: `https://games.shopee.tw/farm/api/prop/buy/v2?t=${new Date().getTime()}`, 82 | headers: config.shopeeHeaders, 83 | body: { 84 | propMetaId: prop.propMetaId, 85 | } 86 | }; 87 | return resolve(prop.name); 88 | } 89 | else { 90 | showNotification = false; 91 | return reject(['沒有可購買的免費道具 ‼️', `本日已購買免費${prop.name}`]); 92 | } 93 | } 94 | } 95 | if (!found) { 96 | showNotification = false; 97 | return reject(['取得道具列表失敗 ‼️', '本日無免費道具']); 98 | } 99 | } else { 100 | return reject(['取得道具列表失敗 ‼️', `錯誤代號:${obj.code},訊息:${obj.msg}`]); 101 | } 102 | } else { 103 | return reject(['取得道具列表失敗 ‼️', response.status]); 104 | } 105 | } 106 | }); 107 | } catch (error) { 108 | return reject(['取得道具列表失敗 ‼️', error]); 109 | } 110 | }); 111 | } 112 | 113 | async function buyFreeItem() { 114 | return new Promise((resolve, reject) => { 115 | try { 116 | $httpClient.post(buyFreeItemRequest, function (error, response, data) { 117 | if (error) { 118 | return reject(['購買道具失敗 ‼️', '連線錯誤']); 119 | } else { 120 | if (response.status === 200) { 121 | const obj = JSON.parse(data); 122 | if (obj.msg === 'success') { 123 | return resolve(); 124 | } 125 | else if (obj.code === 409049) { 126 | return reject(['購買道具失敗 ‼️', `此道具持有數量已滿,請先使用道具再領取。`]); 127 | } 128 | else { 129 | return reject(['購買道具失敗 ‼️', `錯誤代號:${obj.code},訊息:${obj.msg}`]); 130 | } 131 | } else { 132 | return reject(['購買道具失敗 ‼️', response.status]); 133 | } 134 | } 135 | }); 136 | } catch (error) { 137 | return reject(['購買道具失敗 ‼️', error]); 138 | } 139 | }); 140 | } 141 | 142 | (async () => { 143 | console.log('ℹ️ 蝦蝦果園免費道具 v20230128.1'); 144 | try { 145 | await preCheck(); 146 | console.log('✅ 檢查成功'); 147 | const itemName = await getWaterStoreItem(); 148 | console.log('✅ 取得特價商店道具列表成功'); 149 | await buyFreeItem(); 150 | console.log('✅ 購買免費道具成功'); 151 | console.log(`ℹ️ 獲得 ${itemName}`); 152 | surgeNotify( 153 | '購買免費道具成功 ✅', 154 | `獲得 👉 ${itemName} 💎` 155 | ); 156 | } catch (error) { 157 | handleError(error); 158 | } 159 | $done(); 160 | })(); 161 | -------------------------------------------------------------------------------- /scripts/shopee_water_checkin.js: -------------------------------------------------------------------------------- 1 | let showNotification = true; 2 | let config = null; 3 | 4 | function surgeNotify(subtitle = '', message = '') { 5 | $notification.post('🍤 蝦蝦果園水滴任務', subtitle, message, { 'url': 'shopeetw://' }); 6 | }; 7 | 8 | function handleError(error) { 9 | if (Array.isArray(error)) { 10 | console.log(`❌ ${error[0]} ${error[1]}`); 11 | if (showNotification) { 12 | surgeNotify(error[0], error[1]); 13 | } 14 | } else { 15 | console.log(`❌ ${error}`); 16 | if (showNotification) { 17 | surgeNotify(error); 18 | } 19 | } 20 | } 21 | 22 | function getSaveObject(key) { 23 | const string = $persistentStore.read(key); 24 | return !string || string.length === 0 ? {} : JSON.parse(string); 25 | } 26 | 27 | function isEmptyObject(obj) { 28 | return Object.keys(obj).length === 0 && obj.constructor === Object ? true : false; 29 | } 30 | 31 | function cookieToString(cookieObject) { 32 | let string = ''; 33 | for (const [key, value] of Object.entries(cookieObject)) { 34 | string += `${key}=${value};` 35 | } 36 | return string; 37 | } 38 | 39 | async function preCheck() { 40 | return new Promise((resolve, reject) => { 41 | const shopeeInfo = getSaveObject('ShopeeInfo'); 42 | if (isEmptyObject(shopeeInfo)) { 43 | return reject(['檢查失敗 ‼️', '沒有新版 token']); 44 | } 45 | const shopeeHeaders = { 46 | 'Cookie': cookieToString(shopeeInfo.token), 47 | 'Content-Type': 'application/json', 48 | } 49 | config = { 50 | shopeeInfo: shopeeInfo, 51 | shopeeHeaders: shopeeHeaders, 52 | } 53 | return resolve(); 54 | }); 55 | } 56 | 57 | async function checkIn() { 58 | return new Promise((resolve, reject) => { 59 | try { 60 | const request = { 61 | url: 'https://games.shopee.tw/farm/api/task/action?t=' + new Date().getTime(), 62 | headers: config.shopeeHeaders, 63 | body: { actionKey: 'act_Check_In' }, 64 | }; 65 | $httpClient.post(request, function (error, response, data) { 66 | if (error) { 67 | return reject(['打卡失敗 ‼️', '連線錯誤']); 68 | } else { 69 | if (response.status === 200) { 70 | const obj = JSON.parse(data); 71 | if (obj.msg === 'success') { 72 | return resolve(); 73 | } else if (obj.msg === 'false') { 74 | return reject(['打卡失敗 ‼️', '每日只能打卡三次,今日已完成打卡任務']); 75 | } else if (obj.msg === 'task check in invalid time') { 76 | return reject(['打卡失敗 ‼️', '打卡間隔少於三小時']); 77 | } else { 78 | return reject(['打卡失敗 ‼️', `錯誤代號:${obj.code},訊息:${obj.msg}`]); 79 | } 80 | } else { 81 | return reject(['打卡失敗 ‼️', response.status]); 82 | } 83 | } 84 | }); 85 | } catch (error) { 86 | return reject(['打卡失敗 ‼️', error]); 87 | } 88 | }); 89 | } 90 | 91 | (async () => { 92 | console.log('ℹ️ 蝦蝦果園自動打卡 v20230115.1'); 93 | try { 94 | await preCheck(); 95 | console.log('✅ 檢查成功'); 96 | await checkIn(); 97 | console.log('✅ 打卡成功'); 98 | 99 | surgeNotify('打卡成功 ✅', ''); 100 | } catch (error) { 101 | handleError(error); 102 | } 103 | $done(); 104 | })(); 105 | -------------------------------------------------------------------------------- /scripts/shopee_water_mission.js: -------------------------------------------------------------------------------- 1 | const shopeeCookie = $persistentStore.read('CookieSP') + ';SPC_EC=' + $persistentStore.read('SPC_EC') + ';'; 2 | const shopeeCSRFToken = $persistentStore.read('CSRFTokenSP'); 3 | const shopeeHeaders = { 4 | 'Cookie': shopeeCookie, 5 | 'X-CSRFToken': shopeeCSRFToken, 6 | }; 7 | function shopeeNotify(subtitle = '', message = '') { 8 | $notification.post('🍤 蝦蝦果園執行任務', subtitle, message, { 'url': 'shopeetw://' }); 9 | }; 10 | 11 | let request = { 12 | url: '', 13 | headers: shopeeHeaders, 14 | body: { actionKey: '' }, 15 | }; 16 | 17 | let missions = []; 18 | 19 | // if (new Date().getHours() < 12) { 20 | 21 | // } 22 | // else { 23 | // missions.push({ 24 | // actionKey: 'act_claim_water_in_shop', 25 | // missionName: '前往賣場領取水滴' 26 | // }); 27 | // } 28 | 29 | missions.push({ 30 | actionKey: 'act_playrcmdgame', 31 | missionName: '玩商城遊戲' 32 | }); 33 | missions.push({ 34 | actionKey: 'act_play_candy_game', 35 | missionName: '玩蝦皮消消樂' 36 | }); 37 | missions.push({ 38 | actionKey: 'act_play_claw_game', 39 | missionName: '玩蝦皮夾夾樂' 40 | }); 41 | missions.push({ 42 | actionKey: 'act_play_knife_throw_game', 43 | missionName: '玩蝦蝦飛刀' 44 | }); 45 | missions.push({ 46 | actionKey: 'act_play_pet_game', 47 | missionName: '玩蝦蝦寵物村' 48 | }); 49 | missions.push({ 50 | actionKey: 'act_play_bubble_game', 51 | missionName: '玩蝦皮泡泡王' 52 | }); 53 | 54 | for (let i = 0; i < 10; i++) { 55 | missions.push({ 56 | actionKey: 'act_Receive_Water', 57 | missionName: '收到站內朋友助水' 58 | }); 59 | } 60 | 61 | for (let i = 0; i < 10; i++) { 62 | missions.push({ 63 | actionKey: 'act_Help_Watering', 64 | missionName: '幫站內朋友澆水' 65 | }); 66 | } 67 | 68 | function waterMission(index) { 69 | sleep(0.5); 70 | const now = new Date().getTime(); 71 | const missionName = missions[index].missionName; 72 | const actionKey = missions[index].actionKey; 73 | request.url = url = 'https://games.shopee.tw/farm/api/task/action?t=' + now; 74 | request.body.actionKey = actionKey; 75 | 76 | $httpClient.post(request, function (error, response, data) { 77 | if (error) { 78 | shopeeNotify( 79 | '執行 ' + missionName + ' 失敗 ‼️', 80 | '連線錯誤' 81 | ); 82 | } else { 83 | if (response.status === 200) { 84 | try { 85 | const obj = JSON.parse(data); 86 | if (obj.msg === 'success') { 87 | console.log(missions[index].missionName + '成功 ✅'); 88 | // shopeeNotify( 89 | // '執行成功 ✅', 90 | // '已完成 ' + missions[index].missionName 91 | // ); 92 | } else if (obj.msg === 'lock failed.') { 93 | shopeeNotify( 94 | '執行 ' + missionName + ' 失敗 ‼️', 95 | '連線請求過於頻繁' 96 | ); 97 | } else { 98 | shopeeNotify( 99 | '執行 ' + missionName + ' 失敗 ‼️', 100 | obj.msg 101 | ); 102 | } 103 | } catch (error) { 104 | shopeeNotify( 105 | '執行 ' + missionName + ' 失敗 ‼️', 106 | error 107 | ); 108 | } 109 | } else { 110 | shopeeNotify( 111 | 'Cookie 已過期 ‼️', 112 | '請重新登入' 113 | ); 114 | } 115 | } 116 | if (index < missions.length - 1) { 117 | waterMission(index + 1); 118 | } 119 | else { 120 | shopeeNotify( 121 | '已完成所有任務 ✅', 122 | '' 123 | ); 124 | $done(); 125 | } 126 | }); 127 | } 128 | 129 | function sleep(seconds) { 130 | const waitUntil = new Date().getTime() + seconds * 1000; 131 | while (new Date().getTime() < waitUntil) true; 132 | } 133 | 134 | waterMission(0); 135 | -------------------------------------------------------------------------------- /scripts/shopee_water_mission_claim.js: -------------------------------------------------------------------------------- 1 | let showNotification = true; 2 | let config = null; 3 | let rewards = []; 4 | 5 | function surgeNotify(subtitle = '', message = '') { 6 | $notification.post('🍤 蝦蝦果園領取任務獎勵', subtitle, message, { 'url': 'shopeetw://' }); 7 | }; 8 | 9 | function handleError(error) { 10 | if (Array.isArray(error)) { 11 | console.log(`❌ ${error[0]} ${error[1]}`); 12 | if (showNotification) { 13 | surgeNotify(error[0], error[1]); 14 | } 15 | } else { 16 | console.log(`❌ ${error}`); 17 | if (showNotification) { 18 | surgeNotify(error); 19 | } 20 | } 21 | } 22 | 23 | function getSaveObject(key) { 24 | const string = $persistentStore.read(key); 25 | return !string || string.length === 0 ? {} : JSON.parse(string); 26 | } 27 | 28 | function isEmptyObject(obj) { 29 | return Object.keys(obj).length === 0 && obj.constructor === Object ? true : false; 30 | } 31 | 32 | function cookieToString(cookieObject) { 33 | let string = ''; 34 | for (const [key, value] of Object.entries(cookieObject)) { 35 | string += `${key}=${value};` 36 | } 37 | return string; 38 | } 39 | 40 | async function delay(seconds) { 41 | console.log(`⏰ 等待 ${seconds} 秒`); 42 | return new Promise((resolve) => { 43 | setTimeout(() => { 44 | resolve(); 45 | }, seconds * 1000); 46 | }); 47 | } 48 | 49 | async function preCheck() { 50 | return new Promise((resolve, reject) => { 51 | const shopeeInfo = getSaveObject('ShopeeInfo'); 52 | if (isEmptyObject(shopeeInfo)) { 53 | return reject(['檢查失敗 ‼️', '沒有新版 token']); 54 | } 55 | const shopeeHeaders = { 56 | 'Cookie': cookieToString(shopeeInfo.token), 57 | 'Content-Type': 'application/json', 58 | } 59 | config = { 60 | shopeeInfo: shopeeInfo, 61 | shopeeHeaders: shopeeHeaders, 62 | } 63 | return resolve(); 64 | }); 65 | } 66 | 67 | async function getRewardList() { 68 | return new Promise((resolve, reject) => { 69 | try { 70 | const getListRequest = { 71 | url: `https://games.shopee.tw/farm/api/task/listV2?t=${new Date().getTime()}`, 72 | headers: config.shopeeHeaders, 73 | }; 74 | $httpClient.get(getListRequest, function (error, response, data) { 75 | if (error) { 76 | return reject(['取得列表失敗 ‼️', '連線錯誤']); 77 | } else { 78 | if (response.status === 200) { 79 | const obj = JSON.parse(data); 80 | const taskGroups = obj.data.userTasks; 81 | for (let i = 0; i < taskGroups.length; i++) { 82 | const taskList = taskGroups[i]; 83 | for (let j = 0; j < taskList.length; j++) { 84 | const task = taskList[j]; 85 | const taskId = task.taskInfo.Id; 86 | const taskName = task.taskInfo.taskName; 87 | if (task.canReward === true) { 88 | rewards.push({ 89 | taskId: taskId, 90 | taskName: taskName 91 | }); 92 | } 93 | } 94 | } 95 | if (rewards.length) { 96 | console.log(`✅ 取得列表成功,總共有 ${rewards.length} 個任務可領取獎勵`); 97 | return resolve(); 98 | } 99 | else { 100 | return reject(['取得列表失敗 ‼️', '沒有可領取的獎勵']); 101 | } 102 | } else { 103 | return reject(['取得列表失敗 ‼️', response.status]); 104 | } 105 | } 106 | }); 107 | } catch (error) { 108 | return reject(['取得列表失敗 ‼️', error]); 109 | } 110 | }); 111 | } 112 | 113 | async function claimReward(reward) { 114 | return new Promise((resolve, reject) => { 115 | try { 116 | const taskId = reward.taskId; 117 | const taskName = reward.taskName; 118 | 119 | const claimRewardRequest = { 120 | url: `https://games.shopee.tw/farm/api/task/reward/claim?t=${new Date().getTime()}`, 121 | headers: config.shopeeHeaders, 122 | body: { 123 | taskId: taskId, 124 | taskFinishNum: 1, 125 | isNewUserTask: false, 126 | forceClaim: false, 127 | }, 128 | }; 129 | 130 | $httpClient.post(claimRewardRequest, function (error, response, data) { 131 | if (error) { 132 | return reject(['領取失敗 ‼️', '連線錯誤']); 133 | } else { 134 | if (response.status === 200) { 135 | const obj = JSON.parse(data); 136 | if (obj.code === 0) { 137 | console.log(`✅ 領取「${taskName}」成功`); 138 | return resolve(); 139 | } else if (obj.code === 409004) { 140 | return reject(['領取失敗 ‼️', `無法領取「${taskName}」。作物狀態錯誤,請檢查是否已收成`]); 141 | } else { 142 | return reject(['領取失敗 ‼️', `無法領取「${taskName}」,錯誤代號:${obj.code},訊息:${obj.msg}`]); 143 | } 144 | } else { 145 | return reject(['領取失敗 ‼️', response.status]); 146 | } 147 | } 148 | }); 149 | } catch (error) { 150 | return reject(['領取失敗 ‼️', error]); 151 | } 152 | }); 153 | } 154 | 155 | (async () => { 156 | console.log('ℹ️ 蝦蝦果園領取任務獎勵 v20230119.1'); 157 | try { 158 | await preCheck(); 159 | await getRewardList(); 160 | 161 | for (let i = 0; i < rewards.length; i++) { 162 | await delay(0.5); 163 | await claimReward(rewards[i]); 164 | } 165 | console.log('✅ 領取所有獎勵') 166 | surgeNotify('已領取所有獎勵 ✅', ''); 167 | } catch (error) { 168 | handleError(error); 169 | } 170 | $done(); 171 | })(); 172 | -------------------------------------------------------------------------------- /scripts/shopee_water_signin_bundle.js: -------------------------------------------------------------------------------- 1 | let showNotification = true; 2 | let config = null; 3 | let claimSignInBundleRequest = null; 4 | 5 | function surgeNotify(subtitle = '', message = '') { 6 | $notification.post('🍤 蝦蝦果園每日簽到獎勵', subtitle, message, { 'url': 'shopeetw://' }); 7 | }; 8 | 9 | function handleError(error) { 10 | if (Array.isArray(error)) { 11 | console.log(`❌ ${error[0]} ${error[1]}`); 12 | if (showNotification) { 13 | surgeNotify(error[0], error[1]); 14 | } 15 | } else { 16 | console.log(`❌ ${error}`); 17 | if (showNotification) { 18 | surgeNotify(error); 19 | } 20 | } 21 | } 22 | 23 | function getSaveObject(key) { 24 | const string = $persistentStore.read(key); 25 | return !string || string.length === 0 ? {} : JSON.parse(string); 26 | } 27 | 28 | function isEmptyObject(obj) { 29 | return Object.keys(obj).length === 0 && obj.constructor === Object ? true : false; 30 | } 31 | 32 | function cookieToString(cookieObject) { 33 | let string = ''; 34 | for (const [key, value] of Object.entries(cookieObject)) { 35 | string += `${key}=${value};` 36 | } 37 | return string; 38 | } 39 | 40 | async function preCheck() { 41 | return new Promise((resolve, reject) => { 42 | const shopeeInfo = getSaveObject('ShopeeInfo'); 43 | if (isEmptyObject(shopeeInfo)) { 44 | return reject(['檢查失敗 ‼️', '沒有新版 token']); 45 | } 46 | const shopeeHeaders = { 47 | 'Cookie': cookieToString(shopeeInfo.token), 48 | } 49 | config = { 50 | shopeeInfo: shopeeInfo, 51 | shopeeHeaders: shopeeHeaders, 52 | } 53 | return resolve(); 54 | }); 55 | } 56 | 57 | async function getSignInBundleList() { 58 | return new Promise((resolve, reject) => { 59 | try { 60 | const request = { 61 | url: `https://games.shopee.tw/farm/api/sign_in_bundle/list?t=${new Date().getTime()}`, 62 | headers: config.shopeeHeaders, 63 | }; 64 | $httpClient.get(request, function (error, response, data) { 65 | if (error) { 66 | return reject(['取得列表失敗 ‼️', '連線錯誤']); 67 | } else { 68 | if (response.status === 200) { 69 | const obj = JSON.parse(data); 70 | const day = obj.data.day; 71 | const claimed = obj.data.signInBundlePrizes[day - 1].claimed; 72 | if (claimed) { 73 | showNotification = false; 74 | return reject(['取得列表失敗 ‼️', '今日已簽到']); 75 | } 76 | 77 | claimSignInBundleRequest = { 78 | url: `https://games.shopee.tw/farm/api/sign_in_bundle/claim?t=${new Date().getTime()}`, 79 | headers: config.shopeeHeaders, 80 | body: { 81 | 'day': day, 82 | 'forceClaim': true 83 | } 84 | }; 85 | 86 | return resolve(); 87 | } else { 88 | return reject(['取得列表失敗 ‼️', response.status]); 89 | } 90 | } 91 | }); 92 | } catch (error) { 93 | return reject(['取得列表失敗 ‼️', error]); 94 | } 95 | }) 96 | } 97 | 98 | function claimSignInBundle() { 99 | return new Promise((resolve, reject) => { 100 | try { 101 | $httpClient.post(claimSignInBundleRequest, function (error, response, data) { 102 | if (error) { 103 | return reject(['簽到失敗 ‼️', '請重新登入']); 104 | } else { 105 | if (response.status === 200) { 106 | const obj = JSON.parse(data); 107 | if (obj.msg === 'success') { 108 | const day = obj.data.day; 109 | const prize = obj.data.signInBundlePrizes[day - 1]; 110 | let prizeName = ''; 111 | if (prize.prizeDetail) { 112 | prizeName = prize.prizeDetail.prizeName; 113 | } 114 | else { 115 | prizeName = prize.prizeNum + ' 滴水 💧'; 116 | } 117 | return resolve(prizeName); 118 | } 119 | else { 120 | return reject(['簽到失敗 ‼️', `錯誤代號:${obj.code},訊息:${obj.msg}`]); 121 | } 122 | } else { 123 | return reject(['簽到失敗 ‼️', response.status]); 124 | } 125 | } 126 | }); 127 | } catch (error) { 128 | return reject(['簽到失敗 ‼️', error]); 129 | } 130 | }); 131 | } 132 | 133 | (async () => { 134 | console.log('ℹ️ 蝦蝦果園每日簽到獎勵 v20230115.1'); 135 | try { 136 | await preCheck(); 137 | console.log('✅ 檢查成功'); 138 | await getSignInBundleList(); 139 | console.log('✅ 取得列表成功'); 140 | const reward = await claimSignInBundle(); 141 | console.log('✅ 領取簽到獎勵成功'); 142 | console.log(`ℹ️ 獲得 ${reward}`); 143 | surgeNotify( 144 | '簽到成功 ✅', 145 | `獲得 ${reward}` 146 | ); 147 | } catch (error) { 148 | handleError(error); 149 | } 150 | $done(); 151 | })(); 152 | -------------------------------------------------------------------------------- /scripts/surge_check.js: -------------------------------------------------------------------------------- 1 | const foo = 'bar'; //白痴 loon 設計,少任意一行垃圾 code 就不會執行下面的腳本 2 | 3 | (() => { 4 | console.log('ℹ️ Surge 功能檢查 v20230115.1'); 5 | console.log('⚠️ 請勿手動執行此腳本'); 6 | if ('undefined' !== typeof $response) { 7 | let body = $response.body; 8 | $response.body = body.replace('id="mitm-resule">Failed ❌', 'id="mitm-resule">Success ✅'); 9 | if ('undefined' !== typeof $loon) { 10 | body = $response.body; 11 | $response.body = body.replace('

Surge Check

', '

Loon Check

'); 12 | } 13 | $done($response); 14 | 15 | } else if ('undefined' !== typeof $request) { 16 | $request.headers['X-Hiraku-Script'] = '1'; 17 | $done($request); 18 | } else { 19 | $done({}); 20 | } 21 | })(); 22 | -------------------------------------------------------------------------------- /scripts/whosis_sayings_panel.js: -------------------------------------------------------------------------------- 1 | const { v4, v6 } = $network; 2 | 3 | const displayName = { 4 | 'Shawn_N': '$踢低吸 八嘎 NONO 麻ㄙㄟ麻ㄙㄟ', 5 | 'hirakujira': 'Hiraku', 6 | 'bill85101': '森喵', 7 | }; 8 | 9 | if (!v4.primaryAddress && !v6.primaryAddress) { 10 | $done({ 11 | title: '沒有網路', 12 | content: '尚未連接網際網路\n請檢查網際網路狀態後重試', 13 | icon: 'wifi.exclamationmark', 14 | 'icon-color': '#CB1B45', 15 | }); 16 | } else { 17 | $httpClient.get('https://raw.githubusercontent.com/tasi788/Whosis-Sayings/master/public/saying.txt', function (error, response, data) { 18 | if (error) { 19 | $done({ 20 | title: '發生錯誤', 21 | content: '無法成功獲得資料\n請檢查網際網路狀態後重試', 22 | icon: 'wifi.exclamationmark', 23 | 'icon-color': '#CB1B45', 24 | }); 25 | } 26 | 27 | const saying = data.split(';').slice(0, -1).map(x => x.trim()); 28 | const idx = Math.floor(Math.random() * saying.length); 29 | const selectedSaying = saying[idx].split(','); 30 | const title = displayName[selectedSaying[0]] ? displayName[selectedSaying[0]] : selectedSaying[0]; 31 | $done({ 32 | title: title + ':', 33 | content: selectedSaying[1], 34 | icon: 'quote.bubble', 35 | 'icon-color': '#28cd41', 36 | }); 37 | }); 38 | } -------------------------------------------------------------------------------- /style.js: -------------------------------------------------------------------------------- 1 | /* Cytyle - iOS Interface Cascading Style Sheet 2 | * Copyright (C) 2007-2013 Jay Freeman (saurik) 3 | */ 4 | 5 | (function() { 6 | var uncytyle = function(e, d) { 7 | e.className = e.className.replace(new RegExp('(\\s|^)' + d + '(\\s|$)'), ' '); 8 | }; 9 | 10 | var find = function(e) { 11 | for (var item = e.target; item != null && item.nodeName != 'A'; item = item.parentNode); 12 | if (item != null && item.href == '') 13 | return null; 14 | return item; 15 | }; 16 | 17 | if ('ontouchstart' in document.documentElement) { 18 | document.addEventListener('DOMContentLoaded', function() { 19 | FastClick.attach(document.body); 20 | 21 | document.addEventListener('click', function(e) { 22 | var item = find(e); 23 | if (item == null) 24 | return; 25 | 26 | if (typeof cydia != 'undefined') 27 | if (item.href.substr(0, 32) == 'http://cydia.saurik.com/package/') 28 | item.href = 'cydia://package/' + item.href.substr(32); 29 | 30 | item.className += ' cytyle-dn'; 31 | uncytyle(item, 'cytyle-in'); 32 | }); 33 | }, false); 34 | 35 | var timeout = null; 36 | var clear = function() { 37 | if (timeout == null) 38 | return; 39 | clearTimeout(timeout); 40 | timeout = null; 41 | }; 42 | 43 | document.addEventListener('touchstart', function(e) { 44 | var item = find(e); 45 | if (item == null) 46 | return; 47 | 48 | uncytyle(item, 'cytyle-up'); 49 | timeout = setTimeout(function() { 50 | if (timeout != null) 51 | item.className += ' cytyle-in'; 52 | }, 50); 53 | }); 54 | 55 | var stop = function(e) { 56 | var item = find(e); 57 | if (item == null) 58 | return; 59 | 60 | clear(); 61 | uncytyle(item, 'cytyle-in'); 62 | }; 63 | 64 | document.addEventListener('touchmove', stop); 65 | document.addEventListener('touchend', stop); 66 | } else { 67 | document.addEventListener('click', function(e) { 68 | var item = find(e); 69 | if (item == null) 70 | return; 71 | item.className += ' cytyle-dn'; 72 | uncytyle(item, 'cytyle-in'); 73 | }); 74 | 75 | document.addEventListener('mousedown', function(e) { 76 | var item = find(e); 77 | if (item == null) 78 | return; 79 | 80 | uncytyle(item, 'cytyle-up'); 81 | item.className += ' cytyle-in'; 82 | }); 83 | 84 | var stop = function(e) { 85 | var item = find(e); 86 | if (item == null) 87 | return; 88 | 89 | uncytyle(item, 'cytyle-in'); 90 | }; 91 | 92 | document.addEventListener('mousemove', stop); 93 | document.addEventListener('mouseup', stop); 94 | } 95 | 96 | var wipe = function(e) { 97 | var items = document.getElementsByClassName('cytyle-dn'); 98 | for (var i = items.length, e = 0; i != e; --i) { 99 | var item = items.item(i - 1); 100 | uncytyle(item, 'cytyle-in'); 101 | item.className += ' cytyle-up'; 102 | uncytyle(item, 'cytyle-dn'); 103 | } 104 | }; 105 | 106 | var page = function(e) { 107 | window.removeEventListener('pageshow', page); 108 | window.addEventListener('pageshow', wipe); 109 | }; 110 | 111 | if (typeof cydia != 'undefined') 112 | document.addEventListener("CydiaViewWillAppear", wipe); 113 | else if (typeof window.onpageshow != 'undefined') 114 | window.addEventListener('pageshow', page); 115 | })(); 116 | 117 | if (navigator.userAgent.search(/Cydia/) == -1) 118 | document.write(''); 119 | else { 120 | document.write(''); 121 | document.write(''); 122 | } 123 | 124 | // XXX: this might just fail on Chrome everywhere, even Mac :( 125 | // https://code.google.com/p/chromium/issues/detail?id=168646 126 | if (navigator.userAgent.search(/Linux/) != -1) 127 | document.write(''); 128 | 129 | (function() { 130 | var cytyle = window.location.search; 131 | cytyle = cytyle.match(/^\?cytyle=(.*)$/); 132 | 133 | if (cytyle != null) 134 | cytyle = ' cytyle-' + cytyle[1]; 135 | else { 136 | cytyle = navigator.userAgent; 137 | cytyle = cytyle.match(/.*; CPU (?:iPhone )?OS ([0-9_]*) like Mac OS X[;)]/); 138 | cytyle = cytyle == null ? '7.0' : cytyle[1].replace(/_/g, '.'); 139 | cytyle = parseInt(cytyle); 140 | cytyle = cytyle >= 7 ? ' cytyle-flat' : ' cytyle-faux'; 141 | } 142 | 143 | var body = document.documentElement; 144 | body.className += cytyle; 145 | 146 | if (window.devicePixelRatio && devicePixelRatio >= 2) { 147 | var test = document.createElement('div'); 148 | test.style.border = '.5px solid transparent'; 149 | body.appendChild(test); 150 | if (test.offsetHeight == 1) 151 | body.className += ' cytyle-hair'; 152 | body.removeChild(test); 153 | } 154 | })(); 155 | 156 | (function() { 157 | var update = function() { 158 | if (window.parent != window) 159 | parent.postMessage({cytyle: {name: "iframe-y", value: document.body.scrollHeight}}, "*"); 160 | }; 161 | 162 | window.addEventListener('message', function(event) { 163 | var message = event.data.cytyle; 164 | if (message == undefined) 165 | return; 166 | 167 | switch (message.name) { 168 | case "iframe-y": 169 | var height = message.value; 170 | var iframes = document.getElementsByTagName("iframe"); 171 | if (iframes.length != 1) 172 | return; 173 | var iframe = iframes.item(0); 174 | iframe.style.height = height + 'px'; 175 | update(); 176 | break; 177 | } 178 | }, false); 179 | 180 | window.addEventListener('load', update, false); 181 | })(); 182 | 183 | (function() { 184 | var text = document.createElement("span"); 185 | text.appendChild(document.createTextNode("My")); 186 | 187 | var block = document.createElement("div"); 188 | block.style.display = "inline-block"; 189 | block.style.height = "0px"; 190 | block.style.width = "1px"; 191 | 192 | var div = document.createElement("div"); 193 | div.id = 'cytyle-metric'; 194 | div.style.lineHeight = "normal"; 195 | 196 | div.appendChild(text); 197 | div.appendChild(block); 198 | 199 | var body = document.documentElement; 200 | body.appendChild(div); try { 201 | var full = text.offsetHeight; 202 | 203 | var style = div.currentStyle; 204 | if (typeof style == 'undefined') 205 | style = window.getComputedStyle(div, null); 206 | var font = parseInt(style.fontSize); 207 | 208 | block.style.verticalAlign = "baseline"; 209 | var base = block.offsetTop - text.offsetTop; 210 | // XXX: on iOS 3 I am unable to do this? 211 | if (base == 0) 212 | base = 14; 213 | } finally { 214 | body.removeChild(div); 215 | } 216 | 217 | var top = base - font * 0.75; 218 | 219 | //var down = (font - base) / font / 2; 220 | //alert(down + "em = (" + font + " - " + base + ") / " + font + " / 2"); 221 | var down = ((full - (base - top)) / 2 - top) / font; 222 | //alert(down + "em = ((" + full + " - (" + base + " - " + top + ")) / 2 - " + top + ") / " + font); 223 | 224 | //var over = 4.0; // Modern 225 | //var over = 2.5; // Legacy 226 | //var over = 3.5; // Chrome 227 | //var over = 3.0; // Medium 228 | //var desc = font * 0.25; 229 | //var down = (desc - over) / font; 230 | 231 | document.write(''); 232 | })(); 233 | 234 | -------------------------------------------------------------------------------- /telegram_rules.conf: -------------------------------------------------------------------------------- 1 | DOMAIN-SUFFIX,t.me 2 | DOMAIN-SUFFIX,tx.me 3 | DOMAIN-SUFFIX,tdesktop.com 4 | DOMAIN-SUFFIX,telegra.ph 5 | DOMAIN-SUFFIX,telegram.me 6 | DOMAIN-SUFFIX,telegram.org 7 | IP-CIDR,91.108.0.0/16,no-resolve 8 | IP-CIDR,109.239.140.0/24,no-resolve 9 | IP-CIDR,149.154.160.0/20,no-resolve 10 | IP-CIDR6,2001:67c:4e8::/48,no-resolve 11 | IP-CIDR6,2001:b28:f23d::/48,no-resolve 12 | IP-CIDR6,2001:b28:f23f::/48,no-resolve -------------------------------------------------------------------------------- /utils/mcdonalds_token/.eslintrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Black-Magic-Lab/Surge/1551f5d021b0d0b7cd494c9044a5b529fcb9dceb/utils/mcdonalds_token/.eslintrc -------------------------------------------------------------------------------- /utils/mcdonalds_token/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | Mcdonald's Token Parser 5 | 6 |
7 | 19 | 20 |
21 |

本工具不上傳任何資料到伺服器,請安心使用。如有疑慮亦可直接斷網後使用。

22 |

請選擇從 iMazing 匯出的「麥當勞.imazingapp」

23 |
24 | 25 |
26 |
27 | 28 |
29 |
30 |
31 |

本工具不支援 iPhone / iPad,請使用電腦瀏覽。

32 |
33 |
34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /utils/mcdonalds_token/js/main-min.js: -------------------------------------------------------------------------------- 1 | import*as zip from"../node_modules/@zip.js/zip.js/index.js";const bplist=require("bplistParser.js");let reader;const checkHasFile=()=>{document.getElementById("fileInput").value&&document.getElementById("submitButton").removeAttribute("disabled")},handleSubmit=async e=>{if(e.preventDefault(),document.getElementById("result")){let e=document.getElementById("result");for(;e.firstChild;)e.removeChild(e.firstChild)}const t=await getTargetFile(document.getElementById("fileInput").files);if(!t)return void alert("失敗:找不到 tw.com.mcddaily.plist!");const n=await unzipFileData(t),r=await bplist.parseBuffer(n),a=decryptData(r);showToken(a)},getTargetFile=async e=>{const t=e[0];reader=new zip.ZipReader(new zip.BlobReader(t));const n=await reader.getEntries();let r;if(n.length)for(const e of n)if("Container/Library/Preferences/tw.com.mcddaily.plist"===e.filename){r=e;break}return r},unzipFileData=async e=>{try{const t=await e.getData(new zip.BlobWriter);return await t.arrayBuffer()}catch(e){return void alert("失敗:解壓縮失敗,檔案可能已損毀")}},decryptData=e=>{try{const t=CryptoJS.enc.Utf8.parse("1s2unxaounk8zusv"),n={words:[0,0,0,0],sigBytes:16,mode:CryptoJS.mode.ECB,pad:CryptoJS.pad.Pkcs7},r=CryptoJS.AES.decrypt(e[0].MCDUser,t,n).toString(CryptoJS.enc.Utf8);return JSON.parse(r).accessToken}catch(e){return void alert("失敗:無法找到 Token,可能檔案有誤")}},showToken=e=>{const t=document.createElement("p"),n=document.createElement("pre");t.textContent="請將 mcdonalds_set_token.js 的第一行換成以下內容,之後執行:",n.textContent="const token = '"+e+"';",document.getElementById("result").appendChild(t),document.getElementById("result").appendChild(n)};document.getElementById("fileInput").addEventListener("change",checkHasFile),document.getElementById("fileForm").addEventListener("submit",handleSubmit); -------------------------------------------------------------------------------- /utils/mcdonalds_token/js/main.js: -------------------------------------------------------------------------------- 1 | import * as zip from '../node_modules/@zip.js/zip.js/index.js'; 2 | const bplist = require("bplistParser.js"); 3 | let reader; 4 | 5 | const checkHasFile = () => { 6 | if (document.getElementById('fileInput').value) { 7 | document.getElementById('submitButton').removeAttribute('disabled'); 8 | } 9 | }; 10 | 11 | const handleSubmit = async (event) => { 12 | event.preventDefault(); 13 | 14 | if (document.getElementById('result')) { 15 | let element = document.getElementById("result"); 16 | while (element.firstChild) { 17 | element.removeChild(element.firstChild); 18 | } 19 | } 20 | 21 | const targetFile = await getTargetFile( 22 | document.getElementById('fileInput').files 23 | ); 24 | if (!targetFile) { 25 | alert('失敗:找不到 tw.com.mcddaily.plist!'); 26 | return; 27 | } 28 | const buffer = await unzipFileData(targetFile); 29 | const plist = await bplist.parseBuffer(buffer); 30 | const token = decryptData(plist); 31 | showToken(token); 32 | }; 33 | 34 | const getTargetFile = async (files) => { 35 | const file = files[0]; 36 | reader = new zip.ZipReader(new zip.BlobReader(file)); 37 | const entries = await reader.getEntries(); 38 | 39 | let targetFile; 40 | if (entries.length) { 41 | for (const entry of entries) { 42 | if ( 43 | entry.filename === 'Container/Library/Preferences/tw.com.mcddaily.plist' 44 | ) { 45 | targetFile = entry; 46 | break; 47 | } 48 | } 49 | } 50 | return targetFile; 51 | }; 52 | 53 | const unzipFileData = async (file) => { 54 | try { 55 | const blob = await file.getData(new zip.BlobWriter()); 56 | return (await blob.arrayBuffer()); 57 | } catch (error) { 58 | alert('失敗:解壓縮失敗,檔案可能已損毀'); 59 | return; 60 | } 61 | }; 62 | 63 | const decryptData = (jsonData) => { 64 | try { 65 | const aesKey = CryptoJS.enc.Utf8.parse('1s2unxaounk8zusv'); 66 | const aesConfig = { words: [0, 0, 0, 0], sigBytes: 16, mode: CryptoJS.mode.ECB, pad: CryptoJS.pad.Pkcs7 }; 67 | const bytes = CryptoJS.AES.decrypt(jsonData[0].MCDUser, aesKey, aesConfig); 68 | const decodedString = bytes.toString(CryptoJS.enc.Utf8); 69 | const userData = JSON.parse(decodedString); 70 | return userData.accessToken; 71 | } catch (error) { 72 | alert('失敗:無法找到 Token,可能檔案有誤'); 73 | return; 74 | } 75 | } 76 | 77 | const showToken = (token) => { 78 | const description = document.createElement("p"); 79 | const code = document.createElement("pre"); 80 | description.textContent = '請將 mcdonalds_set_token.js 的第一行換成以下內容,之後執行:'; 81 | code.textContent = 'const token = \'' + token + '\';'; 82 | 83 | document.getElementById('result').appendChild(description); 84 | document.getElementById('result').appendChild(code); 85 | } 86 | 87 | document.getElementById('fileInput').addEventListener('change', checkHasFile); 88 | document.getElementById('fileForm').addEventListener('submit', handleSubmit); 89 | -------------------------------------------------------------------------------- /utils/mcdonalds_token/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codekit-project", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@zip.js/zip.js": { 8 | "version": "2.3.17", 9 | "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.3.17.tgz", 10 | "integrity": "sha512-ktTJ8dvbiIu4MAlioJo/475QtTsbOBK5YmBbvRJaduCeEKOof/ZTY2H7DPwiC0pC9dDfEo+uFsqMVI1J4HLl5g==" 11 | }, 12 | "crypto-js": { 13 | "version": "4.1.1", 14 | "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", 15 | "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /utils/mcdonalds_token/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codekit-project", 3 | "version": "1.0.0", 4 | "description": "CodeKit created this starter package.json file. You should customize it appropriately.", 5 | "author": "", 6 | "license": "UNLICENSED", 7 | "repository": {}, 8 | "dependencies": { 9 | "@zip.js/zip.js": "^2.3.17", 10 | "crypto-js": "^4.1.1" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /utils/shopee_crop/get_water_friends.php: -------------------------------------------------------------------------------- 1 | DATE_SUB(NOW(), INTERVAL 10 DAY) AND user_id != '$user_id' ORDER BY priority DESC, RAND() LIMIT 20"; 42 | } elseif ($scope === 'all') { 43 | $query = "SELECT user_id, user_name FROM shopee_crop"; 44 | } elseif ($scope === 'group' || $scope === 'group_all') { 45 | $query = "SELECT id FROM shopee_crop WHERE user_id='$user_id'"; 46 | $result = mysqli_query($link, $query); 47 | $row = mysqli_fetch_assoc($result); 48 | if (!$row) { 49 | echo json_encode(json_decode("[]")); 50 | } 51 | $num = $row['id']; 52 | $min = floor($num/$group_size) * $group_size + 1; 53 | $max = ceil($num/$group_size) * $group_size; 54 | if ($min >= $max) { 55 | $min -= $group_size; 56 | } 57 | // echo $min.' '.$max; 58 | $query = "SELECT user_id, crop_id, user_name FROM shopee_crop WHERE timestamp > DATE_SUB(NOW(), INTERVAL 10 DAY) AND user_id != '$user_id' AND id >= '$min' AND id <= '$max' ORDER BY priority DESC, RAND() LIMIT 20"; 59 | if ($scope === 'group_all') { 60 | $query = "SELECT user_id, crop_id, user_name FROM shopee_crop WHERE user_id != '$user_id' AND id >= '$min' AND id <= '$max'"; 61 | } 62 | } else { 63 | echo json_encode(json_decode("[]")); 64 | return; 65 | } 66 | 67 | $result = mysqli_query($link, $query); 68 | if (mysqli_num_rows($result) > 0) { 69 | $response = array(); 70 | while ($row = mysqli_fetch_assoc($result)) { 71 | $response[] = $row; 72 | } 73 | shuffle($response); 74 | echo json_encode($response); 75 | } else { 76 | echo json_encode(json_decode("[]")); 77 | } 78 | -------------------------------------------------------------------------------- /utils/shopee_crop/upload_crop_info.php: -------------------------------------------------------------------------------- 1 | 0) { 58 | $row = $result->fetch_assoc(); 59 | $old_crop_id = $row['crop_id']; 60 | if (intval($old_crop_id) === intval($crop_id)) { 61 | $data = array("result" => 2); 62 | echo json_encode($data); 63 | return; 64 | } 65 | $query = "UPDATE shopee_crop SET crop_id = '$crop_id', timestamp = now() WHERE user_id = '$user_id'"; 66 | mysqli_query($link, $query); 67 | 68 | $data = array("result" => 0); 69 | echo json_encode($data); 70 | return; 71 | } else { 72 | // 檢查是否超過開放人數 73 | $query = "SELECT count(*) FROM shopee_crop"; 74 | $result = mysqli_query($link, $query); 75 | 76 | $row = mysqli_fetch_row($result); 77 | if ($row[0] >= $max_user) { 78 | $data = array("result" => 1); 79 | echo json_encode($data); 80 | return; 81 | } 82 | 83 | // 新增使用者 84 | $query = "INSERT INTO shopee_crop (user_id, crop_id, user_name, timestamp) VALUES ('$user_id', '$crop_id', '$user_name', now())"; 85 | mysqli_query($link, $query); 86 | 87 | $data = array("result" => 0); 88 | echo json_encode($data); 89 | return; 90 | } 91 | -------------------------------------------------------------------------------- /utils/surge_check/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Surge Check 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |

Surge Check

16 |
17 |
18 |
19 |
20 |
Header Rewrite
21 |
22 | $value) { 25 | if ($name === "X-Hiraku-Rewrite") { 26 | if ($value === "1") { 27 | $rewrite_success = true; 28 | break; 29 | } 30 | } 31 | } 32 | echo $rewrite_success ? "Success ✅" : "Failed ❌"; 33 | ?> 34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
Script Execution
42 |
43 | $value) { 46 | if ($name === "X-Hiraku-Script") { 47 | if ($value === "1") { 48 | $script_success = true; 49 | break; 50 | } 51 | } 52 | } 53 | echo $script_success ? "Success ✅" : "Failed ❌"; 54 | ?> 55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
MitM Response
63 |
Failed ❌
64 |
65 |
66 |
67 |
68 |
69 |
70 | 請先安裝檢測專用模組,以正確顯示本頁結果 71 |
72 |
73 | 安裝連結: 74 | Surge 75 | 、 76 | Loon 77 |
78 |
79 |
80 | 81 | 106 | 107 | --------------------------------------------------------------------------------