// ==UserScript== // @name 携程商家辅助-大道店 // @namespace https://bbs.tampermonkey.net.cn/ // @version 0.0.4 // @description 轮询调价记录、显示房价房态日历隐藏的卖价等 // @author EmpyrealTear // @icon https://ebooking.ctrip.com/favicon.ico // @match *://ebooking.ctrip.com/* // @require https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js // require https://scriptcat.org/lib/513/2.1.0/ElementGetter.js const elmGetter = function () { const e = window.unsafeWindow || document.defaultView || window, t = e.document, r = new WeakMap; let n, o = "css"; const s = e.Element.prototype, c = s.matches || s.matchesSelector || s.webkitMatchesSelector || s.mozMatchesSelector || s.oMatchesSelector, u = e.MutationObserver || e.WebkitMutationObserver || e.MozMutationObserver; let l = 0, a = () => null; function i(e, t) { let n = r.get(e); n || (n = { filters: new Set, remove: function (e, t) { const r = new u(e => { for (const n of e) { if ("attributes" === n.type && (t(n.target, "attr"), r.canceled)) return; for (const e of n.addedNodes) if (e instanceof Element && t(e, "insert"), r.canceled) return } }); return r.canceled = !1, r.observe(e, { childList: !0, subtree: !0, attributes: !0 }), () => { r.canceled = !0, r.disconnect() } }(e, (e, t) => n.filters.forEach(r => r(e, t))) }, r.set(e, n)), n.filters.add(t) } function f(e, t) { const n = r.get(e); n && (n.filters.delete(t), n.filters.size || (n.remove(), r.delete(e))) } function d(e, t, r, o, s) { switch (o) { case "css": if ("attr" === s) return c.call(t, e) ? t : null; return t !== r && c.call(t, e) ? t : t.querySelector(e); case "jquery": { if ("attr" === s) return n(t).is(e) ? n(t) : null; const o = n(t !== r ? t : []).add([...t.querySelectorAll("*")]).filter(e); return o.length ? n(o.get(0)) : null } case "xpath": return e += "/self::*", (t.ownerDocument || t).evaluate(e, "attr" === s ? r : t, null, 9, null).singleNodeValue } } function h(e, t, r, o, s) { switch (o) { case "css": { if ("attr" === s) return c.call(t, e) ? [t] : []; const n = t !== r && c.call(t, e), o = t.querySelectorAll(e); return n ? [t, ...o] : [...o] } case "jquery": { if ("attr" === s) return n(t).is(e) ? [n(t)] : []; const o = n(t !== r ? t : []).add([...t.querySelectorAll("*")]).filter(e); return n.map(o, e => n(e)) } case "xpath": { e += "/self::*"; const n = (t.ownerDocument || t).evaluate(e, "attr" === s ? r : t, null, 7, null), o = []; for (let e = 0; e < n.snapshotLength; e++)o.push(n.snapshotItem(e)); return o } } } function y(e) { return e && e.fn && "string" == typeof e.fn.jquery } function m(e, t, r) { const n = o; return new Promise(o => { const s = d(e, t, t, n); if (s) return o(s); let c; const u = (r, s) => { const l = d(e, r, t, n, s); l && (f(t, u), c && clearTimeout(c), o(l)) }; i(t, u), r > 0 && (c = setTimeout(() => { f(t, u); const r = a(e); void 0 !== r && o(r) }, r)) }) } return { get currentSelector() { return o }, get(e, ...r) { let s = "number" != typeof r[0] && r.shift() || t; "jquery" === o && s instanceof n && (s = s.get(0)); const c = r[0] || l; return Array.isArray(e) ? Promise.all(e.map(e => m(e, s, c))) : m(e, s, c) }, each(e, ...r) { let s = "function" != typeof r[0] && r.shift() || t; "jquery" === o && s instanceof n && (s = s.get(0)); const c = r[0], u = o, l = new WeakSet; for (const t of h(e, s, s, u)) if (l.add("jquery" === u ? t.get(0) : t), !1 === c(t, !1)) return; const a = (t, r) => { for (const n of h(e, t, s, u, r)) { const e = "jquery" === u ? n.get(0) : n; if (l.has(e)) break; if (l.add(e), !1 === c(n, !0)) return f(s, a) } }; i(s, a) }, create(e, ...r) { const n = "boolean" == typeof r[0] && r.shift(), o = r[0], s = t.createElement("template"); s.innerHTML = e; const c = s.content.firstElementChild; if (!c) return null; if (o ? o.appendChild(c) : c.remove(), n) { const e = {}; return c.querySelectorAll("[id]").forEach(t => e[t.id] = t), e[0] = c, e } return c }, selector(t) { switch (!0) { case y(t): return n = t, o = "jquery"; case !t || "function" != typeof t.toLowerCase: return o = "css"; case "jquery" === t.toLowerCase(): for (const t of [window.jQuery, window.$, e.jQuery, e.$]) if (y(t)) { n = t; break } return o = n ? "jquery" : "css"; case "xpath" === t.toLowerCase(): return o = "xpath"; default: return o = "css" } }, onTimeout(...e) { l = "number" == typeof e[0] && e.shift() || l, a = e[0] || a } } }(); // @grant unsafeWindow // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_notification // ==/UserScript== (function () { 'use strict'; unsafeWindow.jQuery = $ const options = { menus: { isNotify: { toStr: (x) => '是否桌面通知:' + (x ? '✓' : '✕') }, isShowOriginalPrice: { toStr: (x) => '是否显示卖价:' + (x ? '✓' : '✕') }, }, loads: function () { Object.keys(this.menus).forEach(v => { let val = GM_getValue(v) this.menus[v]['_menu'] = GM_registerMenuCommand(this.menus[v].toStr(val), () => { GM_setValue(v, !val) Object.keys(this.menus).forEach(v => GM_unregisterMenuCommand(this.menus[v]['_menu'])) this.loads() }) }) } } options.loads() const ajax = { GET: (url, isAsync = false) => { let res = null $.ajax({ url: url, type: 'GET', xhrFields: { withCredentials: true }, dataType: 'json', async: isAsync, success: (e) => { res = e } }) return res }, POST: (url, data, isAsync = false) => { let res = null $.ajax({ url: url, type: 'POST', xhrFields: { withCredentials: true }, data: JSON.stringify(data), contentType: 'application/json', dataType: 'json', async: isAsync, success: (e) => { res = e } }) return res } } // 轮询检查最后一次调价记录,是否为携程调价助手自动调价 setInterval(() => { let response = ajax.POST( 'https://ebooking.ctrip.com/ebkovsroom/api/inventory/search/getroompricereq', { "roomIds": [], "changeDate": new Date().format('yyyy-MM-dd'), "submitMonth": new Date().format('yyyy-MM'), "submitor": "", "appStatus": "A", "reqId": -1 }) let data = response?.data?.reqList if (data && data.length > 0) { if (!/^EBK/.test(data[0].operater)) { let preReqId = GM_getValue('reqId') let reqId = data[0].reqId if (preReqId != reqId) { if (GM_getValue('isNotify')) GM_notification(`${data[0].source}于${data[0].operateDate}发起调价`, '调价提醒') GM_setValue('reqId', reqId) } } } }, 1000 * 60) const xhrOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function () { const xhr = this // 是否显示默认被隐藏的卖价 if (GM_getValue('isShowOriginalPrice') && /ebkovsroom\/api\/inventory\/roomsv2/.test(arguments[1])) { const setRoomPPPriceAuthority = (room, value = 'T') => { return { ...room, roomInfos: room.roomInfos.map(info => ({ ...info, // origin: https://ws-s.tripcdn.cn/modules/EBooking/ebkroom-resource/1.1.69/js/inventory/calendarv9.js // isHidePrice => ppPriceAuthority: T=显示卖价, F=隐藏卖价 ppPriceAuthority: 'T' })) } } const getter = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, "responseText").get Object.defineProperty(xhr, "responseText", { get: () => { let result = JSON.parse(getter.call(xhr)) result.data = result.data.map(rooms => { if (Object.prototype.toString.call(rooms) == '[object Array]') return rooms.map(room => setRoomPPPriceAuthority(room)) return setRoomPPPriceAuthority(rooms) }) return JSON.stringify(result) }, }) } return xhrOpen.apply(xhr, arguments) } })();