// ==UserScript== // @name TradingView Alert Batch Creator // @namespace none // @version 1.1 // @description 在tradingview图表界面批量添加警报 // @author Elias // @grant GM_setValue // @grant GM_getValue // @match https://*.tradingview.com/chart* // @require https://scriptcat.org/lib/637/1.3.3/ajaxHooker.js // @icon https://img2.imgtp.com/2024/03/28/20nZF8pD.webp // ==/UserScript== (function () { 'use strict'; // 发起请求的必要参数 var log_username = GM_getValue('log_username', null); var build_time = GM_getValue('build_time', null); var maintenance_unset_reason = GM_getValue('maintenance_unset_reason', null) // 记录最后一次创建警报的请求 var last_request; // 设置过滤规则,只劫持到特定URL的POST请求 ajaxHooker.filter([ { url: 'https://pricealerts.tradingview.com/create_alert', // 只劫持到这个URL的请求 method: 'POST', // 只劫持POST请求 }, ]); // 获取相关参数 ajaxHooker.hook(request => { // 检查是否为创建警报的请求 if (request.url.includes('create_alert')) { const urlParams = new URLSearchParams(request.url.split('?')[1]); last_request = request; // 存储提取的参数,以便后续使用 GM_setValue('log_username', urlParams.get('log_username')); GM_setValue('build_time', urlParams.get('build_time')); GM_setValue('maintenance_unset_reason', urlParams.get('maintenance_unset_reason')); console.log(`Captured: ${urlParams.get('log_username')}, ${urlParams.get('build_time')}, ${urlParams.get('maintenance_unset_reason')}`); } }); // 创建价格警报 const createAlert = ({ exchange, crypto, price, resolution = "5", onComplete }) => { // 获取当前日期 const currentDate = new Date(); // 计算下个月的日期,Date对象会自动处理月份和年份的变化 currentDate.setMonth(currentDate.getMonth() + 1); // 将日期转换为ISO格式字符串,并截取前面的日期部分 const expiration = currentDate.toISOString(); const apiUrl = `https://pricealerts.tradingview.com/create_alert?log_username=${log_username}&build_time=${build_time}&maintenance_unset_reason=${maintenance_unset_reason}`; const symbolValue = `={"adjustment":"splits","currency-id":"XTVCUSDT","session":"regular","symbol":"${exchange}:${crypto}"}`; const payload = JSON.stringify({ "payload": { "symbol": symbolValue, "resolution": resolution, "message": `${crypto} 穿过(Crossing) ${price}`, "sound_file": "alert/fired", "sound_duration": 0, "popup": true, "expiration": expiration, "condition": { "type": "cross", "frequency": "on_first_fire", "series": [{ "type": "barset" }, { "type": "value", "value": price }] }, "auto_deactivate": true, "email": true, "sms_over_email": false, "mobile_push": true, "web_hook": null, "name": null, "active": true, "ignore_warnings": true, } }); fetch(apiUrl, { method: "POST", headers: { "content-type": "text/plain;charset=UTF-8", "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-site", "x-usenewauth": "true" }, body: payload, referrer: "https://cn.tradingview.com/", referrerPolicy: "origin-when-cross-origin", mode: "cors", credentials: "include" }) .then(response => response.json()) .then(data => { console.log(data); if (data.s !== 'ok') { // 如果状态不是"ok",则认为添加失败,提示用户 if (typeof onComplete === 'function') { onComplete(false, "添加警报失败,请检查是否需要更新参数或手动添加警报。"); } } else { // 如果状态是"ok",则认为添加成功 if (typeof onComplete === 'function') { onComplete(true, `${crypto} 警报添加成功`); } } }) .catch(error => { console.error('Error:', error); if (typeof onComplete === 'function') { onComplete(false, `请求错误:${error.message}`); } }); }; // 创建指标警报 const createAlertIndicator = ({ request, crypto, onComplete }) => { const data_json = JSON.parse(request.data) const sym = JSON.parse(data_json.payload["symbol"].substring(1)) const old_crypto = sym["symbol"] const data = request.data.replace(old_crypto,`BINANCE:${crypto}`) fetch(request.url, { method: request.method, headers: request.headers, body: data, referrer: "https://cn.tradingview.com/", referrerPolicy: "origin-when-cross-origin", mode: "cors", credentials: "include" }) .then(response => response.json()) .then(data => { console.log(data); if (data.s !== 'ok') { // 如果状态不是"ok",则认为添加失败,提示用户 if (typeof onComplete === 'function') { onComplete(false, "添加警报失败,请检查是否需要更新参数或手动添加警报。"); } } else { // 如果状态是"ok",则认为添加成功 if (typeof onComplete === 'function') { onComplete(true, `${crypto} 警报添加成功`); } } }) .catch(error => { console.error('Error:', error); if (typeof onComplete === 'function') { onComplete(false, `请求错误:${error.message}`); } }); } // 输入框数据处理 function processData(data, messageBox,) { // 尝试从存储中获取参数 log_username = GM_getValue('log_username', null); build_time = GM_getValue('build_time', null); maintenance_unset_reason = GM_getValue('maintenance_unset_reason', null); // 检查是否所有必要的参数都已经获取 if (!log_username || !build_time || !maintenance_unset_reason) { // 如果没有找到参数,则弹出提示 alert("请先手动添加一个警报以便脚本捕获必要的参数。"); return } // 参数正常,继续运行 const lines = data.split('\n'); lines.forEach(line => { const [crypto, price] = line.split(','); if (crypto && price) { console.log(`创建警报:${crypto} - ${price}`); createAlert({ exchange: "BINANCE", crypto: crypto.trim(), price: parseFloat(price), onComplete: (success, message) => { showMessage(messageBox, message, success); } }); } }); } // 处理指标类警报 function processData_indicator(data, messageBox) { // 检查是否存在前次警报请求 if(!last_request){ alert("请先手动添加一次 指标/策略 类警报,然后此脚本会批量复制您的 指标/策略 警报 到不同的crypto"); return } // 参数正常,继续运行 const lines = data.split('\n'); lines.forEach(line => { const crypto = line.trim(); if (crypto) { console.log(`创建指标类警报-${crypto}`); createAlertIndicator({ request: last_request, crypto: crypto, onComplete: (success, message) => { showMessage(messageBox, message, success); } }); } }); } // 样式类定义 const styles = ` .container { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 428px; height: 386px; padding: 20px; background-color: #fff; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); z-index: 10000; display: none; } .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .title { font-weight: bold; } .close-btn { background: none; border: none; cursor: pointer; font-size: 1.5rem; color: #333; } .tab-container { display: flex; justify-content: center; margin-bottom: 20px; } .tab { flex: 1; padding: 10px; border: none; background: none; cursor: pointer; outline: none; border-bottom: 3px solid transparent; } .tab.active { border-bottom: 3px solid blue; } .content { padding: 10px; height: calc(100% - 60px); overflow-y: auto; } .open-btn { position: fixed; right: 10px; bottom: 10px; z-index: 10001; background: linear-gradient(to right, rgba(102, 51, 153, 0.8), rgba(159, 122, 234, 0.8)); /* 半透明的紫色渐变背景 */ border: none; border-radius: 20px; /* 圆角 */ box-shadow: 0 2px 4px rgba(0,0,0,0.1); /* 柔和的阴影效果 */ color: white; cursor: pointer; font-size: 16px; padding: 10px 20px; text-align: center; text-decoration: none; display: inline-block; margin: 4px 2px; transition-duration: 0.4s; } .open-btn:hover { box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2), 0 6px 20px 0 rgba(0,0,0,0.19); /* 提升的鼠标悬停效果 */ } `; const styleSheet = document.createElement('style'); styleSheet.innerText = styles; document.head.appendChild(styleSheet); function createTabContent(id, placeholderText, onSubmit) { const contentWrapper = document.createElement('div'); contentWrapper.id = id; contentWrapper.classList.add('content'); contentWrapper.style.display = 'none'; // 默认隐藏 const textarea = document.createElement('textarea'); textarea.placeholder = placeholderText; textarea.style.width = '100%'; textarea.style.height = '150px'; // 设置较大的高度以容纳多行文本 textarea.style.marginBottom = '10px'; const submitButton = document.createElement('button'); submitButton.textContent = '提交'; submitButton.style.display = 'block'; submitButton.onclick = function () { onSubmit(textarea.value, messageBox); }; // 创建用于显示消息的区域 const messageBox = document.createElement('div'); messageBox.style.marginTop = '10px'; messageBox.style.color = 'green'; // 默认为绿色,表示操作成功 contentWrapper.appendChild(textarea); contentWrapper.appendChild(submitButton); contentWrapper.appendChild(messageBox); // 添加消息展示区域到内容包装器中 // 返回创建的包含文本域、提交按钮和消息展示区域的容器 return { contentWrapper, messageBox }; } // 创建UI元素 function createStyledUI() { // 创建容器 const container = document.createElement('div'); container.classList.add('container'); // 创建标题栏 const header = document.createElement('div'); header.classList.add('header'); const title = document.createElement('span'); title.classList.add('title'); title.textContent = '批量警报设置'; const closeBtn = document.createElement('button'); closeBtn.classList.add('close-btn'); closeBtn.textContent = '×'; closeBtn.onclick = () => container.style.display = 'none'; header.appendChild(title); header.appendChild(closeBtn); // 创建选项卡切换按钮 const tabContainer = document.createElement('div'); tabContainer.classList.add('tab-container'); const settingsTab = document.createElement('button'); settingsTab.classList.add('tab'); settingsTab.textContent = '价格警报'; const notificationsTab = document.createElement('button'); notificationsTab.classList.add('tab'); notificationsTab.textContent = '指标与策略警报'; tabContainer.appendChild(settingsTab); tabContainer.appendChild(notificationsTab); // 为每个选项卡创建内容和事件处理函数 const settingsTabContent = createTabContent('settings-content', '输入数据,格式为:\nSYMBOL,PRICE\n例如:\nBTCUSDT.P,71000', (data, messageBox) => { processData(data, messageBox); }); const notificationsTabContent = createTabContent('notifications-content', '输入数据,每行只包含一个商品代码\n注意:\n请确认在执行此项之前,进行过一次您希望进行复制的指标策略类警报的添加', (data, messageBox) => { processData_indicator(data, messageBox); }); // 切换选项卡的函数 function switchTab(tabName) { settingsTabContent.contentWrapper.style.display = 'none'; notificationsTabContent.contentWrapper.style.display = 'none'; settingsTab.classList.remove('active'); notificationsTab.classList.remove('active'); if (tabName === 'settings') { settingsTabContent.contentWrapper.style.display = 'block'; settingsTab.classList.add('active'); } else if (tabName === 'notifications') { notificationsTabContent.contentWrapper.style.display = 'block'; notificationsTab.classList.add('active'); } } // 绑定点击事件到设置和通知按钮 settingsTab.onclick = () => switchTab('settings'); notificationsTab.onclick = () => switchTab('notifications'); // 将元素添加到容器 container.appendChild(header); container.appendChild(tabContainer); container.appendChild(settingsTabContent.contentWrapper); container.appendChild(notificationsTabContent.contentWrapper); // 将容器添加到文档中 document.body.appendChild(container); // 初始化默认选项卡 switchTab('settings'); // 暴露容器供外部使用(例如,打开按钮) return { container, settingsMessageBox: settingsTabContent.messageBox, notificationsMessageBox: notificationsTabContent.messageBox }; } function showMessage(messageBox, message, success) { messageBox.textContent = message; messageBox.style.color = success ? 'green' : 'red'; // 成功为绿色,失败为红色 } const { container, settingsMessageBox, notificationsMessageBox } = createStyledUI(); // 创建打开按钮 const openButton = document.createElement('button'); openButton.classList.add('open-btn'); openButton.textContent = 'Batch Alerts'; openButton.onclick = function () { container.style.display = 'block'; }; document.body.appendChild(openButton); })();