├── .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 |
106 |
107 |
139 |
140 |
372 |
373 |
425 |
426 |
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 |
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 |
--------------------------------------------------------------------------------