您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Control + Option/Alt + ArrowRight = next video. For other configuration, see the top of source codes.
// ==UserScript== // @name jirengu video hotkeys // @description Control + Option/Alt + ArrowRight = next video. For other configuration, see the top of source codes. // @author yeshiqing // @license MIT // @run-at document-idle // @match https://xiedaimala.com/tasks/* // @match https://jirengu.com/tasks/* // @grant none // @version 1.1.4 // @namespace https://github.com/yeshiqing/tampermonkey-scripts // @icon https://www.google.com/s2/favicons?sz=64&domain=tampermonkey.net // ==/UserScript== // ==用户可修改的配置项== const VOLUME_ADJUST_ARROWKEY_DOWN = 4 // 左右方向键调整视频快进快退的粒度,以秒为单位。 const AUTO_FULLSCREEN = true // 视频是否自动全屏 const AUTO_FULLSCREEN_MODE_WEBSITESCREEN = true // 自动全屏是否采用「网页全屏」模式。若为 false,则采用「全屏」模式 const VIDEO_AUTOPLAY = true // 视频是否自动播放 const SHOW_VIDEO_TITLE = true // 浏览器标签页的标题是否显示为视频标题 const F_KEYUP_SWITCH_WEBSITESCREEN = true // f keyup 事件是否触发切换「网页全屏」。若为 false 则事件触发时切换「全屏」。 const ESCAPE_KEYUP_PAUSE_VIDEO = true // esc keyup 事件触发退出全屏后,是否暂停播放。若为 false 则事件触发退出全屏后,保持原有播放状态。 const VIDEO_DBLCLICK_DISABLE = true // 是否禁用视频原有的鼠标双击事件,原有事件会切换「全屏」模式。当切换应用时会误触发双击事件,导致切换至「全屏」。 const VOLUMN_BTN_MOUSEOVER_DISABLE = true // 是否禁用音量调节按钮的 mouseover 事件。禁用 mouseover 会使得鼠标悬浮到音量键上方时不显示音量,这样做的好处是当想精细调节进度时,滑动鼠标到进度条开头部分,不会误触发音量调节。属于个人喜好,我一般用键盘的音量键调节音量。 const CUSTOM_EVENTS_CONFIG = [ { description: "Control + Option/Alt + ArrowRight = next video", key: "ArrowRight", // e.key eventType: "keydown", this: window, // 监听哪个对象的事件,默认为 window modifiers: ["ctrlKey", "altKey"], // 事件监听中 ctrlKey, altKey, metaKey, shiftKey 可作为修饰键 fn: "goToNextVideo" } ] // ==/用户可修改的配置项== // ==Config For Development== const DEBUG_EVENT_MODE = false // 是否开启事件调试模式 const EVENTS_DISABLE = [/*'mouseover'*/] // 禁用所有该类型的事件。 const CMD_KEYDOWN_DISABLE = false // 用于调试 const VIDEO_CLICK_DISABLE = false // 用于调试。不触发源代码中与 video click 事件相关的函数,因为按快捷键前需要点击一下聚焦 video。 const LOG_VIDEO_STATUS = false const F_KEYUP_HIJACK_AFTER = true // 是否在原有 f keyup 事件触发后加入 hook const ESCAPE_KEYUP_HIJACK_AFTER = true // 是否在原有 escape keyup 事件触发后加入 hook const HIJACK_EVENTS_CONFIG = { 'rawEvents': { 'keyup': [{ 'eventType': 'keyup', 'key': 'Escape', // event.key 'this': document, // 监听哪个对象的事件 'fn': null, // 在原有事件处理程序之前插入 hook 的函数名 'hijack': false, // 是否在原有事件处理程序之前插入 hook 'fnAfter': 'pauseVideo', // 在原有事件处理程序之后插入 hook 的函数名 'hijackAfter': ESCAPE_KEYUP_HIJACK_AFTER && ESCAPE_KEYUP_PAUSE_VIDEO, // 是否在原有事件处理程序之后插入 hook 'disable': false // 是否禁用原有事件处理程序 }, { 'eventType': 'keyup', 'key': 'f', 'this': document, 'fnAfter': 'switchWebsiteScreen', 'hijackAfter': F_KEYUP_HIJACK_AFTER && F_KEYUP_SWITCH_WEBSITESCREEN, 'disable': true }], 'keydown': [{ 'eventType': 'keydown', 'key': 'Meta', 'this': document, 'disable': CMD_KEYDOWN_DISABLE }, { 'eventType': 'keydown', 'key': 'ArrowRight', 'this': document, 'fnAfter': 'fastForward', 'hijackAfter': true, 'disable': true }, { 'eventType': 'keydown', 'key': 'ArrowLeft', 'this': document, 'fnAfter': 'fastRewind', 'hijackAfter': true, 'disable': true }], 'click': [{ 'eventType': 'click', 'this': 'video', 'disable': VIDEO_CLICK_DISABLE }], 'dblclick': [{ 'eventType': 'dblclick', 'this': 'video', 'disable': VIDEO_DBLCLICK_DISABLE }], 'mouseover': [{ 'eventType': 'mouseover', 'this': '.vjs-volume-panel', 'disable': VOLUMN_BTN_MOUSEOVER_DISABLE }] }, // 自定义。与 rawEvents 有不同数据结构 'videoEvents': { 'eventType': ['playing', 'play', 'waiting', 'pause', 'ended', 'loadedmetadata'], 'key': null, 'this': 'video', 'fn': 'setVideoStatus', 'hijack': true, 'disable': false }, } // ==/Config For Development== let $utils = { isFunction(obj) { return typeof obj === 'function' }, isBoolean(obj) { return typeof obj === 'boolean' }, isString(obj) { return typeof obj === 'string' }, isUndefined(obj) { return typeof obj === 'undefined' } } /** * 触发 hijack 和 hijackAfter 所绑定的事件 */ let $triggerHijackEvents = { _simulate_keyupEscape() { const event = new KeyboardEvent('keyup', { key: "Escape", view: window, bubbles: true, cancelable: true }) document.dispatchEvent(event) }, /** * 是否网页全屏 */ _isWebsiteScreen() { let elm = document.querySelector('.video-wrapper') return elm && elm.matches('.fullWindow') }, _getVideo() { return document.querySelector('.vjs-tech') || document.querySelector('video') }, /** * f keyup trigger 切换「网页全屏」 */ switchWebsiteScreen(event) { if (this._isWebsiteScreen()) { this._simulate_keyupEscape() } else { document.dispatchEvent(new CustomEvent("videoToggleFullWindow")) } }, /** * Escape keyup trigger */ pauseVideo(event) { if (this._isWebsiteScreen()) { let video = this._getVideo() video && video.pause() } }, /** * 'videoEvents' trigger */ setVideoStatus(event) { $hijackEventsHandler.video_status = event.type LOG_VIDEO_STATUS && console.log($hijackEventsHandler.video_status) }, /** * ArrowRight keydown trigger */ fastForward(event) { let video = this._getVideo() video && (video.currentTime = video.currentTime + VOLUME_ADJUST_ARROWKEY_DOWN) }, /** * ArrowLeft keydown trigger */ fastRewind(event) { let video = this._getVideo() video && (video.currentTime = video.currentTime - VOLUME_ADJUST_ARROWKEY_DOWN) } } let $hijackEventsHandler = { events: HIJACK_EVENTS_CONFIG, video_status: 'loadedmetadata', // loadedmetadata, playing, play, waiting, pause, ended trigger: $triggerHijackEvents, /** * 是否拦截原事件处理程序 * @param {object} event * @returns {boolean} */ isDisable(event) { let obj = $hijackEventsHandler.getEvent(event) if (!obj) { return false } let { disable } = obj return $utils.isBoolean(disable) ? disable : false // 默认 disable 为 false }, /** * 在原本事件之前触发的 hook */ triggerBefore(event) { let obj = $hijackEventsHandler.getEvent(event) if (!obj) { return } let { hijack } = obj, fn = null if (!$utils.isBoolean(hijack)) { hijack = false } // 默认 hijack 为 false hijack && $utils.isFunction(this.trigger[obj.fn]) && this.trigger[obj.fn](event) }, /** * 在原本事件之后触发的 hook */ triggerAfter(event) { let obj = $hijackEventsHandler.getEvent(event) if (!obj) { return } let { hijackAfter } = obj if (!$utils.isBoolean(hijackAfter)) { hijackAfter = false } // 默认 hijackAfter 为 false hijackAfter && $utils.isFunction(this.trigger[obj.fnAfter]) && this.trigger[obj.fnAfter](event) }, getThis(selector) { if (selector instanceof Object) { return selector } else if ($utils.isString(selector)) { return document.querySelector(selector) } else if ($utils.isUndefined(selector)) { console.warn(`add 'this' to 'EVENTS_CONFIG' or 'window' in default`) return window } throw new Error(`can't find element according to the selector: '${selector}'`,) }, /** * 获取 EVENTS_CONFIG 中的事件信息 */ getEvent(event) { let eventType = event.type // get from 'videoEvents' const videoEvents = $hijackEventsHandler.events.videoEvents const VIDEO_EVENTNAME = videoEvents.eventType if (VIDEO_EVENTNAME.includes(eventType) || event.currentTarget.tagName === 'video') { return videoEvents } // get from 'rawEvents' let arr = $hijackEventsHandler.events.rawEvents[eventType] || null if (arr) { let currentTarget = event.currentTarget let key = event.key let obj = arr.find((el, i) => { return $hijackEventsHandler.getThis(el['this']) === currentTarget && (el.key ? el.key === key : true) }) return obj || null } return null } } let $customEventsHandler = { trigger: { // control + option/alt + arrowRight trigger goToNextVideo() { let nodeList = document.querySelectorAll('.task-name') let span_nextTask = nodeList[nodeList.length - 1] span_nextTask && span_nextTask.click() } }, /** * 检测键盘按键是否匹配「触发自定义事件所需的快捷键」 */ checkKeycodes(e, config) { let { key, modifiers } = config if (e.key !== key) { return false } let trigger = true modifiers.forEach((modifier) => { (e[modifier] === false) && (trigger = false) }) return trigger } } let $init = { setDocumentTitleToVideoTitle() { if (!SHOW_VIDEO_TITLE) { return } let ele_title = document.querySelector('.video-title') || document.querySelector('j-panel-title h1') ele_title && (document.title = ele_title.textContent) }, videoAutoPlay() { if (!VIDEO_AUTOPLAY) { return } let ele_playButton = document.querySelector('.vjs-big-play-button') ele_playButton && ele_playButton.click() if (AUTO_FULLSCREEN) { if (AUTO_FULLSCREEN_MODE_WEBSITESCREEN) { let btn = document.querySelector('.video-js-fullwindow-button') btn && btn.click() return } let btn = document.querySelector('.vjs-fullscreen-control') btn && btn.click() } }, /** * control + option + arrowRight = next video */ createCustomHotkeys() { if (!Array.isArray(CUSTOM_EVENTS_CONFIG)) { return } CUSTOM_EVENTS_CONFIG.forEach((item) => { let { eventType, fn } = item let { checkKeycodes, trigger } = $customEventsHandler let eventTarget = item['this'] || window eventTarget.addEventListener(eventType, (e) => { if (checkKeycodes(e, item)) { $utils.isFunction(trigger[fn]) && trigger[fn]() } }) }) }, hijackOriginalHotkeys() { const OLD_ADD_EL = EventTarget.prototype.addEventListener EventTarget.prototype.addEventListener = function (eventType, fn, ...args) { if (EVENTS_DISABLE.includes(eventType)) { return } // worker inject error. `worker.addEventListener('message',fn)` if (this instanceof Worker) { OLD_ADD_EL.call(this, eventType, fn, ...args) return } OLD_ADD_EL.call(this, eventType, function foo(event) { if (DEBUG_EVENT_MODE && event.type === 'keyup' && event.key === 'Escape' /*&& event.currentTarget === document*/) { debugger } $hijackEventsHandler.triggerBefore(event) !$hijackEventsHandler.isDisable(event) && fn.call(this, event) $hijackEventsHandler.triggerAfter(event) }, ...args) } } } // init window.onload = () => { setTimeout(() => { // wait for xhr done $init.setDocumentTitleToVideoTitle() $init.videoAutoPlay() }, 2000) $init.createCustomHotkeys() $init.hijackOriginalHotkeys() }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址