Last active
July 9, 2024 07:19
-
-
Save lan2720/cfd63569d4ea23179cbdf8153291325a to your computer and use it in GitHub Desktop.
油猴脚本 - 美签最新slot推送到飞书消息
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Push Feishu Message | |
// @namespace http://tampermonkey.net/ | |
// @version 2024-07-07 | |
// @description try to take over the world! | |
// @author jarvixwang | |
// @match *://portal.ustraveldocs.com/applicanthome* | |
// @match *://portal.ustraveldocs.com/appointmentcancellation* | |
// @icon https://www.google.com/s2/favicons?sz=64&domain=ustraveldocs.com | |
// @require https://code.jquery.com/jquery-3.7.1.min.js | |
// @require https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js | |
// @resource IMPORTED_CSS https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css | |
// @grant GM_notification | |
// @grant unsafeWindow | |
// @grant GM_getResourceText | |
// @grant GM_addStyle | |
// ==/UserScript== | |
// 美签预约网站的账号和密码 | |
const account_info = { | |
username: 'xxx', | |
password: 'yyy', | |
}; | |
// 飞书推送地址 | |
const WEBHOOK_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/<YOUR_OWN_ID>"; | |
const webhookNotify=(title, rows)=>{ | |
let data = {"msg_type": "interactive", | |
"card": { | |
"header": { | |
"title": { | |
"content": title, | |
"tag": "plain_text", | |
} | |
}, | |
"elements": rows | |
} | |
} | |
fetch(WEBHOOK_URL, { | |
method: "POST", | |
headers: {'Content-Type': 'application/json'}, | |
body: JSON.stringify(data) | |
}) | |
.then(response => { | |
// 处理响应 | |
if (!response.ok) { | |
throw new Error('Network response was not ok ' + response.statusText); | |
} | |
console.log('响应json: ', response.json()); // 将响应体转换为JSON | |
}) | |
.then(responseData => { | |
// 处理转换后的JSON数据 | |
console.log('成功发送消息,响应数据:', responseData); | |
}) | |
.catch(error => { | |
// 处理错误 | |
console.error('发送消息时发生错误:', error); | |
}); | |
} | |
const elemIds = {section: 'Unauthorized:SiteTemplate:siteLogin', | |
username: 'Unauthorized:SiteTemplate:siteLogin:loginComponent:loginForm:username', | |
password: 'Unauthorized:SiteTemplate:siteLogin:loginComponent:loginForm:password', | |
login_button: 'Unauthorized:SiteTemplate:siteLogin:loginComponent:loginForm:loginButton', | |
error_try_again_button: 'neterrorTryAgainButton', | |
} | |
const loginForMe=()=>{ | |
// 填写信息 | |
document.getElementById(elemIds.username).value = account_info.username; | |
document.getElementById(elemIds.password).value = account_info.password; | |
// 协议同意 | |
const checkbox = document.evaluate("//input[@type='checkbox']", | |
document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; | |
checkbox.click(); | |
// 点击登录按钮 | |
const button = document.getElementById(elemIds.login_button); | |
button.click(); | |
console.log("login successfully!"); | |
} | |
const checkUnauthorized=()=>{ | |
const element = document.getElementById(elemIds.section); | |
if (element) { | |
console.log("检测到未授权, 需要登录"); | |
loginForMe(); | |
} else { | |
console.log("无需重新登录"); | |
} | |
} | |
const checkNetError=()=>{ | |
const button = document.getElementById(elemIds.error_try_again_button); | |
if (button) { | |
console.log("找到了网络错误重试按钮"); | |
button.click(); | |
console.log("网络错误重试按钮被点击!"); | |
} else { | |
console.log("没有网络错误出现"); | |
} | |
} | |
const isChallengePopup=()=>{ | |
//var iframe = document.getElementById('cf-chk-widget-fgqjj'); | |
var div = document.querySelector('.cf-turnstile-wrapper'); | |
if (div) { | |
// 确保iframe已经加载完成,然后访问其内容 | |
setTimeout(function() { | |
// 获取iframe的文档对象 | |
var currentDocument = div.contentDocument || div.contentWindow.document; | |
// 使用querySelector查找第一个checkbox | |
var checkbox = currentDocument.querySelector('input[type="checkbox"]'); | |
checkbox.click(); | |
console.log("cloudfare challenge出现checkbox已点击"); | |
}, 1000); | |
} else { | |
console.log("没有人工验证出现"); | |
} | |
} | |
const selectEnglishLanguage=()=>{ | |
console.log("检查英语作为页面显示语言"); | |
const selectors = document.getElementsByTagName('select'); | |
let languageSelector = null; | |
for (let i = 0; i < selectors.length; i++) { | |
if (selectors[i].hasAttribute('onchange') && selectors[i].getAttribute('onchange').toString() === 'changeLanguage()') { | |
console.log("找到了语言选择下拉框"); | |
languageSelector = selectors[i]; | |
break; | |
} | |
} | |
//console.log(languageSelector); | |
if (languageSelector && languageSelector.value !== 'English') { | |
console.log("当前语言非英语,设置语言为英语"); | |
languageSelector.value = 'English'; // 语言不是英语时设置语言为英语 | |
languageSelector.dispatchEvent(new Event('change')); | |
} else { | |
console.log("当前语言是英语,无需操作"); | |
} | |
} | |
const parseDateFromLabel=(inputStr)=>{ | |
const regex = /(Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday)\s(January|February|March|April|May|June|July|August|September|October|November|December)\s\d{1,2},\s\d{4}/; | |
const match = inputStr.match(regex); | |
let resultDate = ""; | |
if (match) { | |
resultDate = match[0]; | |
} | |
return resultDate | |
} | |
const parseRowValue=(tr)=>{ | |
const tds = tr.querySelectorAll('td'); | |
const nextTd = tds[1]; | |
return nextTd.textContent.trim(); | |
} | |
const formatDateTime=(date, returnOnlyDate = true)=>{ | |
const padZero = (num) => String(num).padStart(2, '0'); | |
let dateStr = ( | |
padZero(date.getFullYear()) + '-' + | |
padZero(date.getMonth() + 1) + '-' + | |
padZero(date.getDate()) | |
); | |
if (!returnOnlyDate) { | |
dateStr += (' ' + | |
padZero(date.getHours()) + ':' + | |
padZero(date.getMinutes()) + ':' + | |
padZero(date.getSeconds()) | |
); | |
} | |
return dateStr; | |
} | |
const textFormatted=(text, color="default", isBold=false)=>{ | |
const fontBold = isBold ? "**" : ""; | |
let formatText = text; | |
if (isBold) { | |
formatText = fontBold + text + fontBold; | |
} | |
if (color != "default") { | |
formatText = "<font color='" + color + "'>" + formatText + "</font>"; | |
} | |
return formatText; | |
} | |
const getAppointmentDate=(givenAppointDate = null)=>{ | |
console.log("开始获取预约日期") | |
let consulate_name = ""; | |
let visa_class = ""; | |
let appointmentDateStr = ""; | |
let availableDateStr = ""; | |
const tbody = document.evaluate('//*[@id="j_id0:SiteTemplate:j_id120"]/table/tbody', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; | |
if (tbody === null) { | |
console.log("当前未检测到tbody, 退出, 等待下次检测"); | |
return; | |
} | |
const trs = tbody.querySelectorAll('tr'); | |
for (let i = 0; i < trs.length; i++) { | |
var trText = trs[i].textContent.toString().trim(); | |
if (trText.includes('U.S. Consulate General')) { | |
consulate_name = parseRowValue(trs[i]); | |
console.log("当前大使馆: ", consulate_name); | |
} else if (trText.includes('Visa Class')) { | |
visa_class = parseRowValue(trs[i]); | |
console.log("当前签证类型: ", visa_class); | |
} else if (trText.includes('Appointment Date')) { | |
appointmentDateStr = parseRowValue(trs[i]); | |
console.log("找到当前已预约日期: ", appointmentDateStr); | |
} else if (trText.includes('First available appointment slots')) { | |
const dateLabel = trText; | |
availableDateStr = parseDateFromLabel(dateLabel); | |
console.log("找到最新可预约日期: ", availableDateStr); | |
break; | |
} | |
} | |
console.log("已获取完预约日期"); | |
let appointmentDateObj = new Date("2024-01-01"); | |
if (givenAppointDate) { | |
appointmentDateObj = new Date(givenAppointDate); | |
} else { | |
appointmentDateObj = new Date(Date.parse(appointmentDateStr)); | |
} | |
const availableDateObj = new Date(Date.parse(availableDateStr)); | |
const hasEarlierSlot = availableDateObj < appointmentDateObj; | |
console.log("是否有更早的slot: " + hasEarlierSlot); | |
const title = hasEarlierSlot ? "有了!!!!!!!!!" : "还没有更早日期"; | |
let rows = [ | |
"**运行时间:** " + formatDateTime(new Date(), false), | |
"**大使馆:** " + consulate_name, | |
"**签证类型:** " + visa_class, | |
"**是否有更早的slot:** " + textFormatted(hasEarlierSlot, "red", hasEarlierSlot), | |
]; | |
if (hasEarlierSlot) { | |
rows.push("**最新可预约日期:** " + textFormatted(formatDateTime(availableDateObj), "red", true)); | |
rows.push("**已预约日期:** " + formatDateTime(appointmentDateObj)); | |
// 如果有更早的slot则艾特所有人 | |
rows.push("<at id=all></at>"); | |
} else { | |
rows.push("**已预约日期:** " + formatDateTime(appointmentDateObj)); | |
rows.push("**最新可预约日期:** " + textFormatted(formatDateTime(availableDateObj), "default", true)); | |
} | |
const content = rows.join("\n"); | |
console.log("发送消息:\n", content); | |
rows = [{"tag": "div", | |
"text": {"content": content, | |
"tag": "lark_md"}}] | |
return {"flag": hasEarlierSlot, | |
"title": title, | |
"rows": rows}; | |
} | |
const clickFromHome=()=>{ | |
const links = document.getElementsByTagName('a'); | |
for (let i = 0; i < links.length; i++) { | |
if (links[i].textContent === 'Cancel Appointment' && links[i].hasAttribute('onclick')) { | |
console.log("找到了cancel Appointment的按钮"); | |
links[i].click(); | |
break; | |
} | |
} | |
} | |
const refreshDate=()=>{ | |
console.log("开始刷新, " + new Date()); | |
clickFromHome(); | |
console.log("刷新完毕"); | |
} | |
(function() { | |
'use strict'; | |
const refreshMinutesGap = 3; | |
// 可以手动指定当前日期, 也可以为null则根据账号实际预约日期 | |
const givenAppointDate = "2024-08-30"; | |
setTimeout(function() { | |
// 检测是否有网络连接错误 | |
checkNetError(); | |
setTimeout(function() { | |
// 检测是否有人工验证 | |
isChallengePopup(); | |
setTimeout(function() { | |
// 检测是否需要重新登录 | |
checkUnauthorized(); | |
setTimeout(function() { | |
// 检测语言 | |
selectEnglishLanguage(); // Select English language | |
// 获取最新日期 | |
setTimeout(function() { | |
const result = getAppointmentDate(givenAppointDate); // Get appointment date | |
if (result) { | |
console.log("已从页面获取日期结果,即将发送飞书消息推送"); | |
webhookNotify(result.title, result.rows); | |
console.log("等待", refreshMinutesGap, "分钟后,页面将刷新进行下次检测"); | |
} | |
// 刷新 | |
setTimeout(refreshDate, refreshMinutesGap * 60 * 1000);// Refresh the page after waiting for several minutes | |
}, 5 * 1000); // Wait for 5 seconds after changing page language, then get data from page | |
}, 2 * 1000); | |
}, 2*1000); | |
}, 2*1000); | |
}, 6 * 1000); // Wait for next event loop iteration | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment