给博客搞一个没什么软用的插件

相关代码已发布在Github
https://github.com/icecliffs/hexo-live-mouse

闲着没事逛,在逛v圈时发现了这么一个网站,这是一个可以显示部分虚拟主播月经经期的网站(有一点变态,不过本期主题不是这个,而是下图红色箭头所标的鼠标位置,感觉这个还挺好玩的,可以看看他人的鼠标显示在网页的哪个地方,变向视奸他人

image-20260202142718002

技术实现

我坦白了,这根本没有什么技术含量,本质上就是一个 WebSocket 交互,不过这也是我第一次用hexo框架写hexo插件,简单记录一下,首先需要准备一个 node 项目,然后写一个 index.js 脚本和一个后端脚本,这个后端脚本很简单,可以随便放在一个 serverless 上跑,这里需要用到 socket.io

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
"use strict";
const PORT = process.env.PORT || 3000;
const MAX_CONNECTIONS = 2000;
const MAX_PER_IP = 50;
const MOVE_INTERVAL = 40;
const IDLE_TIMEOUT = 60000;
const connectionCount = new Map();
const lastMoveCache = new Map();
const lastActive = new Map();
const io = require("socket.io")(PORT, {
cors: {
origin: ["http://localhost", "https://iloli.moe"],
methods: ["GET", "POST"]
},
maxHttpBufferSize: 1024,
pingTimeout: 20000,
pingInterval: 25000
});
io.use((socket, next) => {
try {
const ip = socket.handshake.address || "unknown";
if (io.engine.clientsCount > MAX_CONNECTIONS) {
return next(new Error("server full"));
}
const count = connectionCount.get(ip) || 0;
if (count > MAX_PER_IP) {
return next(new Error("too many connections"));
}
connectionCount.set(ip, count + 1);
socket._ip = ip;
next();
} catch {
next(new Error("auth fail"));
}
});
io.on("connection", (socket) => {
console.log("握握手", socket.id);
lastActive.set(socket.id, Date.now());
socket.on("mouse_move", (data) => {
try {
const now = Date.now();
const lastMove = lastMoveCache.get(socket.id) || 0;
if (now - lastMove < MOVE_INTERVAL) return;
if (!data) return;
if (typeof data.x !== "number") return;
if (typeof data.y !== "number") return;
if (data.x < 0 || data.x > 1) return;
if (data.y < 0 || data.y > 1) return;
lastMoveCache.set(socket.id, now);
lastActive.set(socket.id, now);
socket.broadcast.volatile.emit("update_mouse", {
id: socket.id,
x: data.x,
y: data.y
});
} catch {}
});
socket.on("disconnect", () => {
try {
console.log("握握双手", socket.id);
lastMoveCache.delete(socket.id);
lastActive.delete(socket.id);
const ip = socket._ip;
if (ip && connectionCount.has(ip)) {
const c = connectionCount.get(ip) - 1;
if (c <= 0) connectionCount.delete(ip);
else connectionCount.set(ip, c);
}
io.emit("user_left", socket.id);
} catch {}
});
});
setInterval(() => {
const now = Date.now();
for (const [id, time] of lastActive.entries()) {
if (now - time > IDLE_TIMEOUT) {
const socket = io.sockets.sockets.get(id);
if (socket) socket.disconnect(true);
}
}
}, 30000);
console.log("hexo-live-mouse-secure:", PORT);

整个服务端就是人进来了,相互握握手,人出去了,握握双手,清理会话即可,然后做了一点安全限制,接着写一个客户端,这个就是纯 hexo 插件了,很好写,相关文档可以看官方文档

https://hexo.io/zh-cn/docs/plugins

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
"use strict";
const scriptContent = `
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
<script>
(function() {
const socket = io('http://127.0.0.1:3000', {
transports: ['websocket'],
reconnectionAttempts: 5,
timeout: 5000
});
const otherMice = new Map();
const MAX_USERS = 100;
document.addEventListener('mousemove', function(e) {
socket.emit('mouse_move', {
x: e.clientX / window.innerWidth,
y: e.clientY / window.innerHeight
});
});
socket.on('update_mouse', function(data) {
if (!data) return;
if (typeof data.x !== 'number' || typeof data.y !== 'number') return;
if (data.x < 0 || data.x > 1 || data.y < 0 || data.y > 1) return;
if (typeof data.id !== 'string' && typeof data.id !== 'number') return;
const safeId = String(data.id).slice(0, 64);
if (!otherMice.has(safeId) && otherMice.size > MAX_USERS) return;
let el = otherMice.get(safeId);
if (!el) {
el = document.createElement('div');
el.style.cssText = \`
width:20px;
height:20px;
position:fixed;
z-index:9999999;
pointer-events:none;
background-repeat:no-repeat;
background-size:contain;
\`;
el.style.backgroundImage = "url('data:image/svg+xml;utf8,\
<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\">\
<path fill=\\"black\\" d=\\"M3 2L3 22L8 17L12 22L15 20L11 15L20 15Z\\"/>\
</svg>')";
document.body.appendChild(el);
otherMice.set(safeId, el);
}
const x = data.x * window.innerWidth;
const y = data.y * window.innerHeight;
el.style.transform = \`translate3d(\${x}px, \${y}px, 0)\`;
el.style.left = '0';
el.style.top = '0';
});
socket.on('user_left', function(id) {
const safeId = String(id || '').slice(0, 64);
if (otherMice.has(safeId)) {
otherMice.get(safeId).remove();
otherMice.delete(safeId);
}
});
})();
</script>
`;
hexo.extend.injector.register('body_end', scriptContent, 'default');

最终效果

image-20260202150916596

安全问题

不知道