时隐重工

正在获取资源

(无法加载请尝试关闭浏览器插件)

欢迎

如果今天是周日,那就等6:00之后再来
FPS: --
时隐当前
远道而来的旅行者
空格键
Ctrl+M
Ctrl+F
播放暂停
音乐窗口
搜索窗口
遥感数据
建立 条信道
共记录 项观测数据 你是第 位到达这里的

使用桌面端以获得最佳体验

写在前面

首先我并不是个专业写代码的,本篇文章中99.9%的代码由ChatGPT完成,我只负责提供思路和整合

如果你扒F12看JS的时候发现了一些屎山一样的玩意请高抬贵手轻喷。

第一章 友链状态

这是比较简单的,如何显示友链是否在线。

在开始之前首先确保你已经搭好了UptimeKuma,然后创建了至少一个状态页面

比如 https://states.shiyin.cafe/status/all

在你拥有了这个页面之后便可以通过访问 https://states.shiyin.cafe/api/status-page/all

来获取纯文本版的状态页面

其中会包含这样的信息

        {
          "id": 1,
          "name": "时隐重工",
          "sendUrl": 0,
          "type": "http",
          "tags": [],
          "certExpiryDaysRemaining": 54,
          "validCert": true
        }

此时你只需要在前端js里根据这个列表里的"name"和"validCert"来简单的判断网站是否失效

"name"需要与网站友链的名称对应,不然可就找不到对应的元素了

然后你就可以像我一样在对应的友链卡片右下角添加一个小点来显示友链是否失效

以我的网站为例

document.querySelectorAll(".link-item").forEach(li => {
        const name = li.querySelector(".sitename")?.textContent.trim();
        if (!name) return;
        const monitor = monitors.find(m => m.name.trim() === name);
        li.classList.remove("no-status", "offline-filter", "online-filter", "warning-filter");
        const oldDot = li.querySelector(".status-dot");
        if (oldDot) oldDot.remove();
        if (!monitor) { li.classList.add("no-status"); return; }
        const dot = document.createElement("div"); dot.className = "status-dot"; li.appendChild(dot);
        if (!monitor.validCert) { dot.classList.add("offline"); li.classList.add("offline-filter"); offlineItems.push(li); }
        else if (monitor.certExpiryDaysRemaining !== "" && monitor.certExpiryDaysRemaining < 7) { dot.classList.add("warning"); li.classList.add("warning-filter"); }
        else { dot.classList.add("online"); li.classList.add("online-filter"); }
      });

".link-item"为所有友链名片的容器如果找不到对应的名字会直接return

如果有的话则会根据前面链接里获取到的状态来给卡片分配一个状态

是正常访问还是离线亦或者证书还有七天到期"certExpiryDaysRemaining"< 7

再然后你就可以再延伸一下,比如给下线的网站单独添加一个分组

if (offlineItems.length > 0) {
        let title = document.querySelector("h3 .group-title[data-nosignal]");
        let noSignalContainer = document.querySelector("ul.link-items.no-signal");
        if (!title) {
          title = document.createElement("h3");
          title.className = "link-title";
          title.innerHTML = `<span>无信号</span><span class="group-title" data-nosignal>NO SIGNAL</span>`;
          noSignalContainer = document.createElement("ul"); noSignalContainer.className = "link-items no-signal";
          const lastContainer = allContainers[allContainers.length - 1];
          lastContainer.insertAdjacentElement("afterend", noSignalContainer);
          noSignalContainer.insertAdjacentElement("beforebegin", title);
        }
        offlineItems.forEach(li => noSignalContainer.appendChild(li));
      }

解释一下就是如果失效友链大于0的话就创建一个”无信号“分组,然后把失效友链丢进去

那么至此你完成了友链状态的前端显示

第二章 个人状态

这个就比较复杂了,至少对我来说是绝对的超纲题了

在开始这一章之前我们要首先明白一件事

前端网页不仅可以从其他网页或者服务器获取信息

也可以被动的接受服务器的推送消息

这种可以双向推送信息的服务器一般称之为Websocket

说通俗易懂点的就是你跟你的好友在聊天的感觉

尽管也可以让前端网页不停的访问后端的某个文件来达到某种意义上的实时通讯

但比起Websocket来说性能还是太差劲了

且不说各种浏览器的优化,不停的请求后端服务器对请求数来说也是个不小的考验

OK,那么Websocket究竟都能做什么呢

说些比较常见的:

  1. 统计在线用户数

  2. 推送正在播放的音乐

  3. 显示用户正在看的页面

  4. 推送服务器负载

  5. 实时聊天

除此之外这玩意也是页游常用的技术

换句话说,只要能拿到信息你可以用Websocket把任何信息推送到网页中

关于如何搭建服务器可以参考这篇文章 2分钟搭建一个简单的WebSocket服务器 - 知乎

当你进行到 二、服务器的时候就可以停下了,因为我们需要的并不是个即时聊天服务器

我们需要推送的只是自己的状态,所以wss.on的部分就是这样

wss.on('connection', ws => {
  clients.add(ws);
  console.log('客户端连接:', ws._socket.remoteAddress);
  fs.readFile(jsonPath, 'utf8', (err, data) => {
    if (!err && data) {
      try {
        let payload = JSON.parse(data);
        payload.online_count = clients.size;

        ws.send(JSON.stringify(payload));
        console.log("已向新客户端推送初始状态");
      } catch (e) {
        console.error("初始 JSON 解析失败", e);
      }
    }
  });

  ws.on('close', () => {
    clients.delete(ws);
  });
});
const jsonPath = path.join(__dirname, 'current_window.json');

这其中current_window.json就是我想给前端网页推送的信息

里面的信息是

{
  "timestamp": 1763433130.387335,
  "window_title": "写天书",
  "current_song": "Once More - Gregory Esayan/武川アイ"
}

也就是时间戳和我正在做什么以及听什么音乐

每当有用户连接的时候就会立即推送这个格式的文本到前端

const watcher = chokidar.watch(jsonPath, { ignoreInitial: true, awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 100 } });
watcher.on('change', path => {
  fs.readFile(path, 'utf-8', (err, data) => {
    if (err) return console.error(err);
    if (!data) return;

    let payload;
    try {
      payload = JSON.parse(data);
    } catch (e) {
      console.error('JSON 解析失败:', e);
      return;
    }

    payload.online_count = clients.size;
    const sendData = JSON.stringify(payload);
    clients.forEach(ws => {
      if (ws.readyState === WebSocket.OPEN) {
        ws.send(sendData);
      }
    });

    console.log('已推送更新:', sendData);
  });
});

然后再加上这些,当json文件有更新时就立即推送一次,然后顺带把网页连接人数也附带进推送消息里

前端收到的格式就是这样

  1. current_song: "Once More - Gregory Esayan/武川アイ"

  2. online_count: 1

  3. timestamp: 1763433130.387335

  4. window_title: "写天书"

其中1是音乐,2是当前连接的人数,4是当前状态

拿到数据之后再怎么往网页里填就不多废话了

创建好元素,然后把数据扔进去就行了

举个简单的栗子

document.getElementById('title').textContent = window_title;
document.getElementById('count').textContent = online_count;
document.getElementById('states').textContent = states-title;
document.getElementById('song').textContent = current_song;

这样数据就会自动填充到网页对应的id=title和其它对应id的元素里了

然后你肯定会好奇,现在知道怎么填数据了

那数据从哪来呢

好问题!

此时你有两个选择

如果你是在自己电脑上搭建的websocket服务器

那么只需要写一个获取前台活动窗口id并生成json文件的脚本就可以了

这玩意随便问问AI它就帮你写了

比如

import json
import win32gui

def get_active_window_title():
    hwnd = win32gui.GetForegroundWindow()
    return win32gui.GetWindowText(hwnd)

title = get_active_window_title()

with open("window.json", "w", encoding="utf-8") as f:
    json.dump({"window_title": title}, f, ensure_ascii=False, indent=2)

print("已写入 window.json:", title)

他就会生成一个包含window_title的json文件,直接推送这个文件到前端就可以了

但因为这是个python脚本,所以你还得装个python

音乐也是同理

import json
import win32gui

def get_current_song():
    hwnd = win32gui.GetForegroundWindow()
    title = win32gui.GetWindowText(hwnd)
    if "网易云音乐" in title:
        return title.replace(" - 网易云音乐", "")
    return None

song_title = get_ncm_song_title()

with open("current_song.json", "w", encoding="utf-8") as f:
    json.dump({"current_song": current_song}, f, ensure_ascii=False, indent=2)

print("已写入 current_song.json:", current_song)

如果你正在播放Once More - Gregory Esayan/武川アイ就得到了一个内容为current_song: "Once More - Gregory Esayan/武川アイ"的json文件

你只需要把上面这两个脚本合并一下就能输出包含两个信息的json了,推送就好

但因为你在自己电脑上搭建服务器,所以当你关机的时候服务器也会停止运行

信息也就无法推送,而且也没办法知道网页中的活跃人数

况且你还得有个公网IP

所以此时你有了第二个选择

如果你是在线上服务器搭建的websocket

那这个就相对来说实用性更高

你只需要研究怎么把你电脑上生成的json文件上传到服务器以供推送就好了

其余的步骤根在自己电脑上搭建没区别

至于怎么上传,也举个简单的栗子

首先在服务器搭一个php环境

然后写一个支持json上传的php

<?php
// upload.php
$data = file_get_contents("php://input"); 
file_put_contents("/var/data/song.json", $data);
echo "OK";
?>

其中/var/data/song.json就是你想把文件保存到哪里的绝对路径

之后你只需要在上文中wss.on的最后const jsonPath = path.join(__dirname, 'current_window.json');

把路径改成文件所在的路径就好了,当然我的建议是直接把上传的文件和websocket的js文件放在一起就好,省时省力

完成这个步骤之后启动websocket服务器就可以在你关机的情况下依然推送在线人数了

当然因为你关机了所以你的状态也不会推送也没必要推送

后话

以上是websocket的最基础应用,我说的也只是一些皮毛而已

可能你就算照着操作了也不一定就能跑的通

我自己的代码要比我举例的复杂几十倍

上千行的代码量

其中包括了关机时自动生成一个离线状态的json文本

把生成json文件的脚本封装成exe文件供开机自启和后台运行

根据当前状态自动选择对应状态的图标显示在状态后面

根据当前状态自动生成一个对应的前缀文本

给歌曲标题添加打字机效果并处理没有音乐时的标题

当服务器连接失败时/服务器维护时显示的文本

更别提为了美观添加的逝量美化用代码

实际工作要比我写这篇文章举例的内容多太多

全部写出来的话这篇文章也会变得巨长

但正如我在控制台写的那句话

真正的力量来自于创造,而非模仿

尽管我写的代码99.9%都是用AI写的

但只要思想不滑坡,这种对我来说严重超纲的题也能完成不是吗

那么今天的分享就到这里

感谢观看,我们下篇文章再见