自动填写表单,完成GPT team/plus订阅
// ==UserScript==
// @name GPT Payment Tool
// @namespace http://tampermonkey.net/
// @version 1.0.0
// @description 自动填写表单,完成GPT team/plus订阅
// @author Community
// @match https://chatgpt.com/*
// @match https://pay.openai.com/*
// @match https://js.stripe.com/*
// @grant GM_setClipboard
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @run-at document-idle
// ==/UserScript==
(() => {
'use strict';
if (window.__gpt_toolkit__) return;
window.__gpt_toolkit__ = true;
GM_addStyle(`
.gpt-orb{position:fixed;top:100px;right:30px;z-index:999999;cursor:move;user-select:none}
.gpt-orb__sphere{width:48px;height:48px;background:#000;border-radius:50%;box-shadow:0 2px 8px rgba(0,0,0,.15);display:flex;align-items:center;justify-content:center;font-size:20px;cursor:pointer;transition:box-shadow .2s}
.gpt-orb__sphere:hover{box-shadow:0 3px 12px rgba(0,0,0,.25)}
.gpt-orb__panel{position:absolute;top:0;right:60px;background:#fff;border:1px solid #e0e0e0;border-radius:8px;box-shadow:0 4px 16px rgba(0,0,0,.08);padding:12px;width:180px;opacity:0;visibility:hidden;transform:translateX(8px);transition:all .2s}
.gpt-orb__panel--visible{opacity:1;visibility:visible;transform:translateX(0)}
.gpt-orb__header{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid #f0f0f0}
.gpt-orb__title{font:600 13px -apple-system,system-ui,sans-serif;color:#000}
.gpt-orb__actions{display:flex;gap:8px;align-items:center}
.gpt-orb__icon{cursor:pointer;opacity:.5;font-size:14px;transition:opacity .2s;padding:2px;color:#000}
.gpt-orb__icon:hover{opacity:1}
.gpt-orb__btn{width:100%;padding:8px;margin:4px 0;border:1px solid #d0d0d0;border-radius:6px;font:600 12px -apple-system,system-ui,sans-serif;cursor:pointer;transition:all .15s;color:#000;background:#fff}
.gpt-orb__btn:hover{background:#f5f5f5;border-color:#a0a0a0}
.gpt-orb__btn:active{background:#e8e8e8}
.gpt-orb__btn:disabled{opacity:.4;cursor:not-allowed;background:#fff}
.gpt-orb__msg{margin-top:8px;padding:8px;border-radius:4px;font-size:11px;text-align:center;border:1px solid}
.gpt-orb__msg--info{background:#fafafa;color:#666;border-color:#e0e0e0}
.gpt-orb__msg--ok{background:#f5f5f5;color:#333;border-color:#d0d0d0}
.gpt-orb__msg--err{background:#fff;color:#000;border-color:#999}
`);
const DB = {
read: k => GM_getValue(k, null),
write: (k, v) => GM_setValue(k, v)
};
const PRESET = {
cardNum: '5236860170307039',
expiry: '01/32',
cvv: '724',
holder: 'Danny Johey',
nation: 'US',
street: '540 North Water Avenue LOT 43',
town: 'Gallatin',
region: 'TN',
postal: '37066'
};
const SCHEMA = [
['卡号', 'cardNum'],
['有效期 (MM/YY)', 'expiry'],
['CVV', 'cvv'],
['持卡人姓名', 'holder'],
['国家代码', 'nation'],
['街道地址', 'street'],
['城市', 'town'],
['州/省', 'region'],
['邮编', 'postal']
];
const loc = location;
const pageType = {
stripeCard: loc.href.includes('elements-inner-payment'),
stripeAddr: loc.href.includes('elements-inner-address'),
payPortal: loc.hostname === 'pay.openai.com',
checkoutFlow: loc.hostname === 'chatgpt.com' && loc.href.includes('/checkout/'),
mainChat: loc.hostname === 'chatgpt.com' && !loc.href.includes('/checkout/')
};
const q = sel => document.querySelector(sel);
const qMulti = selectors => {
for (const s of selectors) {
const el = q(s);
if (el) return el;
}
return null;
};
const poll = (condition, interval = 50, maxAttempts = 60) => {
return new Promise(resolve => {
let attempts = 0;
const timer = setInterval(() => {
if (condition() || ++attempts >= maxAttempts) {
clearInterval(timer);
resolve();
}
}, interval);
});
};
const fillInput = (element, value) => {
if (!element || !value) return;
element.focus();
const desc = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
if (desc && desc.set) desc.set.call(element, value);
else element.value = value;
['input', 'change', 'blur'].forEach(evt => {
element.dispatchEvent(new Event(evt, { bubbles: true }));
});
};
const fillSelect = (element, value) => {
if (!element) return;
element.focus();
const desc = Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype, 'value');
if (desc && desc.set) desc.set.call(element, value);
else element.value = value;
element.dispatchEvent(new Event('change', { bubbles: true }));
};
const toggleCheck = element => {
if (element && !element.checked) {
element.click();
element.checked = true;
}
};
const clip = text => GM_setClipboard(text, 'text');
const fetchAuth = async () => {
const resp = await fetch('/api/auth/session');
if (resp.status === 403) throw new Error('访问被拒绝');
const json = await resp.json();
if (!json.accessToken) throw new Error('未登录');
return json.accessToken;
};
const fetchUserEmail = async () => {
try {
const resp = await fetch('/api/auth/session');
if (!resp.ok) return null;
const json = await resp.json();
return json?.user?.email || null;
} catch {
return null;
}
};
const createPlusCheckout = async () => {
const token = await fetchAuth();
const resp = await fetch('https://chatgpt.com/backend-api/payments/checkout', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
plan_type: 'plus',
checkout_ui_mode: 'hosted',
cancel_url: 'https://chatgpt.com/',
success_url: 'https://chatgpt.com/'
})
});
const json = await resp.json();
if (json.url) return json.url;
throw new Error(json.detail || '请求失败');
};
const createTeamCheckout = async () => {
const token = await fetchAuth();
const resp = await fetch('https://chatgpt.com/backend-api/payments/checkout', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
plan_name: 'chatgptteamplan',
team_plan_data: {
workspace_name: 'MyTeam',
price_interval: 'month',
seat_quantity: 5
},
promo_campaign: {
promo_campaign_id: 'team-1-month-free',
is_coupon_from_query_param: true
},
checkout_ui_mode: 'custom'
})
});
const json = await resp.json();
if (json.checkout_session_id) {
return `https://chatgpt.com/checkout/openai_llc/${json.checkout_session_id}`;
}
throw new Error(json.detail || '请求失败');
};
const populateStripeCard = cfg => {
fillInput(qMulti(['#payment-numberInput', '[name="number"]']), cfg.cardNum);
fillInput(qMulti(['#payment-expiryInput', '[name="expiry"]']), cfg.expiry);
fillInput(qMulti(['#payment-cvcInput', '[name="cvc"]']), cfg.cvv);
};
const populateStripeAddress = cfg => {
fillInput(qMulti(['#billingAddress-nameInput', '[name="name"]']), cfg.holder);
fillSelect(qMulti(['#billingAddress-countryInput', '[name="country"]']), cfg.nation);
fillInput(qMulti(['#billingAddress-addressLine1Input', '[name="addressLine1"]']), cfg.street);
fillInput(qMulti(['#billingAddress-localityInput', '[name="locality"]']), cfg.town);
fillSelect(qMulti(['#billingAddress-administrativeAreaInput', '[name="administrativeArea"]']), cfg.region);
fillInput(qMulti(['#billingAddress-postalCodeInput', '[name="postalCode"]']), cfg.postal);
};
const populatePaymentPortal = cfg => {
fillInput(q('#cardNumber'), cfg.cardNum);
fillInput(q('#cardExpiry'), cfg.expiry);
fillInput(q('#cardCvc'), cfg.cvv);
fillInput(q('#billingName'), cfg.holder);
fillSelect(q('#billingCountry'), cfg.nation);
fillInput(q('#billingAddressLine1'), cfg.street);
fillInput(q('#billingLocality'), cfg.town);
fillInput(q('#billingPostalCode'), cfg.postal);
fillSelect(q('#billingAdministrativeArea'), cfg.region);
toggleCheck(q('#termsOfServiceConsentCheckbox'));
};
const configureSettings = (isFirstTime = false) => {
const existing = { ...PRESET, ...(DB.read('settings') || {}) };
if (isFirstTime && DB.read('settings')) return existing;
if (isFirstTime) {
alert('⚙️ 首次使用,请配置支付信息');
}
const updated = {};
for (const [label, key] of SCHEMA) {
const input = prompt(`${label}:`, existing[key] || '');
if (input === null) return null;
updated[key] = input;
}
DB.write('settings', updated);
if (!isFirstTime) alert('✅ 设置已保存');
return { ...PRESET, ...updated };
};
let orbEl = null;
let panelEl = null;
let msgEl = null;
let isPanelVisible = false;
const notify = (text, type = 'info') => {
if (!msgEl) return;
msgEl.textContent = text;
msgEl.className = `gpt-orb__msg gpt-orb__msg--${type}`;
msgEl.style.display = 'block';
};
const togglePanel = () => {
isPanelVisible = !isPanelVisible;
if (panelEl) {
panelEl.classList.toggle('gpt-orb__panel--visible', isPanelVisible);
}
};
const makeDraggable = (element) => {
let isDragging = false;
let currentX;
let currentY;
let initialX;
let initialY;
let xOffset = 0;
let yOffset = 0;
const sphere = element.querySelector('.gpt-orb__sphere');
sphere.addEventListener('mousedown', dragStart);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', dragEnd);
sphere.addEventListener('touchstart', dragStart);
document.addEventListener('touchmove', drag);
document.addEventListener('touchend', dragEnd);
function dragStart(e) {
if (e.type === 'touchstart') {
initialX = e.touches[0].clientX - xOffset;
initialY = e.touches[0].clientY - yOffset;
} else {
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
}
if (e.target === sphere) {
isDragging = true;
}
}
function drag(e) {
if (isDragging) {
e.preventDefault();
if (e.type === 'touchmove') {
currentX = e.touches[0].clientX - initialX;
currentY = e.touches[0].clientY - initialY;
} else {
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
}
xOffset = currentX;
yOffset = currentY;
setTranslate(currentX, currentY, element);
}
}
function dragEnd(e) {
if (isDragging) {
initialX = currentX;
initialY = currentY;
isDragging = false;
}
}
function setTranslate(xPos, yPos, el) {
el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`;
}
};
const buildWidget = () => {
if (q('.gpt-orb')) return;
orbEl = document.createElement('div');
orbEl.className = 'gpt-orb';
let template = `
<div class="gpt-orb__sphere">💳</div>
<div class="gpt-orb__panel">
<div class="gpt-orb__header">
<span class="gpt-orb__title">支付工具</span>
<div class="gpt-orb__actions">
<span class="gpt-orb__icon" data-action="settings">⚙</span>
<span class="gpt-orb__icon" data-action="close">✕</span>
</div>
</div>
`;
if (pageType.mainChat) {
template += `
<button class="gpt-orb__btn" data-action="plus">Plus 订阅</button>
<button class="gpt-orb__btn" data-action="team">Team 订阅</button>
`;
}
template += `<div class="gpt-orb__msg" style="display:none"></div></div>`;
orbEl.innerHTML = template;
document.body.appendChild(orbEl);
panelEl = orbEl.querySelector('.gpt-orb__panel');
msgEl = orbEl.querySelector('.gpt-orb__msg');
const sphere = orbEl.querySelector('.gpt-orb__sphere');
makeDraggable(orbEl);
sphere.addEventListener('click', (e) => {
if (!e.defaultPrevented) {
togglePanel();
}
});
orbEl.querySelector('[data-action="close"]').onclick = () => {
togglePanel();
};
const settingsIcon = orbEl.querySelector('[data-action="settings"]');
if (settingsIcon) {
settingsIcon.onclick = () => configureSettings();
}
const plusBtn = orbEl.querySelector('[data-action="plus"]');
if (plusBtn) {
plusBtn.onclick = async function() {
this.textContent = '生成中...';
this.disabled = true;
try {
const link = await createPlusCheckout();
clip(link);
notify('✓ Plus 链接已复制', 'ok');
console.log('[工具箱] Plus:', link);
} catch (err) {
notify(`✕ ${err.message}`, 'err');
console.error('[工具箱] 错误:', err);
}
this.textContent = 'Plus 订阅';
this.disabled = false;
};
}
const teamBtn = orbEl.querySelector('[data-action="team"]');
if (teamBtn) {
teamBtn.onclick = async function() {
this.textContent = '生成中...';
this.disabled = true;
try {
const link = await createTeamCheckout();
clip(link);
notify('✓ Team 链接已复制', 'ok');
console.log('[工具箱] Team:', link);
} catch (err) {
notify(`✕ ${err.message}`, 'err');
console.error('[工具箱] 错误:', err);
}
this.textContent = 'Team 订阅';
this.disabled = false;
};
}
};
const execute = async () => {
const timestamp = Date.now();
if (pageType.stripeCard) {
await poll(() => qMulti(['#payment-numberInput', '[name="number"]']));
const settings = configureSettings(true);
if (settings) {
populateStripeCard(settings);
DB.write('completed', timestamp);
console.log('[工具箱] Stripe 卡片信息已填充');
}
} else if (pageType.stripeAddr) {
await poll(() => qMulti(['#billingAddress-nameInput', '[name="name"]']));
const settings = configureSettings(true);
if (settings) {
populateStripeAddress(settings);
DB.write('completed', timestamp);
console.log('[工具箱] Stripe 地址信息已填充');
}
} else if (pageType.payPortal) {
await poll(() => q('#cardNumber') || q('#email'));
buildWidget();
notify('正在填充...', 'info');
const cachedEmail = DB.read('userEmail');
if (cachedEmail) fillInput(q('#email'), cachedEmail);
const settings = configureSettings(true);
if (settings) {
populatePaymentPortal(settings);
notify('✓ 填充完成', 'ok');
console.log('[工具箱] 支付页面表单已填充');
} else {
notify('点击 ⚙ 配置', 'info');
}
} else if (pageType.checkoutFlow) {
await poll(() => document.body);
const email = await fetchUserEmail();
if (email) {
DB.write('userEmail', email);
console.log('[工具箱] 邮箱已缓存:', email);
}
await poll(() => q('#email'), 100, 100);
if (email) fillInput(q('#email'), email);
buildWidget();
if (DB.read('settings')) {
notify('填充中...', 'info');
} else {
notify('点击 ⚙ 配置', 'info');
}
const monitor = setInterval(() => {
if (DB.read('completed') > timestamp) {
clearInterval(monitor);
notify('✓ 填充完成', 'ok');
}
}, 200);
setTimeout(() => clearInterval(monitor), 30000);
} else if (pageType.mainChat) {
const email = await fetchUserEmail();
if (email) {
DB.write('userEmail', email);
console.log('[工具箱] 邮箱已缓存:', email);
}
setTimeout(buildWidget, 1000);
}
};
execute().catch(err => console.error('[工具箱] 初始化错误:', err));
})();