TradingView Alert Batch Creator
// ==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);
})();