├── README.md └── Flexiroam_FreePlan.py /README.md: -------------------------------------------------------------------------------- 1 | # FreeRoam 2 | 3 | 1. Push to your aws cloud or git actions. 4 | 2. Register new flexiroam account. 5 | 3. Redeem to get new eSim card. 6 | 4. start program, enjoy to used global network. 7 | -------------------------------------------------------------------------------- /Flexiroam_FreePlan.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import requests 3 | import json 4 | import random 5 | import time 6 | import threading 7 | from datetime import datetime, timedelta 8 | 9 | USERNAME = "" 10 | PASSWORD = "" 11 | CARDBIN = "528911" 12 | JWT_Default = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjbGllbnRfaWQiOjQsImZpcnN0X25hbWUiOiJUcmF2ZWwiLCJsYXN0X25hbWUiOiJBcHAiLCJlbWFpbCI6InRyYXZlbGFwcEBmbGV4aXJvYW0uY29tIiwidHlwZSI6IkNsaWVudCIsImFjY2Vzc190eXBlIjoiQXBwIiwidXNlcl9hY2NvdW50X2lkIjo2LCJ1c2VyX3JvbGUiOiJWaWV3ZXIiLCJwZXJtaXNzaW9uIjpbXSwiZXhwaXJlIjoxODc5NjcwMjYwfQ.-RtM_zNG-zBsD_S2oOEyy4uSbqR7wReAI92gp9uh-0Y" 13 | 14 | def main(): 15 | logging.basicConfig(level=logging.INFO, format='%(asctime)s.%(msecs)03d [%(levelname)s] [%(filename)s:%(lineno)d] %(message)s', datefmt='%Y-%m-%d %H:%M:%S') 16 | logging.info("正在启动 Flexiroam 自动注册 MasterCard 免费 3G Plan 脚本程序......") 17 | 18 | session = requests.session() 19 | 20 | logging.info("正在登录获取 token ......") 21 | 22 | res, resultLogin = login(session, USERNAME, PASSWORD) 23 | if not res: 24 | logging.error("登录获取 token 失败! 原因: " + resultLogin) 25 | exit(1) 26 | 27 | logging.info("正在获取 csrf ......") 28 | res, csrf = getCsrf(session) 29 | token = resultLogin["token"] 30 | 31 | if not res: 32 | logging.error("获取 csrf 失败! 原因: " + csrf) 33 | exit(1) 34 | 35 | logging.info("正在认证获取 __Secure-authjs.session-token ......") 36 | 37 | # 获取认证 Cookie 38 | res, resultCredentials = credentials(session, csrf, token) 39 | 40 | if not res: 41 | logging.error("获取 __Secure-authjs.session-token 失败! 原因: " + resultCredentials) 42 | exit(1) 43 | 44 | logging.info("登录成功!正在初始化计划信息,并启用 session 更新......") 45 | 46 | # 启动 session 更新线程 47 | threading.Thread(target=updateSessionThread, daemon=True, kwargs={ "session": session }).start() 48 | 49 | # 启动 计划管理线程 50 | threading.Thread(target=autoActivePlansThread, daemon=True, kwargs={ "session": session, "token": token }).start() 51 | 52 | # 梗塞进程 53 | while True: 54 | time.sleep(1000) 55 | 56 | # 计划管理线程 57 | def autoActivePlansThread(session, token): 58 | def selectOutPlans(plans): 59 | newPlans = [] 60 | 61 | for plan in plans["plans"]: 62 | percentage = plan["circleChart"]["percentage"] 63 | 64 | if percentage != 0: 65 | newPlans.append(plan) 66 | 67 | return newPlans 68 | 69 | def getActivePercentage(plans): 70 | allRate = 0 71 | 72 | for plan in plans: 73 | if plan["status"] == 'Active': 74 | allRate += plan["circleChart"]["percentage"] 75 | 76 | return allRate 77 | 78 | def getInactivePlan(plans): 79 | allCount = 0 80 | planId = 0 81 | allRate = 0 82 | 83 | for plan in plans["plans"]: 84 | if plan["status"] == 'In-active': 85 | allCount += 1 86 | allRate += plan["circleChart"]["percentage"] 87 | 88 | if planId == 0: 89 | planId = plan["planId"] 90 | continue 91 | 92 | return allCount, allRate, planId 93 | 94 | # 默认时间 95 | dayGet = 0 96 | timeSec = 0 97 | 98 | # 保守起见 99 | lastGetPlansTime = datetime.now() - timedelta(hours=7) 100 | while True: 101 | 102 | # 默认120秒 103 | time.sleep(120) 104 | 105 | # 一天最大获取计划上限重置 106 | timeSec += 120 107 | if timeSec > 86400: 108 | dayGet = 0 109 | timeSec = 0 110 | 111 | # 获取当前计划 112 | res, resultPlans = getPlans(session) 113 | 114 | if not res and "获取计划失败,没有寻找到计划信息" not in resultPlans: 115 | logging.error("获取 Plans 失败! 原因: " + resultPlans) 116 | continue 117 | 118 | if not res: 119 | resultPlans = { "plans": [] } 120 | 121 | activePlans = selectOutPlans(resultPlans) 122 | balanceCount, inRate, fristPlanId = getInactivePlan(resultPlans) 123 | 124 | # 获取目前剩余流量 125 | rateRoam = getActivePercentage(activePlans) 126 | 127 | logging.info("已经激活流量:「" + str((rateRoam / 100) * 3) + " G」,未激活流量:「" + str((inRate / 100) * 3) + " G」,剩余计划数:「" + str(balanceCount) + "」") 128 | 129 | 130 | current_time = datetime.now() 131 | # 判断是否流量不够了 132 | if rateRoam <= 30 and balanceCount != 0: 133 | res, resultStartPlan = startPlans(session, token, fristPlanId) 134 | 135 | if not res: 136 | logging.error("启动新 Plans 失败! 原因: " + resultStartPlan) 137 | continue 138 | 139 | # 如果启动新计划了,等待一个小时候后再注册新计划 140 | if current_time - lastGetPlansTime >= timedelta(hours=6): 141 | lastGetPlansTime = datetime.now() - timedelta(hours=5) 142 | 143 | logging.info("启动新 Plans 成功! PlanId: " + str(fristPlanId)) 144 | continue 145 | 146 | # 自动补充计划 147 | if balanceCount < 2 and dayGet < 4 and current_time - lastGetPlansTime >= timedelta(hours=6): 148 | result = eligibilityAddToAccount(session, token) 149 | if result == 1: 150 | # 重置时间 151 | lastGetPlansTime = datetime.now() 152 | 153 | if result != 0: 154 | continue 155 | 156 | # 获取 +1 157 | dayGet += 1 158 | 159 | # 重置时间 160 | lastGetPlansTime = datetime.now() 161 | 162 | 163 | 164 | def eligibilityAddToAccount(session, token): 165 | # 生成卡号 166 | cardNumber = generate_card_number(CARDBIN) 167 | 168 | # 确认卡号是否符合规则 169 | res, resultEligibilityPlan = eligibilityPlan(session, token, cardNumber) 170 | 171 | if not res: 172 | if resultEligibilityPlan == "We are currently processing your previous redemption, kindly retry again later": 173 | logging.warning("确认卡号资格失败! 原因: 正在等待新计划下发,重置等待时间2小时 cardinfo: " + cardNumber) 174 | return 1 175 | 176 | # 直接停止运行 177 | if "账号被封" in resultEligibilityPlan or "卡号不符合规则" in resultEligibilityPlan: 178 | logging.warning("确认卡号资格失败! 原因: "+ resultEligibilityPlan + " cardinfo: " + cardNumber) 179 | exit(-1) 180 | 181 | logging.error("确认卡号资格失败! 原因: " + resultEligibilityPlan + " cardinfo: " + cardNumber) 182 | return 2 183 | 184 | # 确认注册计划 185 | res, resultRedemptionConfirm = redemptionConfirm(session, token, resultEligibilityPlan) 186 | 187 | if not res: 188 | logging.error("获取新 Plans 失败! 原因: " + resultRedemptionConfirm + " cardinfo: " + cardNumber) 189 | return 2 190 | 191 | logging.info("获取新 Plans 成功! msg: " + resultRedemptionConfirm + " cardinfo: " + cardNumber) 192 | return 0 193 | 194 | # 自动更新 Session 线程 195 | def updateSessionThread(session): 196 | while True: 197 | res, result = updateSession(session) 198 | 199 | if not res: 200 | logging.error("更新 Session 失败! 原因: " + result) 201 | exit(1) 202 | 203 | logging.info("更新 Session 成功!") 204 | time.sleep(3600) 205 | 206 | # 信用卡计算工具 207 | ############################################ 208 | 209 | def luhn_checksum(card_number): 210 | """计算 Luhn 校验和""" 211 | digits = [int(d) for d in card_number] 212 | for i in range(len(digits) - 2, -1, -2): # 从倒数第二位开始,每隔一位翻倍 213 | digits[i] *= 2 214 | if digits[i] > 9: 215 | digits[i] -= 9 # 如果翻倍后大于 9,则减去 9 216 | return sum(digits) % 10 # Luhn 校验值 217 | 218 | def generate_card_number(bin_prefix, length=16): 219 | """基于 BIN 生成符合 Luhn 规则的完整卡号""" 220 | while True: 221 | card_number = bin_prefix + ''.join(str(random.randint(0, 9)) for _ in range(length - len(bin_prefix) - 1)) 222 | check_digit = (10 - luhn_checksum(card_number + "0")) % 10 # 计算 Luhn 校验位 223 | full_card_number = card_number + str(check_digit) 224 | 225 | if luhn_checksum(full_card_number) == 0: # 确保卡号有效 226 | return full_card_number 227 | 228 | # API 列表 229 | ############################################ 230 | 231 | def login(session, user, pwd): 232 | result = session.post(url="https://prod-enduserservices.flexiroam.com/api/user/login",headers={ 233 | "authorization": "Bearer " + JWT_Default, 234 | "content-type": "application/json", 235 | "user-agent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Mobile Safari/537.36" 236 | },json={ 237 | "email": user, 238 | "password": pwd, 239 | "device_udid": "iPhone17,2", 240 | "device_model": "iPhone17,2", 241 | "device_platform": "ios", 242 | "device_version": "18.3.1", 243 | "have_esim_supported_device": 1, 244 | "notification_token": "undefined" 245 | }) 246 | 247 | resultJson = result.json() 248 | if resultJson["message"] != "Login Successful": 249 | return False, resultJson["message"] 250 | 251 | return True, resultJson["data"] 252 | 253 | def credentials(session, csrf, token): 254 | result = session.post(url="https://www.flexiroam.com/api/auth/callback/credentials?", headers={ 255 | "content-type": "application/x-www-form-urlencoded", 256 | "referer": "https://www.flexiroam.com/en-us/login", 257 | "user-agent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Mobile Safari/537.36", 258 | "x-auth-return-redirect": "1" 259 | }, data={ 260 | "token": token, 261 | "redirect": False, 262 | "csrfToken": csrf, 263 | "callbackUrl": "https://www.flexiroam.com/en-us/login" 264 | }) 265 | 266 | resultJson = result.json() 267 | if "url" not in resultJson: 268 | return False, result.text 269 | 270 | return True, "" 271 | 272 | def updateSession(session): 273 | result = session.get(url="https://www.flexiroam.com/api/auth/session", headers={ 274 | "referer": "https://www.flexiroam.com/en-us/home", 275 | "user-agent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Mobile Safari/537.36" 276 | }) 277 | 278 | resultJson = result.json() 279 | if "expires" not in resultJson: 280 | return False, result.text 281 | 282 | return True, "" 283 | 284 | def getCsrf(session): 285 | result = session.get(url="https://www.flexiroam.com/api/auth/csrf", headers={ 286 | "referer": "https://www.flexiroam.com/en-us/home", 287 | "user-agent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Mobile Safari/537.36" 288 | }) 289 | 290 | resultJson = result.json() 291 | if "csrfToken" not in resultJson: 292 | return False, result.text 293 | 294 | return True, resultJson["csrfToken"] 295 | 296 | def getPlans(session): 297 | try: 298 | result = session.get(url="https://www.flexiroam.com/en-us/my-plans", headers={ 299 | "referer": "https://www.flexiroam.com/en-us/home", 300 | "rsc": "1", 301 | "user-agent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Mobile Safari/537.36" 302 | }) 303 | 304 | # 获取只有计划的那个 Json 数据 305 | for line in result.text.splitlines(): 306 | if '{"plans":[' in line: 307 | splits = line.split('{"plans":[') 308 | resultRaw = '{"plans":[' + splits[1][:len(splits[1]) - 1] 309 | 310 | return True, json.loads(resultRaw) 311 | 312 | return False, "获取计划失败,没有寻找到计划信息,可能是没手动注册第一个,操作后等一会就好。" 313 | except: 314 | time.sleep(1) 315 | return getPlans(session) 316 | 317 | def startPlans(session, token, sim_plan_id): 318 | result = session.post(url="https://prod-planservices.flexiroam.com/api/plan/start", headers={ 319 | "authorization": "Bearer " + token, 320 | "content-type": "application/json", 321 | "user-agent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Mobile Safari/537.36" 322 | }, json={ 323 | "sim_plan_id": sim_plan_id 324 | }) 325 | 326 | resultJson = result.json() 327 | if "data" not in resultJson: 328 | return False, resultJson["message"] 329 | 330 | return True, "激活计划成功!" 331 | 332 | def eligibilityPlan(session, token, lookup_value): 333 | result = session.post(url="https://prod-enduserservices.flexiroam.com/api/user/redemption/check/eligibility", headers={ 334 | "authorization": "Bearer " + token, 335 | "content-type": "application/json", 336 | "user-agent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Mobile Safari/537.36" 337 | }, json={ 338 | "email": USERNAME, 339 | "lookup_value": lookup_value 340 | }) 341 | 342 | resultJson = result.json() 343 | if "Authorization Failed" in resultJson["message"]: 344 | return False, "账号被封,停止运行。" 345 | 346 | if "Your Mastercard is not eligible for the offer" in resultJson["message"]: 347 | return False, "卡号不符合规则。" 348 | 349 | if "3GB Global Data Plan" not in resultJson["message"]: 350 | return False, resultJson["message"] 351 | 352 | return True, resultJson["data"]["redemption_id"] 353 | 354 | def redemptionConfirm(session, token, redemption_id): 355 | result = session.post(url="https://prod-enduserservices.flexiroam.com/api/user/redemption/confirm", headers={ 356 | "authorization": "Bearer " + token, 357 | "content-type": "application/json", 358 | "user-agent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Mobile Safari/537.36" 359 | }, json={ 360 | "redemption_id": redemption_id 361 | }) 362 | 363 | resultJson = result.json() 364 | if resultJson["message"] != "Redemption confirmed": 365 | return False, resultJson["message"] 366 | 367 | return True, "获取新计划成功!" 368 | 369 | main() 370 | --------------------------------------------------------------------------------