油猴脚本 手机和电脑都可以使用
目前看来是被暗改了 只有鼠标放到名字后面的图标上才显示出来
// ==UserScript==
// @name NodeLoc(机型标签伪造)
// @namespace http://tampermonkey.net/
// @version 1.0
// @author CC1359
// @description 支持发帖时伪造/自定义机型标签
// @match https://*.nodeloc.com/*
// @grant none
// @run-at document-start
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// ==================== 机型标签伪造配置 ====================
// 是否启用伪造
const ENABLED = true;
// 当前选择的伪造机型(可修改为预设名称或自定义英文/数字)
let CURRENT_MODEL = "iPhone 4S";
const DEVICES = [
{ name: "iPhone 15 Pro Max", brand: "Apple", model: "iPhone 15 Pro Max", platform: "ios" },
{ name: "iPhone SE (3rd generation)", brand: "Apple", model: "iPhone SE (3rd generation)", platform: "ios" },
{ name: "iPad Pro 12.9", brand: "Apple", model: "iPad Pro", platform: "ios" },
{ name: "Google Pixel 8 Pro", brand: "Google", model: "Pixel 8 Pro", platform: "android" },
{ name: "Samsung Galaxy S24 Ultra", brand: "Samsung", model: "Samsung Galaxy S24 Ultra", platform: "android" },
{ name: "Xiaomi 14", brand: "Xiaomi", model: "Xiaomi 14", platform: "android" },
{ name: "Redmi Note 13 Pro", brand: "Redmi", model: "Redmi Note 13 Pro", platform: "android" },
{ name: "HUAWEI Mate 60 Pro", brand: "HUAWEI", model: "HUAWEI Mate 60 Pro", platform: "android" },
{ name: "OPPO Find X7 Ultra", brand: "OPPO", model: "OPPO Find X7 Ultra", platform: "android" },
{ name: "vivo X100 Pro", brand: "vivo", model: "vivo X100 Pro", platform: "android" },
{ name: "OnePlus 12", brand: "OnePlus", model: "OnePlus 12", platform: "android" },
{ name: "NodeLoc", brand: "NodeLoc", model: "NodeLoc", platform: "android" }
];
function getDeviceConfig() {
if (!ENABLED) return null;
let found = DEVICES.find(d => d.model.toLowerCase() === CURRENT_MODEL.toLowerCase());
if (found) {
return {
platform: found.platform,
brand: found.brand,
model: found.model
};
}
let cleanModel = String(CURRENT_MODEL || "").replace(/[^\x20-\x7E]/g, "").trim();
if (!cleanModel) return null;
return {
platform: "android",
brand: "NodeLoc",
model: cleanModel
};
}
const DEVICE_CONFIG = getDeviceConfig();
const DeviceInject = (() => {
function applyDevice(p, d) {
if (!d) return p;
if (d.platform) p.mobile_source_platform = d.platform;
if (d.brand) p.mobile_source_brand = d.brand;
if (d.model) p.mobile_source_model = d.model;
return p;
}
function parseUrlencoded(str) {
const out = {};
if (typeof str !== "string" || !str) return out;
const sp = new URLSearchParams(str);
for (const [k, v] of sp.entries()) out[k] = v;
return out;
}
function serializeUrlencoded(p) {
const sp = new URLSearchParams();
for (const k of Object.keys(p)) {
if (p[k] === undefined || p[k] === null) continue;
sp.append(k, String(p[k]));
}
return sp.toString();
}
function parseJson(str) {
try { return str && typeof str === "string" && str.trim() ? JSON.parse(str) : {}; }
catch { return {}; }
}
function isFormData(b) { return typeof FormData !== "undefined" && b instanceof FormData; }
function isURLSearchParams(b) { return typeof URLSearchParams !== "undefined" && b instanceof URLSearchParams; }
function injectBody(body, contentType, device) {
if (!device) return { body, contentType: contentType || "" };
if (isFormData(body) || isURLSearchParams(body)) {
if (device.platform) body.append("mobile_source_platform", device.platform);
if (device.brand) body.append("mobile_source_brand", device.brand);
if (device.model) body.append("mobile_source_model", device.model);
return { body, contentType: contentType || "" };
}
const ct = (contentType || "").toLowerCase();
const forceJson = ct.includes("json");
if (typeof body === "string") {
if (forceJson || body.trim().startsWith("{") || body.trim().startsWith("[")) {
const obj = parseJson(body);
applyDevice(obj, device);
return { body: JSON.stringify(obj), contentType: ct || "application/json" };
}
const p = parseUrlencoded(body);
applyDevice(p, device);
return { body: serializeUrlencoded(p), contentType: ct || "application/x-www-form-urlencoded" };
}
if (body && typeof body === "object") {
const obj = forceJson ? body : parseUrlencoded(String(body));
applyDevice(obj, device);
if (forceJson) return { body: JSON.stringify(obj), contentType: ct };
return { body: serializeUrlencoded(obj), contentType: "application/x-www-form-urlencoded" };
}
const p = {};
applyDevice(p, device);
return { body: serializeUrlencoded(p), contentType: "application/x-www-form-urlencoded" };
}
return { injectBody };
})();
const TARGET_RE = /\/posts(\.json)?(\?|$)/;
function isTarget(url) {
if (!url) return false;
try {
const u = new URL(url, location.href);
return u.hostname.endsWith("nodeloc.com") && TARGET_RE.test(u.pathname);
} catch { return TARGET_RE.test(url); }
}
function looksJson(s) {
return typeof s === "string" && (s.trim().startsWith("{") || s.trim().startsWith("["));
}
function getContentType(headers) {
if (!headers) return "";
if (typeof headers.get === "function") return headers.get("Content-Type") || "";
if (headers instanceof Object) return headers["Content-Type"] || headers["content-type"] || "";
return "";
}
function setContentType(headers, value) {
if (!headers) return;
if (typeof headers.set === "function") headers.set("Content-Type", value);
else if (headers instanceof Object) headers["Content-Type"] = value;
}
// 拦截 fetch
const origFetch = window.fetch.bind(window);
window.fetch = function (input, init = {}) {
const url = typeof input === "string" ? input : input && input.url || "";
const method = (init && init.method) || (input instanceof Request ? input.method : "POST");
if (ENABLED && DEVICE_CONFIG && isTarget(url) && /^post$/i.test(method || "POST")) {
const ct = getContentType(init.headers);
const useJson = /json/i.test(ct) || looksJson(init.body);
const res = DeviceInject.injectBody(
init.body,
useJson ? "application/json" : "application/x-www-form-urlencoded",
DEVICE_CONFIG
);
console.log("[NodeLoc] 已拦截 fetch 发帖请求,注入机型:", DEVICE_CONFIG);
const newInit = { ...init, body: res.body };
setContentType(newInit.headers || (newInit.headers = new Headers()), res.contentType);
if (input instanceof Request) input = new Request(input, newInit);
else init = newInit;
}
return origFetch(input, init);
};
// 拦截 XMLHttpRequest
const origXHROpen = XMLHttpRequest.prototype.open;
const origXHRSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function (method, url, ...rest) {
this.__nl_method = method;
this.__nl_url = url;
return origXHROpen.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function (body) {
if (ENABLED && DEVICE_CONFIG && isTarget(this.__nl_url) && /^post$/i.test(this.__nl_method || "POST")) {
const useJson = looksJson(body);
const res = DeviceInject.injectBody(
body,
useJson ? "application/json" : "application/x-www-form-urlencoded",
DEVICE_CONFIG
);
console.log("[NodeLoc] 已拦截 XHR 发帖请求,注入机型:", DEVICE_CONFIG);
if (res.contentType) {
try { this.setRequestHeader("Content-Type", res.contentType); } catch (e) {}
}
body = res.body;
}
return origXHRSend.call(this, body);
};
console.log("[NodeLoc] 机型标签伪造已加载,当前伪造机型:", CURRENT_MODEL);
})();

