// ==UserScript==
// @name Weread gamepad control
// @namespace Violentmonkey Scripts
// @match https://weread.qq.com/web/reader/*
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @version 1.2
// @author -
// @license GPL 3.0
// @description 使用xbox 游戏手柄控制微信读书翻页、滚动、全屏
// @update update v1.1 增加Y切换全屏功能
// @update update v1.2 增加LB RB 切换主题,X切换亮度功能
// ==/UserScript==
var gamepadAPI = {
job: 0,
update: function () {
// 清除按钮缓存
gamepadAPI.buttonsCache = [];
// 从上一帧中移动按钮状态到缓存中
for (var k = 0; k < gamepadAPI.buttonsStatus.length; k++) {
gamepadAPI.buttonsCache[k] = gamepadAPI.buttonsStatus[k];
}
// 清除按钮状态
gamepadAPI.buttonsStatus = [];
// 获取 gamepad 对象
var c = navigator.getGamepads()[0];
// 遍历按键,并将按下的按钮加到数组中
var pressed = [];
if (c.buttons) {
for (var b = 0, t = c.buttons.length; b < c.buttons.length; b++) {
if (c.buttons[b].pressed) {
// console.log("buttons pressed " + gamepadAPI.buttons[b])
pressed.push(gamepadAPI.buttons[b]);
}
}
}
// 遍历坐标值并加到数组中
var axes = [];
if (c.axes) {
for (var a = 0, x = c.axes.length; a < x; a++) {
if (Math.abs(c.axes[a]) > 0.7) {
console.log("axes pressed " + gamepadAPI.axes[a])
}
axes.push(c.axes[a].toFixed(2));
}
}
// 分配接收到的值
gamepadAPI.axesStatus = axes;
gamepadAPI.buttonsStatus = pressed;
// 返回按钮以便调试
return pressed;
},
buttonPressed: function (button, hold, released) {
var newPress = false;
var newRelease = false;
// 轮询按下的按钮
for (var i = 0, s = gamepadAPI.buttonsStatus.length; i < s; i++) {
// 如果我们找到我们想要的按钮
if (gamepadAPI.buttonsStatus[i] == button) {
// 设置布尔变量(newPress)为 true
newPress = true;
// 如果我们想检查按住还是单次按下
if (!hold) {
// 从上一帧轮询缓存状态
for (var j = 0, p = gamepadAPI.buttonsCache.length; j < p; j++) {
// 如果按钮(之前)已经被按下了则忽略新的按下状态
if (gamepadAPI.buttonsCache[j] == button) {
newPress = false;
}
}
}
}
}
// 检查是不是放开按钮
if (released) {
for (var j = 0, p = gamepadAPI.buttonsCache.length; j < p; j++) {
if (gamepadAPI.buttonsCache[j] == button) {
// 检查当前帧中按钮是否未被按下
if (!gamepadAPI.buttonsStatus.includes(button)) {
newRelease = true;
}
}
}
}
return { pressed: newPress, released: newRelease };
},
buttons: [
'A', 'B', 'X', 'Y',
'LB', 'RB', 'LT', 'RT',
'Option', 'Start',
'Axis-Left', 'Axis-Right',
'DPad-Up', 'DPad-Down', 'DPad-Left', 'DPad-Right',
],
axes: [
'Left H', 'Left V',
'Right H', 'Right V',
],
buttonsCache: [],
buttonsStatus: [],
axesStatus: [],
};
var themes = [
{ name: "白雪", value: ["#ffffff", "#000000"] },
{ name: "灰绿", value: ["#d8e7eb", "#000000"] },
{ name: "浅绿", value: ["#e9faff", "#000000"] },
{ name: "明黄", value: ["#ffffed", "#000000"] },
{ name: "淡绿", value: ["#eefaee", "#000000"] },
{ name: "草绿", value: ["#cce8cf", "#000000"] },
{ name: "红粉", value: ["#fcefff", "#000000"] },
{ name: "米黄", value: ["#f5f5dc", "#000000"] },
{ name: "茶色", value: ["#d2b48c", "#000000"] },
{ name: "银色", value: ["#c0c0c0", "#000000"] },
{ name: "浅黄", value: ["#f5f1e8", "#000000"] },
{ name: "浅灰", value: ["#d9e0e8", "#000000"] },
{ name: "午夜", value: ["#002b36", "#839496"] },
{ name: "墨水屏", value: ["#c0d3d7", "#111111"] },
{ name: "漆黑", value: ["#000000", "#555555"] },
];
/**
*
* @param {string} color hex color
* @param {number} factor 0-1
* @returns css color
*/
function darkenColor(color, factor) {
function hex2rgb(color) {
hex = color.replace('#', '');
let r = parseInt(hex.substr(0, 2), 16);
let g = parseInt(hex.substr(2, 2), 16);
let b = parseInt(hex.substr(4, 2), 16);
return [r, g, b];
}
function lab2rgb(lab) {
var y = (lab[0] + 16) / 116,
x = lab[1] / 500 + y,
z = y - lab[2] / 200,
r, g, b;
x = 0.95047 * ((x * x * x > 0.008856) ? x * x * x : (x - 16 / 116) / 7.787);
y = 1.00000 * ((y * y * y > 0.008856) ? y * y * y : (y - 16 / 116) / 7.787);
z = 1.08883 * ((z * z * z > 0.008856) ? z * z * z : (z - 16 / 116) / 7.787);
r = x * 3.2406 + y * -1.5372 + z * -0.4986;
g = x * -0.9689 + y * 1.8758 + z * 0.0415;
b = x * 0.0557 + y * -0.2040 + z * 1.0570;
r = (r > 0.0031308) ? (1.055 * Math.pow(r, 1 / 2.4) - 0.055) : 12.92 * r;
g = (g > 0.0031308) ? (1.055 * Math.pow(g, 1 / 2.4) - 0.055) : 12.92 * g;
b = (b > 0.0031308) ? (1.055 * Math.pow(b, 1 / 2.4) - 0.055) : 12.92 * b;
return [Math.max(0, Math.min(1, r)) * 255,
Math.max(0, Math.min(1, g)) * 255,
Math.max(0, Math.min(1, b)) * 255]
}
function rgb2lab(rgb) {
var r = rgb[0] / 255,
g = rgb[1] / 255,
b = rgb[2] / 255,
x, y, z;
r = (r > 0.04045) ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
g = (g > 0.04045) ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
b = (b > 0.04045) ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047;
y = (r * 0.2126 + g * 0.7152 + b * 0.0722) / 1.00000;
z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883;
x = (x > 0.008856) ? Math.pow(x, 1 / 3) : (7.787 * x) + 16 / 116;
y = (y > 0.008856) ? Math.pow(y, 1 / 3) : (7.787 * y) + 16 / 116;
z = (z > 0.008856) ? Math.pow(z, 1 / 3) : (7.787 * z) + 16 / 116;
return [(116 * y) - 16, 500 * (x - y), 200 * (y - z)]
}
let lab = rgb2lab(hex2rgb(color));
console.log("lab color", lab);
lab[0] = Math.max(0, lab[0] - lab[0] * factor);
let rgb = lab2rgb(lab);
console.log("rgb color", rgb);
return `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
}
let wechatControl = {
next: () => document.querySelector(".renderTarget_pager_button_right").click(),
prev: () => document.querySelector(".renderTarget_pager_button:not(.renderTarget_pager_button_right)").click(),
readerView: () => document.querySelector(".readerChapterContent"),
readerWrap: () => document.querySelector(".readerChapterContent_container"),
readerContent: () => document.querySelector(".wr_whiteTheme .readerChapterContent"),
topBar: () => document.querySelector(".readerTopBar"),
controls: () => document.querySelectorAll(".readerControls_item"),
/** @type {number} */
currentTheme: -1,
/** @type {Node | null} */
currentCss: null,
updateStyle: (direction) => {
let prevTheme = null
if (wechatControl.currentTheme != -1) {
prevTheme = themes[wechatControl.currentTheme]
}
wechatControl.currentTheme += direction
if (wechatControl.currentTheme >= themes.length) wechatControl.currentTheme = 0
let theme = themes[wechatControl.currentTheme].value
wechatControl.setTheme(theme)
wechatControl.saveTheme()
const renderFontColor = () => {
const resizeEvent = new Event('resize');
window.dispatchEvent(resizeEvent);
}
if (!prevTheme || prevTheme.value[1] != theme[1]) {
renderFontColor();
}
},
dim: 0,
darkMode: () => {
if (wechatControl.currentTheme == -1) {
wechatControl.currentTheme = 0
wechatControl.saveTheme()
}
if (wechatControl.dim == 0) {
wechatControl.dim = 0.3
} else {
wechatControl.dim = 0
}
wechatControl.updateStyle(0)
},
/**
* 设置主题
* @param {Array<string>} theme - 主题数组,包含背景色和字体颜色
*/
setTheme: (theme) => {
console.log("update theme")
console.log(theme[0], theme[1])
let color = theme[0]
const dim = wechatControl.dim
const backgroundColor = darkenColor(color, 0.1 + dim);
if (dim != 0) {
color = darkenColor(color, dim)
}
wechatControl.readerView().style.backgroundColor = color;
let control_items = wechatControl.controls()
for (let i = 0; i < control_items.length; i++) {
control_items[i].style.backgroundColor = color;
}
wechatControl.readerWrap().style.backgroundColor = backgroundColor;
wechatControl.topBar().style.backgroundColor = `#00000000`;
if (wechatControl.currentCss) {
wechatControl.currentCss.remove();
}
wechatControl.currentCss = GM_addStyle(`
.wr_whiteTheme .readerChapterContent {
color: ${theme[1]} !important;
}
` );
},
saveTheme: () => {
let theme = themes[wechatControl.currentTheme]
GM_setValue("theme", theme.name)
},
loadTheme: () => {
let themeName = GM_getValue("theme")
if (themeName) {
let theme
let index = 0
for (; index < themes.length; index++) {
if (themes[index].name == themeName) {
theme = themes[index]
break
}
}
wechatControl.currentTheme = index
wechatControl.setTheme(theme.value)
}
},
toggleFullScreen: () => {
if (!document.fullscreenElement && // 标准方法
!document.mozFullScreenElement && // Firefox
!document.webkitFullscreenElement && // Chrome, Safari and Opera
!document.msFullscreenElement) { // IE/Edge
// 请求全屏
if (document.documentElement.requestFullscreen) {
document.documentElement.requestFullscreen();
} else if (document.documentElement.mozRequestFullScreen) { // Firefox
document.documentElement.mozRequestFullScreen();
} else if (document.documentElement.webkitRequestFullscreen) { // Chrome, Safari and Opera
document.documentElement.webkitRequestFullscreen();
} else if (document.documentElement.msRequestFullscreen) { // IE/Edge
document.documentElement.msRequestFullscreen();
}
} else {
// 退出全屏
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) { // Firefox
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) { // Chrome, Safari and Opera
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) { // IE/Edge
document.msExitFullscreen();
}
}
},
scroll: (direction) => {
const hasVerticalScrollbar = document.documentElement.scrollHeight > document.documentElement.clientHeight;
if (hasVerticalScrollbar) {
window.scrollTo({
//60 fps
top: window.scrollY + ((300 / 60) * direction),
behavior: 'instant'
});
}
}
};
(function () {
'use strict';
wechatControl.loadTheme()
window.addEventListener("gamepadconnected", (evt) => {
console.log('控制器已连接。');
gamepadAPI.job = setInterval(() => {
gamepadAPI.update()
// 明确调用 buttonPressed 并传递按钮名称
if (gamepadAPI.buttonPressed('A').pressed) {
wechatControl.next()
}
// if (gamepadAPI.buttonPressed('A', true).pressed) {
// console.log('A 按钮被持续按下');
// }
// if (gamepadAPI.buttonPressed('A', false, true).released) {
// console.log('A 按钮被放开');
// }
if (gamepadAPI.buttonPressed('B').pressed) {
wechatControl.prev();
}
if (gamepadAPI.buttonPressed('Y').pressed) {
wechatControl.toggleFullScreen();
}
if (gamepadAPI.buttonPressed('X').pressed) {
wechatControl.darkMode()
}
if (gamepadAPI.buttonPressed('LB').pressed) {
wechatControl.updateStyle(-1)
}
if (gamepadAPI.buttonPressed('RB').pressed) {
wechatControl.updateStyle(1)
}
if (gamepadAPI.buttonPressed('DPad-Up').pressed) {
wechatControl.prev();
}
if (gamepadAPI.buttonPressed('DPad-Up', true).pressed) {
wechatControl.scroll(-1)
}
if (gamepadAPI.buttonPressed('DPad-Down').pressed) {
wechatControl.next();
}
if (gamepadAPI.buttonPressed('DPad-Down', true).pressed) {
wechatControl.scroll(1)
}
if (gamepadAPI.buttonPressed('DPad-Left').pressed) {
wechatControl.prev();
}
if (gamepadAPI.buttonPressed('DPad-Right').pressed) {
wechatControl.next();
}
}, 16)
});
window.addEventListener("gamepaddisconnected", (evt) => {
console.log('控制器已断开。');
clearInterval(gamepadAPI.job)
});
})();