您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
让DRRR.COM聊天室支持点歌功能
当前为
// ==UserScript== // @name DRRR点歌助手(drrr-music-helper) // @description 让DRRR.COM聊天室支持点歌功能 // @namespace Violentmonkey Scripts // @match https://drrr.com/* // @license MIT // @require https://cdn.staticfile.org/layer/3.5.1/layer.min.js // @resource layerCss https://cdn.staticfile.org/layer/3.5.1/theme/default/layer.min.css // @unwrap // @version 3.1.4 // @author QQ:121610059 // @update 2023-11-18 19:53:31 // @supportURL http://greasyfork.icu/zh-CN/scripts/414535-drrr-com%E6%99%BA%E8%83%BD%E8%84%9A%E6%9C%AC-%E8%87%AA%E5%8A%A8%E5%AF%B9%E8%AF%9D-%E8%87%AA%E5%8A%A8%E7%82%B9%E6%AD%8C // ==/UserScript== // 定义全局变量 let lastRequestedSong = null // 外部创建一个变量来存储上一首点的歌曲信息 const userSongTimestamps = {} // 外部创建一个对象来跟踪每个用户的点歌时间戳// 外部创建一个对象来跟踪每个用户的点歌时间戳 let userInteracted = false; // 添加一个变量来跟踪用户是否已经与网页交互 // 添加事件监听器来检测用户的互动行为 document.addEventListener('click', function() { userInteracted = true; }); // 检测本地存储是否已经存在songList if (!localStorage.getItem('songList')) { // 如果不存在,将数组中的数据设置为本地存储的songList localStorage.setItem('songList', JSON.stringify([ '极乐净土', 'aLIEZ', 'only my railgun', '恋爱循环', '打上花火', '我的战争', '菲克瑟先生', 'Lost in Paradise', 'Under the tree', 'Last stardust', '青鸟', '直到世界尽头' ])); } // 从本地存储中获取songList let songList = JSON.parse(localStorage.getItem('songList')); // 创建一个消息类型到处理函数的映射 const typeToHandler = { 'join': handleJoin, 'leave': handleLeave, 'message': handleMessage, 'music': handleMusic } // 添加全局事件监听器 $(document).ready(function () { // 初始化逻辑 initialize() // 监听音乐播放状态 musicPlaybackStatus() // 监控talks子元素数量不超过50,并以倒序方式移除超过指定数量的子元素。 monitorTalksElement() // 添加 AJAX 请求监听器 setupAjaxListeners() }) // 初始化函数 function initialize() { // 创建并添加样式表 createAndAppendStylesheet() // 当自动放歌功能打开时页面加载成功提示确认放歌 showConfirmationDialog() // 创建设置按钮 createAndAppendSettingsButton() // 设置按钮点击事件 settingsButtonClick() // 添加自动发送面板 createAndAppendAutoSendToPanel() // 添加欢迎加入面板 createAndAppendWelcomePanel() } // 添加欢迎加入面板 function createAndAppendWelcomePanel() { $(".nav-tabs").append(` <li role="presentation" id="settings-user-tab"> <a href="#settings-join-welcome" aria-controls="settings-user" role="tab" data-toggle="tab">加入欢迎</a> </li> `) $(".tab-content").append(` <div role="tabpanel" class="tab-pane" id="settings-join-welcome"> <div class="checkbox">加入欢迎开关</div> <div class="checkbox"> <label> <input type="checkbox" id="welcome_to_the_switch"> 开关 </label> </div> <div class="form-group"> <label for="welcome_to_the_content">加入欢迎内容</label> <div class="input-group" style="display: flex;"> <input type="text" class="form-control" id="welcome_to_the_content" name="welcome_to_the_content" placeholder="加入欢迎内容" value="{username} 欢迎加入房间!" style="border-right-width: revert;border-radius: 6px;"> </div> </div> </div> `) const checkboxId = 'welcome_to_the_switch' const checkbox = document.getElementById(checkboxId) // 尝试从 localStorage 中获取保存的状态 checkbox.checked = localStorage.getItem(`${checkboxId}_state`) === 'true'; checkbox.addEventListener('change', function() { // 存储 checkbox 的 checked 状态为字符串 localStorage.setItem(`${checkboxId}_state`, this.checked.toString()) }) } // 添加自动发送面板 function createAndAppendAutoSendToPanel() { $(".nav-tabs").append(` <li role="presentation" id="settings-user-tab"> <a href="#settings-auto-send" aria-controls="settings-user" role="tab" data-toggle="tab">定时发送</a> </li> `) $(".tab-content").append(` <div role="tabpanel" class="tab-pane" id="settings-auto-send"> <div class="checkbox">定时发送开关</div> <div class="checkbox"> <label> <input type="checkbox" id="timed_send_switch"> 开关 </label> </div> <div class="form-group"> <label for="scheduled_send_intervals">定时发送间隔(秒)</label> <div class="input-group" style="display: flex;"> <input type="number" min="30" oninput="if(value<30)value=30" class="form-control" id="scheduled_send_intervals" name="scheduled_send_intervals" placeholder="自动发送间隔(单位:秒)" value="300" style="border-right-width:revert;border-radius:6px;"> </div> </div> <div class="form-group"> <label for="scheduled_send_content">定时发送内容</label> <div class="input-group" style="display: flex;"> <input type="text" class="form-control" id="scheduled_send_content" name="scheduled_send_content" placeholder="自动发送内容" value="现在时间是{time}" style="border-right-width:revert;border-radius:6px;"> </div> </div> </div> `) // 定时发送代码 let intervalSendTimer = setInterval(intervalSendFn, Number(scheduled_send_intervals.value) * 1000) //获取当前日期函数 function getFormattedDateTime() { const now = new Date(); const formatNumber = (num) => String(num).padStart(2, '0'); const year = now.getFullYear(); const month = formatNumber(now.getMonth() + 1); const day = formatNumber(now.getDate()); const hours = formatNumber(now.getHours()); const minutes = formatNumber(now.getMinutes()); const seconds = formatNumber(now.getSeconds()); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; } function intervalSendFn() { clearInterval(intervalSendTimer) if(timed_send_switch.checked){ logMessage(`提示: 当前定时间隔为${Number(scheduled_send_intervals.value) }秒`,'success') sendMessage(scheduled_send_content.value.replace('{time}', getFormattedDateTime())) } intervalSendTimer = setInterval(intervalSendFn, Number(scheduled_send_intervals.value) * 1000) } const checkboxId = 'timed_send_switch' const checkbox = document.getElementById(checkboxId) // 尝试从 localStorage 中获取保存的状态 checkbox.checked = localStorage.getItem(`${checkboxId}_state`) === 'true'; checkbox.addEventListener('change', function() { // 存储 checkbox 的 checked 状态为字符串 localStorage.setItem(`${checkboxId}_state`, this.checked.toString()) }) } // 创建并添加样式表 function createAndAppendStylesheet() { // 创建一个link元素 const link = document.createElement('link') link.rel = 'stylesheet' link.type = 'text/css' link.href = 'https://fastly.jsdelivr.net/npm/[email protected]/dist/theme/default/layer.min.css' // 将link元素附加到文档的head中 document.head.appendChild(link) // 插入控制面板样式 // 创建一个<style>元素 var styleElement = document.createElement('style'); // 在<style>元素中添加你的CSS样式 styleElement.innerHTML = `body.stop-scrolling{overflow:auto!important;height:auto!important;}.sweet-overlay,.sweet-alert{display: none!important;}#tip{color: #000;}.settingContainer{width:100%;height:100%;background-color:rgb(77,189,60);display:flex;flex-direction:column;align-items:center;justify-content:center;padding:1rem;box-sizing:border-box}.settingContainer button{background-color:#fff;border-radius:5px;border:1px solid #000;color:#000}.settingTiele{color:#fff;font-weight:bold;font-size:2rem;margin-bottom:1rem}.randomSongListsTextarea{display:flex;width:100%;justify-content:center}.randomSongListsTextarea textarea{width:100%}.panel{margin-top:1rem;display:flex;align-items:center;justify-content:space-between;width:100%}.panel .right{height:100%;color:#fff;display:flex;justify-content:center;align-items:center;padding:0 1rem;border:1px solid #fff;border-radius:5px;flex-direction:column-reverse}.panel .right .checkBox{margin:0 0.2rem;display:flex;align-items:center}.panel .right .checkBox label{margin-bottom:0}textarea{border:none;outline:none;padding:0;margin:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-image:none;background-color:transparent;font-size:inherit;width:100%}textarea:focus{outline:none}.textarea{display:inline-block;resize:vertical;padding:5px 15px;line-height:1.5;box-sizing:border-box;color:#606266;background-color:#fff;border:1px solid #dcdfe6;border-radius:4px;transition:border-color 0.2s cubic-bezier(0.645,0.045,0.355,1)}.textarea::placeholder{color:#c0c4cc}.textarea:hover{border-color:#c0c4cc}.textarea:focus{border-color:#3677f0}.savebtn{color:#0099CC;background:transparent;border:2px solid #0099CC;border-radius:6px;border:none;color:white;padding:16px 32px;text-align:center;display:inline-block;font-size:16px;-webkit-transition-duration:0.4s;transition-duration:0.4s;cursor:pointer;text-decoration:none;text-transform:uppercase}.savebtn{background-color:white;color:black;border:2px solid #008CBA}.savebtn:hover{background-color:#000;color:white;border:1px solid #fff}@media screen and (min-width:769px){textarea.textarea,.panel{width:50%}}`; // 将<style>元素添加到页面的<head>部分 document.head.appendChild(styleElement); } // 创建并添加设置按钮 function createAndAppendSettingsButton() { waitForElementToExist('#np',(element) => { element.style.position = 'relative' // 播放器设置相对定位 const spanElement = document.createElement('span') // 创建span标签插入 spanElement.style.position = 'absolute' spanElement.style.right = '0px' spanElement.style.color = '#fff' spanElement.style.padding = '0 10px' spanElement.style.backgroundColor = '#4dbd3c' spanElement.textContent = '脚本设置' spanElement.id = 'settings' element.appendChild(spanElement) }) } // 封装函数,页面加载完成确认自动放歌对话框 function showConfirmationDialog() { // 等待自动播放的歌曲加载 setTimeout(() => { const autoChecked = localStorage.getItem("autoChecked") === "true"; const isAutoPlayEnabled = window.location.href.includes("room/?id") && autoChecked && Player.isPausing; // 判断是不是音乐房,只有在当前地址包含"room/?id"时才访问room.musicRoom if (isAutoPlayEnabled) { // 如果已经和与网页进行交互 if (userInteracted){ logMessage('网页交互状态检测成功','success') if (room.musicRoom) { // 显示确认对话框 let tip = layer.confirm('检测到自动点放歌功已开启,现在是否要播放随机歌曲?', { title: '提示信息', id: 'tip', btn: ['需要', '不需要'] // 按钮 }, function () { layer.msg('准备播放随机歌曲'); autoSongRequest(); // 开始自动播放 layer.close(tip); // 关闭提示框 }); } else { // 不是音乐房间,显示提示信息 layer.alert('当前不是音乐房间,无法点歌。', { shadeClose: true, id: 'tip', title: '提示信息' }); } }else{ logMessage('网页未进行交互,等待3秒后再检测','warning') // 如果没有和网页进行交互,则等待 3秒后再执行 setTimeout(function () { showConfirmationDialog(); }, 3000); } } }, 3000); } // 监控talks子元素数量,并以倒序方式移除超过指定数量的子元素。 function monitorTalksElement(maxElementCount = 50) { // 获取 #talks 元素 const talksElement = document.querySelector('#talks'); if (!talksElement) { return; // 如果找不到 #talks 元素,则退出函数 } // 创建 MutationObserver 对象以监听子元素变化 const observer = new MutationObserver(function (mutationsList) { for (const mutation of mutationsList) { if (mutation.type === 'childList') { const currentChildCount = talksElement.children.length; // 如果子元素数量超过指定的最大数量,倒序移除超出的元素 if (currentChildCount > maxElementCount) { const elementsToRemove = currentChildCount - maxElementCount; for (let i = 0; i < elementsToRemove; i++) { talksElement.removeChild(talksElement.lastElementChild); // 移除最后一个子元素 } } } } }); // 开始监听 #talks 元素的子元素变化 observer.observe(talksElement, { childList: true }); } // 封装函数,用于创建设置窗口 function createSettingsWindow() { layer.open({ title: '脚本设置', move: false, type: 1, offset: 'b', anim: 'slideUp', area: ['100%', '100%'], shade: 0.1, shadeClose: true, content: ` <div class="settingContainer"> <div class="settingTiele">设置随机歌单</div> <div class="randomSongListsTextarea"> <textarea class="textarea" rows="18"></textarea> </div> <div class="tips" style="margin-top: 1rem;color: #fff;">提示: 一行就是一首随机歌曲,自动放歌会从上面输入框中随机抽取一首歌进行搜索播放,修改后需保存!</div> <div class="panel"> <div class="left"> <button class="savebtn" id="save">保存歌单</button> </div> <a href="https://wpa.qq.com/msgrd?v=3&uin=1221610059&site=qq&menu=yes&jumpflag=1" target="_blank" style="text-decoration: none">反馈问题</a> <div class="right"> <div class="checkBox"><input name="checkbox" type="checkbox" id="auto" value="1"><label for="auto"> 自动放歌</label></div> <div class="checkBox"><input name="checkbox2" type="checkbox" id="song" value="1"><label for="song"> 自助点歌</label></div> </div> </div> </div>` }); // 返回设置窗口的 DOM 元素 return document.querySelector('.randomSongListsTextarea textarea'); } // 封装函数,用于监听保存按钮点击事件 function listenSaveButtonClick() { waitForElementToExist('#save', (element) => { element.addEventListener('click', () => { let songListTextarea = document.querySelector('.randomSongListsTextarea textarea'); const newSongList = songListTextarea.value.split('\n').filter(item => item !== ''); if (arraysAreEqual(newSongList, songList)) { logMessage('歌曲列表没有更改,无需保存', 'success'); layer.msg('歌曲列表没有更改,无需保存'); } else { localStorage.setItem('songList', JSON.stringify(newSongList)); songList = newSongList; logMessage('保存成功', 'success'); layer.msg('保存成功'); } }); }); } // 封装函数,用于监听复选框变化事件 function listenCheckboxChange(autoCheckbox, message, successMessage, warningMessage, callback) { autoCheckbox.addEventListener("change", function () { const isChecked = this.checked; // 获取复选框状态 localStorage.setItem(message, isChecked); if (this.checked) { layer.msg(successMessage); logMessage(successMessage, "success"); if (typeof callback === "function") { callback(); } // 在自助点歌功能开启时发送消息 if (message === "songChecked") { sendMessage('自助点歌功能已开启,自助点歌格式为:点歌+歌名 或者 播放 + 歌名'); } // 在自动放歌功能开启时触发提示并发送消息 if (message === "autoChecked" && isChecked) { layer.msg('自动放歌功能已开启'); logMessage("自动放歌功能已开启", "success"); sendMessage('自动放歌功能已开启'); } } else { layer.msg(warningMessage); logMessage(warningMessage, "warning"); // 在自助点歌功能关闭时发送消息 if (message === "songChecked") { sendMessage('自助点歌功能已关闭'); } // 在自动放歌功能关闭时触发提示并发送消息 if (message === "autoChecked" && !isChecked) { layer.msg('自动放歌功能已关闭'); logMessage("自动放歌功能已关闭", "warning"); sendMessage('自动放歌功能已关闭'); } } }); } // 设置按钮点击事件 function settingsButtonClick() { waitForElementToExist('#settings', (element) => { element.addEventListener('click', () => { const songListTextarea = createSettingsWindow(); // 拼接歌曲列表显示到文本框中 waitForElementToExist('.randomSongListsTextarea textarea', (element) => { element.value = songList.join('\n'); }); listenSaveButtonClick(); // 获取复选框元素 const autoCheckbox = document.getElementById("auto"); const songCheckbox = document.getElementById("song"); // 初始化本地存储值 const autoChecked = localStorage.getItem("autoChecked") === "true"; const songChecked = localStorage.getItem("songChecked") === "true"; // 根据本地存储值设置复选框状态 autoCheckbox.checked = autoChecked; songCheckbox.checked = songChecked; // 监听复选框变化事件 listenCheckboxChange( autoCheckbox, "autoChecked", "自动放歌功能已开启", "自动放歌功能已关闭", () => { if (Player.isPausing) { autoSongRequest(); } } ); listenCheckboxChange( songCheckbox, "songChecked", "自助点歌功能已开启", "自助点歌功能已关闭" ); }); }); // 注册键盘保存事件 document.addEventListener('keydown', function(event) { if (event.ctrlKey && (event.key === 's' || event.key === 'S')) { event.preventDefault(); const specifiedElement = document.querySelector('.randomSongListsTextarea textarea'); if (specifiedElement) { const save = document.querySelector('#save') save.click() } } }); } // 检查两个数组是否相等 function arraysAreEqual(arr1, arr2) { if (arr1.length !== arr2.length) { return false; } for (let i = 0; i < arr1.length; i++) { if (arr1[i] !== arr2[i]) { return false; } } return true; } // 打印带有不同样式的消息。 function logMessage(message, messageType) { let color = "black"; // 默认为黑色文本 // 根据消息类型设置颜色 if (messageType === "success") { color = "green"; // 成功消息显示为绿色 } else if (messageType === "error") { color = "red"; // 错误消息显示为红色 } else if (messageType === "warning") { color = "orange"; // 警告消息显示为橙色 } // 获取当前时间,并格式化为 [YYYY-M-D H:i:s] 格式 const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, "0"); // 月份是从 0 开始的,所以要加 1 const day = String(now.getDate()).padStart(2, "0"); const hours = String(now.getHours()).padStart(2, "0"); const minutes = String(now.getMinutes()).padStart(2, "0"); const seconds = String(now.getSeconds()).padStart(2, "0"); // 格式化时间 const formattedTimestamp = `[${year}-${month}-${day} ${hours}:${minutes}:${seconds}]`; // 组合时间戳和消息文本 const logText = `${formattedTimestamp} ${message}`; // 使用控制台打印带有样式的消息 console.log(`%c${logText}`, `color: ${color}; font-weight: bold;`); } // 监听音乐播放状态 function musicPlaybackStatus() { // 音乐开始播放回调 function onMusicStart(_this) { logMessage('音乐开始播放','success') _this.howl.seek(0) // 从头开始播放 } // 音乐播放结束回调 function onMusicEnd(_this) { logMessage('音乐播放结束', 'success'); // 检查本地存储值 autoChecked const autoChecked = localStorage.getItem("autoChecked") === "true"; if (autoChecked) { // 如果自动点歌功能已启用,执行自动点歌逻辑 autoSongRequest(); } else { // 如果自动点歌功能未启用,显示提示信息 logMessage("自助点歌功能未启用", 'warning'); } } // hook 音乐播放状态 MusicItem = function() { function e(n) { var r = this; _classCallCheck(this, e), this.music = n, console.log(n) this.name = DRRRClientBehavior.literalMusicTitle(n), this.url = n.playURL, this.schedule = null; var o = function() { r._unschedule_progress_update(100), visualizerEnabled && visualizer.stop(), Player.isPausing = !0, $(document).trigger("music-end", r) } , i = function() { r._unschedule_progress_update(100 * r.percent()) } , a = function() { r._schedule_progress_update() } , s = function() { r._unschedule_progress_update() }; const originalOnPlay = a; const originalOnStop = o; "apple_music" == n.source ? this.howl = new AppleMusicBackend(n,{ autoplay: !1, onend: () => { onMusicEnd(this); // 直接调用全局的 onMusicEnd 函数,并传递当前对象(this) originalOnStop(); // 执行原始的 onstop }, onpause: i, onplay: () => { onMusicStart(this); // 直接调用全局的 onMusicStart 函数,并传递当前对象(this) originalOnPlay(); // 执行原始的 onplay }, onstop: s, }) : this.howl = new Howl({ autoplay: !1, src: [this.url], html5: !0, volume: visualizerEnabled ? 1 : Player.volume, onload: function() { visualizerEnabled && visualizer.play(r._sounds[0]._node) }, onend: () => { onMusicEnd(this); // 直接调用全局的 onMusicEnd 函数,并传递当前对象(this) originalOnStop(); // 执行原始的 onstop }, onpause: i, onplay: () => { onMusicStart(this); // 直接调用全局的 onMusicStart 函数,并传递当前对象(this) originalOnPlay(); // 执行原始的 onplay }, onstop: s, onloaderror: function(e, n) { if (r._unschedule_progress_update(), "No audio support." != n && ("No codec support for selected audio sources." != n || -1 === r.url.indexOf(visualizerUrlPrefix))) { switch (n = n || "Unknown") { case 1: n = "fetching process aborted by user"; break; case 2: n = "error occurred when downloading"; break; case 3: n = "error occurred when decoding"; break; case 4: n = "URL is incorrect or audio/video not supported" } visualizerEnabled && visualizer.stop(), swal(t("Music: "), t("Audio cannot be loaded: {1}", r.name) + "\n\n" + t("Error: {1}", n), "warning") } }, onplayerror: function() { r.howl.once("unlock", function() { r.howl.play() }) } }) } return _createClass(e, [{ key: "volume", value: function(e) { this.howl.volume(e) } }, { key: "_schedule_progress_update", value: function() { var e = this; $(document).trigger("music-start", this), $(document).trigger("music-update-percent", 100 * this.percent()), this.schedule = setInterval(function() { $(document).trigger("music-update-percent", 100 * e.percent()) }, 950) } }, { key: "_unschedule_progress_update", value: function() { var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : 0; $(document).trigger("music-stop"), clearInterval(this.schedule), !1 !== e && $(document).trigger("music-update-percent", e) } }, { key: "now", value: function() { return this.howl.seek() } }, { key: "setTime", value: function(e) { var t = this , n = new Date; 0 == this.duration() ? this.howl.once("play", function() { var r = (new Date - n) / 1e3; t.howl.seek(e + r) }) : e <= this.duration() ? this.howl.seek(e) : this.howl.stop() } }, { key: "duration", value: function() { return this.howl.duration() } }, { key: "percent", value: function() { return this.now() / this.duration() } }, { key: "play", value: function() { $(document).trigger("music-play", this), Player.nowPlaying = this, this instanceof Howl && this.stopOthers(), this.howl.play(), Player.isPausing = !1, visualizerEnabled && visualizer.resume() } }, { key: "stopOthers", value: function() { var e = this; Player.playList.forEach(function(t) { t !== e && null != t && null != t.howl && t.stop() }) } }, { key: "pause", value: function() { this.howl.pause(), visualizerEnabled && visualizer.pause(), Player.isPausing = !0 } }, { key: "stop", value: function() { Player.isPausing = !0, this.howl.stop() } }, { key: "unload", value: function() { clearInterval(this.schedule), this.howl.unload() } }, { key: "previewOnly", get: function() { return "apple_music" == this.music.source && this.howl.previewOnly } }, { key: "time", get: function() { return this.now() } }, { key: "music_object", get: function() { return this.music } }]), e }(); } // 设置 AJAX 请求监听器 function setupAjaxListeners() { // 在每个 AJAX 请求成功完成后执行的拦截器 $(document).ajaxSuccess(function(event, xhr, settings) { // 检查请求的类型是否为 GET 且请求的 URL 包含 "update" if (settings.type === 'GET' && settings.url.includes('update')) { try { // 解析响应数据为 JSON 对象 const responseData = JSON.parse(xhr.responseText) // 检查是否有名为 "talks" 的属性,且它是一个数组且不为空 if (Array.isArray(responseData.talks) && responseData.talks.length > 0) { // 访问 "talks" 数组并遍历其中的元素 const talksArray = responseData.talks logMessage('请求最新消息完成。最新消息:','success') talksArray.forEach(talk => { // 根据 "type" 属性调用相应的处理函数 const handler = typeToHandler[talk.type] if (handler) { handler(talk) } else { logMessage(`未知类型: ${talk.type}`,'warning') } }) } } catch (error) { logMessage(`无法解析 JSON 响应数据: ${error}`, 'error' ) } } }) // 添加全局 AJAX 发送前事件监听器 $(document).ajaxSend(function(event, xhr, settings) { // 检查请求类型是否为 POST 且请求的 URL 包含 "ajax" if (settings.type === 'POST' && settings.url.includes('ajax')) { // 获取请求数据 const requestDatas = settings.data // 检查 requestData 是否包含 "message" if (requestDatas.includes('message')) { // 如果包含 "message",继续检查是否同时包含 "url" 和 "to" if (requestDatas.includes('url') && requestDatas.includes('to')) { logMessage('用户主动通过表单发送消息', 'success') } else { logMessage('脚本自动处理发送消息', 'success') let name = localStorage.username // 获取本地用户名 // 使用 URLSearchParams 来解析 const params = new URLSearchParams(requestDatas) // 获取键值对 const requestData = {} for (const [key, value] of params) { requestData[key] = value } // 插入本地消息 const talks = document.getElementById('talks') const icon = profile.icon const div = ` <dl class="talk ${icon}"> <dt class="dropdown user"> <div class="avatar avatar-${icon}"></div> <div class="name" data-toggle="dropdown"> <span class="select-text">${name}</span> </div> <ul class="dropdown-menu" role="menu"></ul> </dt> <dd> <div class="bubble"> <div class="tail-wrap center" style="background-size: 65px"> <div class="tail-mask"></div> </div> <p class="body select-text">${requestData.message}</p> </div> </dd> </dl> ` setTimeout(function(){ // 延迟1.5秒插入本地消息 talks.insertAdjacentHTML("afterbegin", div) }, 1500 ) } } } }) } // 定义一个函数,用于等待指定元素出现 function waitForElementToExist(selector, callback) { const interval = setInterval(() => { // 检查是否存在指定选择器的元素 const element = document.querySelector(selector) if (element) { clearInterval(interval) // 停止轮询 callback(element) // 调用回调函数并传递找到的元素 } }, 100) // 每100毫秒检查一次 } // 自动点歌逻辑 function autoSongRequest() { if (songList.length > 0) { // 随机选择一个歌曲 const randomIndex = Math.floor(Math.random() * songList.length) // 点歌选定的歌曲 const selectedSongName = songList[randomIndex] // 使用 getSongInfo 函数来获取歌曲的URL getSongInfo(selectedSongName, function(url, songName) { // 在成功回调函数中点歌 logMessage(`自动点歌: ${songName}`, 'success') sendMusicRequest(url, songName) }, function(errorMessage) { logMessage('自动点歌失败:', 'error') console.log(errorMessage) }) } else { logMessage('歌曲列表为空,无法自动点歌。', 'error') } } // 自定义处理点歌请求的函数 function handleSongRequest(songRequest, name, id) { // 当前不是音乐房间就返回提示信息 if (!room.music){ sendMessage('当前不是音乐房间,无法点歌。') logMessage('当前不是音乐房间,无法点歌。', 'error') return } // 检查当前这首点的歌曲是否与上一首不一样 if (lastRequestedSong === songRequest) { // 上一首点的歌曲与当前这首一样,触发错误提示 logMessage('上一首点的歌曲与当前这首相同,忽略点歌。', 'warning') // 发送消息提示用户 sendMessage('上一首点的歌曲与当前这首相同,请选择其他歌曲。') return } // 获取当前时间戳 const currentTime = Date.now() // 定义秒数限制,例如设置为 30 秒 const songRequestLimitInSeconds = 1 // 设置点歌频率的秒数限制 const songRequestLimit = songRequestLimitInSeconds * 1000 // 将秒数转换为毫秒 // 检查用户是否在指定秒数内重复点歌 if (userSongTimestamps[id] && currentTime - userSongTimestamps[id] < songRequestLimit) { // 用户在指定秒数内重复点歌,触发错误提示 logMessage('@' + name + ' 点歌的频率太高,请稍后再试。限制时间:' + songRequestLimitInSeconds + '秒','warning') // 发送消息提示用户 sendMessage('@' + name + ' 点歌的频率太高,请稍后再试。限制时间:' + songRequestLimitInSeconds + '秒') return } // 更新用户的点歌时间戳 userSongTimestamps[id] = currentTime // 更新上一首点的歌曲信息 lastRequestedSong = songRequest // 在这里执行你的点歌处理逻辑 logMessage(`收到点歌请求: ${songRequest}`, 'success') // 定义一个处理成功地回调函数 function onSuccess(url, songName) { logMessage(`成功获取歌曲 URL: ${url}`, 'success') logMessage(`歌曲名称: ${songName}`, 'success') // 在这里执行你的进一步操作,比如发送音乐请求 sendMusicRequest(url, songName) } // 定义一个处理失败的回调函数 function onError(errorMessage) { logMessage('获取歌曲 URL 失败:', 'error') console.log(errorMessage) // 在这里执行你的错误处理逻辑 sendMessage('获取歌曲 URL 失败, 请稍后再试。') } // 示发送请求获取歌曲信息 getSongInfo(songRequest, onSuccess, onError) } // 发送音乐消息 function sendMusicRequest(url, name) { // 创建请求的数据对象 const requestData = { music: 'music', url: url, name: name } // 发送 POST 请求 $.post('/room/?ajax=1', requestData, function(responseData) { if (!responseData) { // 响应为空,发送音乐成功 logMessage('发送音乐成功!','success') } else if (responseData === '慢一点,你发送得太快了!') { // 响应为 '慢一点,你发送得太快了!',等待2秒后重新发送 logMessage('发送音乐过快,等待3秒后重新发送...', 'warning') setTimeout(function() { sendMusicRequest(url, name) // 重新发送音乐请求 }, 3000) // 等待3秒 } }).fail(function(error) { // 处理请求失败的情况 logMessage('发送音乐请求失败:','error') console.log(error) }) } // 发送消息 function sendMessage(message) { // 创建包含消息数据的对象 let dataToSend = { message: message } // 发送 POST 请求 $.ajax({ type: "POST", url: "/room/?ajax=1", data: dataToSend, success: function(response) { // 请求成功的处理 if (response === "") { // 响应为空,表示发送消息成功 logMessage("消息发送成功!", 'success') } else { // 响应不为空,可能包含错误信息或其他内容 logMessage("消息发送失败,响应内容:",'error') console.log(response) } }, error: function(jqXHR, textStatus) { // 请求失败的处理 logMessage("消息发送失败,错误信息:",'error') console.log(textStatus) } }) } // 通过关键词获取音乐url function getSongInfo(keyword, successCallback, errorCallback) { if (keyword === "") return //关键词为空,不发送请求 // 第一次请求,获取歌曲信息数组 $.get(`https://api.533526.top/drrrAuto/music.php?action=search&keyword=${keyword}`, function(responseData) { // 解析响应数据为 JSON 格式 const responseJSON = JSON.parse(responseData) // 检查是否是数组以及是否有数据 if (Array.isArray(responseJSON) && responseJSON.length > 0) { // 获取数组中第一条数据的 rid 和歌曲信息 const firstSong = responseJSON[0] const firstSongRid = firstSong.rid // 构建歌曲名称(以name和artist拼接) const songName = `${firstSong.name} - ${firstSong.artist}` // 第二次请求,获取歌曲 URL $.get(`https://api.533526.top/drrrAuto/music.php?action=url&rid=${btoa(firstSongRid)}`, function(urlData) { // 检查是否成功获取歌曲 URL if (urlData) { urlData = replaceDomain(urlData) // 替换域名支持https successCallback(urlData, songName) // 调用成功回调函数并传递 URL 和歌曲名称 } else { errorCallback('无法获取歌曲 URL') // 调用错误回调函数 } }).fail(function() { // 请求第二次失败时,重新执行 getSongInfo 并带上参数 setTimeout(function(){ getSongInfo(keyword, successCallback, errorCallback); },5000) }) } else { logMessage('无法获取歌曲信息数组或数组为空','error') errorCallback('无法获取歌曲信息数组或数组为空') // 调用错误回调函数 sendMessage('未搜索到相关歌曲信息') // 给用户发送提示 } }).fail(function(error) { logMessage('获取歌曲信息数组失败:', 'error') console.log(error) errorCallback('获取歌曲信息数组失败') // 调用错误回调函数 // 请求第一次失败时,重新执行 getSongInfo 并带上参数 setTimeout(function(){ getSongInfo(keyword, successCallback, errorCallback); },5000) }) } // 替换酷我域名支持https function replaceDomain(url) { // 使用正则表达式替换规则 return url.replace(/http:\/\/([^\/]+)\.sycdn\.kuwo\.cn/g, function (match, p1) { let parts = p1.split('.') if (parts.length >= 3) { return 'https://' + parts.join('-') + '-sycdn.kuwo.cn' } else { return 'https://' + p1 + '-sycdn.kuwo.cn' } }) } // 处理加入类型的函数 function handleJoin(talk) { logMessage('处理加入:', 'success' ) console.log(talk) let lastUserName = talk.user.name // 在这里执行处理加入的操作 if (welcome_to_the_switch.checked){ sendMessage(welcome_to_the_content.value.replace('{username}', lastUserName)) } } // 处理离开类型的函数 function handleLeave(talk) { logMessage('处理离开:', 'success' ) console.log(talk) // 在这里执行处理离开的操作 } // 处理消息类型的函数 function handleMessage(talk) { const { from, message } = talk; // 解构获取属性 const { name, id, time } = from; // 解构获取属性 // 检查消息发送者是否与当前用户相同 if (name === localStorage.username) { logMessage('消息发送者与当前用户相同,不处理消息。', 'warning'); return; // 如果相同,则不继续处理消息 } logMessage('处理消息:', 'success'); console.log(talk); // 检查本地存储值 songChecked const songChecked = localStorage.getItem("songChecked") === "true"; // 如果 songChecked 为 true,则正常处理点歌操作 if (songChecked) { const trimmedMessage = message.trim(); // 检查消息内容是否以 "播放" 开头 if (trimmedMessage.startsWith("播放")) { // 提取播放后面的字符串 const songToPlay = trimmedMessage.substring("播放".length).trim(); // 执行处理播放请求的逻辑 if (songToPlay !== '') { handleSongRequest(songToPlay, name, id, time); return; // 继续执行后面的逻辑 } else { // 如果播放后面的字符串为空,返回错误消息 logMessage("播放歌曲不能为空", 'error'); } } else if (trimmedMessage.startsWith("点歌")) { // 正常处理点歌请求的逻辑 // 提取点歌后面的字符串 const songRequest = trimmedMessage.substring("点歌".length).trim(); // 执行你的自定义函数来处理点歌请求 if (songRequest !== '') { handleSongRequest(songRequest, name, id, time); } else { // songRequest 为空,返回错误消息 logMessage("点歌歌名不能为空", 'error'); } } } else { // 如果 songChecked 为 false,则打印提示信息 sendMessage('自助点歌功能未启用') logMessage("自助点歌功能未启用", 'warning'); } // 在这里执行处理消息的操作 } // 处理音乐类型的函数 async function handleMusic(talk) { console.log(talk); // 手机设备才执行播放歌曲 if (profile.device !== 'mobile') { logMessage('播放歌曲功能仅支持手机端', 'warning'); return; } if (!Player.isPausing) { return; } // 如果用户没有与页面交互,显示提示消息并等待用户互动 if (!userInteracted) { sendMessage('请管理员检查脚本的提示信息') logMessage('请与页面互动后才能播放音乐', 'warning'); await showAlert('请与页面互动后才能播放音乐'); userInteracted = true; // 用户确认后设置为 true } // 开始播放音乐 logMessage('开始播放音乐', 'success'); // 创建音乐项对象,传入音乐数据对象作为参数 const musicItem = new MusicItem(talk.music); // 播放音乐 musicItem.play(); } // 封装弹出提示的逻辑 function showAlert(message) { return new Promise((resolve) => { const tip = layer.alert(message, { shadeClose: true, id: 'tip', title: '提示信息' }, function() { resolve(); layer.close(tip); }); }); }