> For the complete documentation index, see [llms.txt](https://zhangbohun.gitbook.io/web-data-monitor/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://zhangbohun.gitbook.io/web-data-monitor/master.md).

# Web Data Monitor User Guide

## Introduction

Web Data Monitor： [Chrome 商店链接](https://chrome.google.com/webstore/detail/web-data-monitor/jicncdodkpdlndbppohjljcggoemckjp)\
这是一个自定义可编程的网络数据监控框架。\
扩展提供了基于 Javascript 编程语言以及 jQuery 工具库的支持让用户可以编程自定义监控及提醒逻辑，\
通过它用户可以监控任意网页或接口数据变更，实现如股价提醒，商品打折提醒，剧漫更新提醒，RSS更新提醒，直播通知，新邮件提，网站签到等等功能。

## Manifest V3 Version Update ！！！

最后关头，Web Data Monitor 终于支持了 Chrome Manifest V3

新版继续保留扩展的核心能力：配置网络请求、使用浏览器已有登录态、自定义 JavaScript 数据处理逻辑、自动监控数据变化、提醒通知，以及任务配置的导入导出

同时本次更新增加了以下能力：

* 更灵活的调度方式，支持固定间隔以及每天、每周、每月指定时间
* 更多提醒方式设置：无提醒、Toast 提示、通知提醒、通知+声音
* 支持任务级自定义提示音，也可以设置全局默认提示音（提醒方式 鼠标右击触发）
* Popup 列表中支持快捷启用/禁用任务
* 优化了原版丑陋的弹出窗口样式包括日志查看弹窗（任务Item右击触发）

但由于Manifest V3 版本 API 安全机制变化，在经过一系列努力尝试解决后新版还是有部分功能上的调整和限制：

* 低于 30 秒的自动检测周期会调整为 Chrome API 允许的最短 30 秒
* `Cookie`、`Origin`、`User-Agent`、`Sec-*` 等由浏览器控制的请求头不能再通过任务配置手动设置
* JSONP、`dataType: "script"` 以及部分高级 `jQuery.ajax` 配置不再作为新版支持范围
* 自定义处理脚本仍可使用，常见字符串处理、JSON 处理、DOM 查询和简单 AJAX 请求可以继续工作；但复杂 jQuery Deferred 行为或底层 XHR 自定义能力可能与旧版不同

如果你的任务主要基于普通 GET/POST 接口，并依赖浏览器已有登录状态，通常可以继续使用\
升级后建议先检查几个常用任务，并保留自动下载的 MV2 兼容备份 JSON，方便需要时回滚或留档

如果你的现有任务在 MV3 版本中无法正常工作，这里也还是提供了可以下载旧版 MV2 `.crx` 安装包：

[下载旧版 MV2 插件](https://pan.baidu.com/s/1Ri5gHspL7JUoEe4idVujrw?pwd=av2w)

保留升级时自动下载的 MV2 兼容备份 JSON回，退到旧版插件后可以用这份备份恢复原有任务配置

## Simple Document​​​

![添加任务界面](/files/-M4AAcp0Zi6f5Hd-CiAL)

1. ​任务名称：用于任务说明以及唯一标记
2. 相关页面：点击提醒时默认打开页面的url
3. 监测周期：调用网络请求周期
4. 允许弹窗提醒：弹窗提醒控制（优先级高于代码逻辑）
5. 请求参数：基础参数已列出，具体可参考 jQuery ajax请求参数 API 文档（如遇网站复杂请求参数可以通过Chrome浏览器开发者工具拿到curl再通过[工具网站](https://curlconverter.com/javascript-jquery/)转换获取）
6. 数据处理：相关代码说明如下

```javascript
//以下是几个预置变量说明
//queryResult  调用jQuery ajax请求返回的结果，格式根据请求参数可以是JSON或者HTML文本或者XML
//presetPageUrl 配置中的相关页面url
//isRefresh 是否是主动点击刷新标志位
//lastReturn 上一次处理的返回，具体可看最下return返回代码
 
var highlight = lastReturn.highlight;//是否高亮显示
var popup = lastReturn.popup;//是否弹框提醒
var url = presetPageUrl;//点击打开页面url
var content = lastReturn.content;//条目显示文本
var extra = lastReturn.extra;//额外数据
//以上部分为默认设置一般无需修改
 
//此处添加处理代码，根据queryResult和isRefresh确定highlight,popup,url,content,extra
//此处还可以添加额外的操作，如调用其他API实现邮件微信提醒等
 
//返回处理结果一般无需修改
//扩展根据返回结果进行高亮，弹窗，显示说明等操作
return {'highlight':highlight,'popup':popup,'url':url,'content':content,'extra':extra};
```

## Basic Usage​

### 各种更新提醒

```javascript
//queryResult
//presetPageUrl  isRefresh  lastReturn
var highlight = lastReturn.highlight;
var popup = lastReturn.popup;
var url = presetPageUrl;
var content = lastReturn.content;
var extra = lastReturn.extra;
 
//此处可以通过CSS选择器，正则或者读取JSON对象来定位获取更新后不相同的部分
//此示例基本可以实现类似Chrome扩展的所有功能
queryResult = $(queryResult).find('#video_list_li a:last').text();//1 
 
content = '更新到了 ' + queryResult;
if (content != lastReturn.content) {
    highlight = true;
    popup = true;
}
 
 
return {'highlight':highlight,'popup':popup,'url':url,'content':content,'extra':extra};
```

{% file src="/files/-M4EOcjlVu7aYnSDMVtw" %}

{% file src="/files/-M4AQzv\_cIbe9mZufmPd" %}

{% file src="/files/-M4AQrMxFw764KhiS4\_9" %}

### 邮件提醒示例

```javascript
//queryResult
//presetPageUrl  isRefresh  lastReturn
var highlight = lastReturn.highlight;
var popup = lastReturn.popup;
var url = presetPageUrl;
var content = lastReturn.content;
var extra = lastReturn.extra;
 

if (queryResult.indexOf('unreadMessageCount') == -1) {//1 此处为判断登陆的代码，不同邮箱不同
    highlight = true;
    popup = content != '登录失效，请重新登录！';
    content = '登录失效，请重新登录！';
} else {
    //2 此处为用于获取邮件数量的代码
    var count = Number(new RegExp('folderStats.*?\'unreadMessageCount\':(.*?),', 'ig').exec(queryResult)[1]); 
    extra = count;
    var lastCount = lastReturn.extra;
 
    content = count == 0 ? '暂无新邮件' : '收到 ' + count + ' 封新邮件';
    if (count == 0) {
        highlight = false;
        popup = false;
    }
    else if (count > lastCount) {
        highlight = true;
        popup = true;
    } else if (count > 0 && isRefresh) {
        highlight = true;
        popup = false;
    } else if (count < lastCount) {
        popup = false;
    }
}
 
 
return {'highlight':highlight,'popup':popup,'url':url,'content':content,'extra':extra};
```

{% file src="/files/-M4AQrMv-Z2ghKd8qQvd" %}

{% file src="/files/-M4E\_PeTpRP8wQk2VrQB" %}

### 直播提醒示例

```javascript
//queryResult
//presetPageUrl  isRefresh  lastReturn
var highlight = lastReturn.highlight;
var popup = lastReturn.popup;
var url = presetPageUrl;
var content = lastReturn.content;
var extra = lastReturn.extra;


if (queryResult.error == -1) {//1 判断登陆状态
    highlight = true;
    popup = content != '登录失效，请重新登录！';
    content = '登陆失效，请重新登录！';
} else {
    var onLiveInfoList = extra ? JSON.parse(extra) : [];
    var nowOnLiveInfoList = [];
    var newOnline = false;
    var newOffline = false;
    var baseUrl = 'https://www.douyu.com';//2 用于拼接完整直播间网址，一般直播网站都会需要

    queryResult = queryResult.data.list;//3 从JSON数据中获取到直播间列表
    for (let i = 0; i < queryResult.length; i++) {
        if (queryResult[i].show_status == 1) {//4 判断是否在线
            onLiveInfo = {
                'name': queryResult[i].nickname,//5 直播间名
                'subUrl': queryResult[i].url,//6 直播间子网址
            };
            nowOnLiveInfoList.push(onLiveInfo);
        }
    }

    //维护onLiveInfoList
    //移除已下线
    var newOnlineNameMap = {};
    for (let i = 0; i < nowOnLiveInfoList.length; i++) {
        newOnlineNameMap[nowOnLiveInfoList[i].name] = i;
    }
    for (let i = onLiveInfoList.length - 1; i >= 0; i--) {
        if (newOnlineNameMap[onLiveInfoList[i].name] == undefined) {
            newOffline = true;
            onLiveInfoList.splice(i, 1);
        }
    }
    //添加新上线
    var onLiveNameMap = {};
    for (let i = 0; i < onLiveInfoList.length; i++) {
        onLiveNameMap[onLiveInfoList[i].name] = i;
    }
    for (let i = 0; i < nowOnLiveInfoList.length; i++) {
        if (onLiveNameMap[nowOnLiveInfoList[i].name] == undefined) {
            newOnline = true;
            onLiveInfoList.unshift(nowOnLiveInfoList[i]);
        }
    }
    extra = JSON.stringify(onLiveInfoList);

    for (let i = 0; i < onLiveInfoList.length; i++) {
        if (i == 0) {
            content = '当前有' + onLiveInfoList.length + '人正在直播：\n';
            content += onLiveInfoList[i].name + '(最新) ';
            url = baseUrl + onLiveInfoList[i].subUrl;
        } else {
            content += ', ' + onLiveInfoList[i].name;
        }
    }
    if (onLiveInfoList.length == 0) {
        content = '暂无人直播';
        highlight = false;
        popup = false;
    } else if (newOnline) {
        highlight = true;
        popup = true;
    } else if (onLiveInfoList.length > 0 && isRefresh) {
        highlight = true;
        popup = false;
    } else if (onLiveInfoList.length > 0 && newOffline) {
        popup = false;
    }
}


return {'highlight': highlight, 'popup': popup, 'url': url, 'content': content, 'extra': extra};
```

{% file src="/files/-M4AQrMndpVG5bph1ULc" %}

### 站点签到示例

```javascript
//queryResult
//presetPageUrl  isRefresh  lastReturn
var highlight = lastReturn.highlight;
var popup = lastReturn.popup;
var url = presetPageUrl;
var content = lastReturn.content;
var extra = lastReturn.extra;
 
 
if ($(queryResult).find('.inner').length == 0) {//1
    highlight = true;
    popup = content != '登录失效，请重新登录！';
    content = '登录失效，请重新登录！';
} else {
    if (queryResult.indexOf('领取今日的登录奖励') != -1) {//2
        var once = new RegExp(/\?once=(\d*?)\';/, 'ig').exec(queryResult)[1];
        $.ajax({
            "type": "get",
            "async": false,
            "url": "https://www.v2ex.com/mission/daily/redeem?once=" + once,
            "dataType": "html",
            "timeout": 7000,
            success: function (res) {
                highlight = true;
                content = '签到成功';
            }
        });
    } else {
        highlight = false;
        content = '已签到';
    }
}
 
 
return {'highlight': highlight, 'popup': popup, 'url': url, 'content': content, 'extra': extra};
```

{% file src="/files/-M8v33PP8leCkfQEvii-" %}

{% file src="/files/-M8v37wPV1FzNvgfg70Y" %}

### Xpath定位示例

```js
//queryResult
//presetPageUrl  isRefresh  lastReturn
var highlight = lastReturn.highlight;
var popup = lastReturn.popup;
var url = presetPageUrl;
var content = lastReturn.content;
var extra = lastReturn.extra;

// 使用 XPath 定位 HTML 节点
function xpathOne(html, expr) {
    var doc = new DOMParser().parseFromString(html, 'text/html');
    return doc.evaluate(
        expr,
        doc,
        null,
        XPathResult.FIRST_ORDERED_NODE_TYPE,
        null
    ).singleNodeValue;
}

// 使用 XPath 获取节点文本
function xpathText(html, expr) {
    var node = xpathOne(html, expr);
    return node ? node.textContent.trim() : '';
}

// 使用 XPath 获取节点属性
function xpathAttr(html, expr, name) {
    var node = xpathOne(html, expr);
    return node ? node.getAttribute(name) : '';
}

// 此处可以通过 CSS 选择器、XPath、正则或者读取 JSON 对象来定位获取更新后不相同的部分
// CSS 示例：queryResult = $(queryResult).find('#video_list_li a:last').text();
// XPath 文本示例：queryResult = xpathText(queryResult, '//*[@id="video_list_li"]//a[last()]');
// XPath 属性示例：url = xpathAttr(queryResult, '//*[@id="video_list_li"]//a[last()]', 'href');
// XPath 按文本查找示例：queryResult = xpathText(queryResult, '//a[contains(text(), "签到")]');
queryResult = xpathText(queryResult, '//*[@id="video_list_li"]//a[last()]');//1

content = '更新到了 ' + queryResult;
if (content != lastReturn.content) {
    highlight = true;
    popup = true;
}

return {'highlight':highlight,'popup':popup,'url':url,'content':content,'extra':extra};
```

## Other Extensions

最后附上本人开发的另外几个实用的 Chrome 扩展

Tab Freezer： [Chrome 商店链接](https://chrome.google.com/webstore/detail/tab-freezer/opmcmbcmkbobnbloaanbgminfokciinp)\
这个扩展是提供给海量标签页综合症 TMTS (Too Many Tabs Syndrome) 患者的（比如我自己），冻结页面节省内存（然后打开更多的标签，，？？？），相比于其他 OneTab 等扩展，有两个优点，1. 标签页不会关闭；2. 点击即可生效，不会再弹出扩展弹框

Volume Manager： [Chrome 商店链接](https://chrome.google.com/webstore/detail/volume-manager/ogpbjlhpdjmcbkocjibemdmblffnfkmm)\
这个扩展是用来控制页面内音频视频的音量的，应该是 Chrome 市场里最完善的一款音量控制扩展了。\
不仅支持 HTML5 标准的音频视频同时也支持 Flash，最关键的是可以记忆每个页面或者网站的音量修改配置。

Regex Extractor： [Chrome 商店链接](https://chrome.google.com/webstore/detail/regex-extracter/hhnkaciopdblfpomobniofiiecfiibph)\
这个扩展的功能简单来说就是用正则表达式查询当前网页内的文本，默认如果正则中有捕获就返回捕获部分的文本，\
支持文本、节点和源码三种模式，常用的场景比如：批量获取邮箱，批量获取图片链接，批量获取动漫电视剧每集的下载链接等。
