其实只是前端被下掉,后端没改,我做了个油猴脚本,可以继续下单ESIM
大家接着奏乐接着舞
下单地址: https://www.o2online.de/mobilfunk/prepaid/esim/
添加脚本后,在无痕模式打开页面,并下单,出现ESIM就成功

// ==UserScript==
// @name o2 Prepaid Force E_SIM
// @namespace local.o2.force-esim
// @version 0.3
// @description Force o2 simType from SIM_CARD to E_SIM in page state and cart requests.
// @match https://www.o2online.de/*
// @match https://o2online.de/*
// @match https://*.o2online.de/*
// @match https://*.o9.de/*
// @run-at document-start
// @grant unsafeWindow
// ==/UserScript==
(function () {
'use strict';
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const FROM = 'SIM_CARD';
const TO = 'E_SIM';
const DEBUG = true;
const NativeJSONParse = W.JSON.parse.bind(W.JSON);
const NativeJSONStringify = W.JSON.stringify.bind(W.JSON);
const NativeRequest = W.Request;
const NativeResponse = W.Response;
const NativeXHR = W.XMLHttpRequest;
function log(...args) {
if (DEBUG && W.console) {
W.console.log(
'%c[o2 force E_SIM]',
'color:#0a84ff;font-weight:bold',
...args
);
}
}
function warn(...args) {
if (DEBUG && W.console) {
W.console.warn('[o2 force E_SIM]', ...args);
}
}
function isObject(value) {
return value !== null && typeof value === 'object';
}
function getUrl(input) {
try {
if (typeof input === 'string') {
return new W.URL(input, W.location.href);
}
if (input && typeof input.url === 'string') {
return new W.URL(input.url, W.location.href);
}
} catch (_) {}
return null;
}
function isTargetUrl(url) {
if (!url) return true;
const host = String(url.hostname || '').toLowerCase();
return (
host === String(W.location.hostname || '').toLowerCase() ||
/(^|\.)o2online\.de$/.test(host) ||
/(^|\.)o9\.de$/.test(host) ||
host.includes('telefonica')
);
}
/**
* 递归修改对象里的 simType / defaultSimType / selectedSimType 等字段。
* 注意:不会把 supportedSimType: ["E_SIM", "SIM_CARD"] 这种数组里的 SIM_CARD 删除。
*/
function patchObject(obj, seen) {
if (!isObject(obj)) return false;
const WeakSetCtor = W.WeakSet || WeakSet;
seen = seen || new WeakSetCtor();
if (seen.has(obj)) return false;
seen.add(obj);
let changed = false;
if (W.Array.isArray(obj)) {
for (const item of obj) {
if (isObject(item)) {
changed = patchObject(item, seen) || changed;
}
}
return changed;
}
for (const key of Object.keys(obj)) {
const value = obj[key];
// simType / defaultSimType / selectedSimType / currentSimType ...
if (/simType/i.test(key) && value === FROM) {
obj[key] = TO;
changed = true;
}
// 兼容 { key: "simType", value: "SIM_CARD" } 这种结构
if ((key === 'value' || key === 'selectedValue') && value === FROM) {
const marker = [
obj.key,
obj.name,
obj.id,
obj.type,
obj.code
].filter(Boolean).join(' ');
if (/simType/i.test(marker)) {
obj[key] = TO;
changed = true;
}
}
if (isObject(value)) {
changed = patchObject(value, seen) || changed;
}
}
return changed;
}
function patchStringBody(text) {
if (typeof text !== 'string') {
return { body: text, changed: false };
}
if (!text.includes(FROM) || !/simType/i.test(text)) {
return { body: text, changed: false };
}
const trimmed = text.trim();
// JSON body
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
try {
const data = NativeJSONParse(text);
if (patchObject(data)) {
return {
body: NativeJSONStringify(data),
changed: true
};
}
} catch (_) {}
}
// 普通字符串 / fallback
const patched = text
.replace(/("([^"]*simType[^"]*)"\s*:\s*")SIM_CARD(")/gi, '$1E_SIM$3')
.replace(/('([^']*simType[^']*)'\s*:\s*')SIM_CARD(')/gi, '$1E_SIM$3')
.replace(/(\b[\w.-]*simType[\w.-]*=)SIM_CARD\b/gi, '$1E_SIM');
return {
body: patched,
changed: patched !== text
};
}
function patchBodySync(body) {
if (!body) {
return { body, changed: false };
}
if (typeof body === 'string') {
return patchStringBody(body);
}
// URLSearchParams: simType=SIM_CARD
try {
if (W.URLSearchParams && body instanceof W.URLSearchParams) {
let changed = false;
const next = new W.URLSearchParams(body.toString());
for (const [key, value] of next.entries()) {
if (/simType/i.test(key) && value === FROM) {
next.set(key, TO);
changed = true;
}
}
return changed
? { body: next, changed: true }
: { body, changed: false };
}
} catch (_) {}
// FormData
try {
if (W.FormData && body instanceof W.FormData) {
let changed = false;
for (const [key, value] of body.entries()) {
if (/simType/i.test(key) && value === FROM) {
body.set(key, TO);
changed = true;
}
}
return { body, changed };
}
} catch (_) {}
// 普通对象,少见,但兼容一下
try {
if (isObject(body)) {
const changed = patchObject(body);
return { body, changed };
}
} catch (_) {}
return { body, changed: false };
}
function maybePatchBody(body, url, source) {
if (!isTargetUrl(url)) {
return { body, changed: false };
}
const result = patchBodySync(body);
if (result.changed) {
log(`${source}: ${FROM} -> ${TO}`, url ? url.href : '');
}
return result;
}
/**
* 1. 拦截 JSON.parse
* 页面源码里很多 MFE 配置是 JSON.parse('...') 读进去的。
*/
try {
W.JSON.parse = function patchedJSONParse(text, reviver) {
const data = NativeJSONParse(text, reviver);
try {
if (
typeof text === 'string' &&
text.includes(FROM) &&
/simType/i.test(text)
) {
if (patchObject(data)) {
log(`JSON.parse: ${FROM} -> ${TO}`);
}
}
} catch (_) {}
return data;
};
log('JSON.parse patched');
} catch (e) {
warn('JSON.parse patch failed', e);
}
/**
* 2. 拦截 JSON.stringify
* 很多 add 请求的 body 是 JSON.stringify(payload) 之后发出去的。
*/
try {
W.JSON.stringify = function patchedJSONStringify(value, replacer, space) {
const text = NativeJSONStringify(value, replacer, space);
try {
const result = patchStringBody(text);
if (result.changed) {
log(`JSON.stringify: ${FROM} -> ${TO}`);
return result.body;
}
} catch (_) {}
return text;
};
log('JSON.stringify patched');
} catch (e) {
warn('JSON.stringify patch failed', e);
}
/**
* 3. 拦截 Response.json
* 如果 item?offerIds=... 返回了 defaultSimType/simType=SIM_CARD,
* 这里会在前端读取响应时改成 E_SIM。
*/
try {
if (NativeResponse && NativeResponse.prototype && NativeResponse.prototype.json) {
const nativeResponseJson = NativeResponse.prototype.json;
NativeResponse.prototype.json = function patchedResponseJson(...args) {
const responseUrl = this && this.url ? this.url : '';
return nativeResponseJson.apply(this, args).then((data) => {
try {
const url = getUrl(responseUrl);
if (isTargetUrl(url) && patchObject(data)) {
log(`Response.json: ${FROM} -> ${TO}`, responseUrl);
}
} catch (_) {}
return data;
});
};
log('Response.json patched');
}
} catch (e) {
warn('Response.json patch failed', e);
}
/**
* 4. 拦截 Request 构造器
* 兼容 new Request(url, { body: ... }) 这种写法。
*/
try {
if (typeof NativeRequest === 'function' && typeof Proxy === 'function') {
W.Request = new Proxy(NativeRequest, {
construct(target, args, newTarget) {
try {
const input = args[0];
const init = args[1];
const url = getUrl(input);
if (init && Object.prototype.hasOwnProperty.call(init, 'body')) {
const result = maybePatchBody(init.body, url, 'new Request');
if (result.changed) {
args[1] = Object.assign({}, init, {
body: result.body
});
}
}
} catch (e) {
warn('Request constructor patch error', e);
}
return Reflect.construct(target, args, newTarget);
}
});
W.Request.prototype = NativeRequest.prototype;
log('Request constructor patched');
}
} catch (e) {
warn('Request constructor patch failed', e);
}
/**
* 5. 拦截 fetch
*/
try {
if (typeof W.fetch === 'function') {
const nativeFetch = W.fetch;
W.fetch = async function patchedFetch(input, init) {
let nextInput = input;
let nextInit = init;
const url = getUrl(input);
try {
// fetch(url, { body: ... })
if (init && Object.prototype.hasOwnProperty.call(init, 'body')) {
const result = maybePatchBody(init.body, url, 'fetch init.body');
if (result.changed) {
nextInit = Object.assign({}, init, {
body: result.body
});
}
}
// fetch(new Request(...))
else if (
NativeRequest &&
input instanceof NativeRequest &&
!/^(GET|HEAD)$/i.test(input.method || 'GET')
) {
const cloned = input.clone();
const text = await cloned.text();
const result = maybePatchBody(text, url, 'fetch Request.body');
if (result.changed) {
nextInput = new NativeRequest(input, {
body: result.body
});
}
}
} catch (e) {
warn('fetch patch error', e);
}
return nativeFetch.call(this, nextInput, nextInit);
};
log('fetch patched');
}
} catch (e) {
warn('fetch patch failed', e);
}
/**
* 6. 拦截 XMLHttpRequest
*/
try {
if (NativeXHR && NativeXHR.prototype) {
const nativeOpen = NativeXHR.prototype.open;
const nativeSend = NativeXHR.prototype.send;
NativeXHR.prototype.open = function patchedOpen(method, url, ...rest) {
try {
this.__o2ForceESimUrl = getUrl(url);
this.__o2ForceESimMethod = method;
} catch (_) {}
return nativeOpen.call(this, method, url, ...rest);
};
NativeXHR.prototype.send = function patchedSend(body) {
try {
const result = maybePatchBody(
body,
this.__o2ForceESimUrl,
'XMLHttpRequest.send'
);
if (result.changed) {
body = result.body;
}
} catch (e) {
warn('XHR patch error', e);
}
return nativeSend.call(this, body);
};
log('XMLHttpRequest patched');
}
} catch (e) {
warn('XMLHttpRequest patch failed', e);
}
W.__o2ForceESimPatch = {
enabled: true,
from: FROM,
to: TO,
version: '0.3'
};
log('installed', W.__o2ForceESimPatch);
})();