电脑开启儿童保护模式(3):浏览器插件编写

时隔一年回到家,弟弟还是长期沉迷于各种动画片,考虑到之前更改host会将整个视频网站禁掉,无法查看某些学习视频(最后还得手动给他解禁hhhh),因此决定编写一个浏览器插件实现对内容的过滤。

环境

本地开发环境:

  • Windows 10
  • Chrome浏览器

服务器环境:

  • Ubuntu 20.04
  • Python

需求分析

因为在上一次设置了安装新应用需要输入密码,因此将电脑其他浏览器卸载掉,只安装Chrome浏览器,就可以使其无法绕过我们编写的插件了(至于弟弟是否会卸载插件,可以对插件进行一定的伪装使他意识不到这个问题)。

然后,就是插件的功能需求,主要有以下几点:

  1. 禁掉各种主流的动画片
  2. 禁掉各种网页小游戏
  3. 禁掉各种涩情暴力等不利于儿童身心的网页内容
  4. 保留各类科普、学习相关的网页

程序设计

根据以上的需求分析,可以编写一个浏览器插件,对网页标题进行校验,如包含熊出没喜羊羊等关键词,则跳转页面至其他页面。

考虑到仅通过前端校验的方式,当关键词更新时就需要重新打包安装插件,无法实现程序的快速迭代和更新,因此,考虑增设后台程序,前端将获取到的网页标题发送给后台,由后台进行校验返回布尔值,前端接收到布尔值后再进行禁掉或放行。

程序编写

浏览器插件框架

首先,搭建一个浏览器插件的框架。

新建一个文件夹,这里以插件名称命名为DDblocker,然后找一个图标,新建一个名为manifest.json的文件:

{
  "manifest_version": 2,
  "name": "DDBlocker",
  "version": "1.0",

  "description": "Filter the video list before play it.",

  "icons": {
    "48": "icons/border-48.png"
  },

  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["ddblocker.js"]
    }
  ]
}

其中,name是插件名称,可以随意取名,description为程序描述,icons则填写之前的图标路径(48表示图标的长宽为48*48像素,这里最好同时提供4896像素长宽的图标),content_scripts中的matches表示在何处启用插件,这里的all_urls表示所有网址都启用,而js则为插件js函数的入口,也就是插件的主要内容,后续程序编写都是在这个js文件内。

新建文件,文件名为上文配置文件中定义的名称,这里是ddblocker.js,打开文件,就可以开始编写功能函数了。

最终文件目录:


目录结构

插件功能函数

插件主要分为两个功能,分别是获取页面标题请求服务器和根据服务器的返回值执行对应操作。

首先,书写根据服务器返回值执行对应操作的函数:

function dd_callback(response_value){
    if (response_value == true){
        // contine as default.
        return true;
    }else{
        console.log(response_value);
        location.href = 'about:blank';
    }
}

当返回值为true时不执行任何操作,即放行;当返回值为false时跳转页面至空白页。

然后将该段函数插入到页面中去:

function adding_elements(){
    // dd function
    var a = document.createElement("script");
    a.type = 'text/javascript';
    a.innerHTML = dd_callback.toString();
    document.body.appendChild(a);
}
adding_elements();

这里将上述函数通过toString()转换为字符串,构建一个script标签,插入到页面之中。

然后,需要书写请求服务器的函数。考虑到浏览器存在跨域问题,因此这里采用jsonp的方式进行跨域:

function jsonp_request(){
    // request script
    var b = document.createElement('script');
    title = document.title;
    b.src = build_url('dd_callback', title);
    document.body.appendChild(b);
}

通过document.title获取标签页标题,通过插入script标签,将其src指定为远端服务器的地址(这里使用函数build_url()来进行URL构建),来发送跨域请求,构建url的函数如下:

function build_url(callback_f, title){
    url = 'http://ddblocker.xxx.com:5000/ddblocker?title=' + title;
    
    // adding callback function.
    url += '&callback=' + callback_f;
    return url;
}

这里远程服务器开启了5000端口,接口为ddblocker,接收titlecallback两个参数,其中,title用于服务器判断是否应该放行该网址,而callback用于返回前端,以执行回调函数。

服务器端程序编写

由于所有的判断操作在服务器端执行,因此,需要书写服务器端代码,这里使用flask作为后端代码的web服务。

首先,新建一个flask应用,并定义接口:

from flask import Flask, request
app = Flask('ddblocker')

@app.route('/ddblocker')
def hello_dd():
    pass

if __name__ == "__main__":
    app.run(
        host='0.0.0.0',
        port=5000,
        # debug=True
    )

然后编写函数内容:

def hello_dd():
    # server logic here.
    # request -> data, document.title from client area.
    # server should allow jsonp protocol and return javascript which call the target function with args 'true' or 'false'.
    title = request.args.get('title')
    print(title)
    callback_f = request.args.get('callback')
    
    if not title or not callback_f:
        return 'location.href="about:blank"'
    
    return build_function_to_javascript( callback_f, title )

首先,接收传过来的参数,然后判定其是否为空,任一参数为空即返回跳转页面至空白页的js代码,否则返回执行dd_callback函数的js代码(该代码通过函数build_function_to_javascript进行构造)。

构建返回的js代码的函数如下:

def build_function_to_javascript(function_name, function_args):
    args_to_string = '(' + check_function(function_args) + ')'
    js_code = function_name + args_to_string
    return js_code

其中,通过字符串拼接的方式构建出形如dd_callback(true)的js代码,代码中的参数使用函数check_function来进行判断是否放行。

判断标签页标题是否符合要求的代码如下:

import pickle

# init
# read pickle file save the lists.
try:
    with open('./banned_list.pkl','rb') as f:
        banned_list = pickle.load(f)
except:
    banned_list = set() # empty


def check_function(title):
    for t in banned_list:
        if t in title:
            return 'false'
    return 'true'

这里使用了文件存储禁用词条,通过黑名单的方式进行判断。读取文件,遍历文件中的每个词条,判定标题是否包含该关键词,是的话则返回false,否则返回true

为了便于添加词条,设定一个添加词条的接口:

@app.route('/Blocker_changer')
def manager():
    operation_t = request.args.get('type')
    title = request.args.get('title')
    
    if not operation_t or not title:
        # format not supported.
        # return empty.
        return 'Error operations'
    else:
        # check operations and do something here.
        if operation_t == 'add':
            banned_list.add(title)
            return 'adding success'
        elif operation_t == 'remove':
            try:
                banned_list.remove(title)
            except:
                # pass
                return 'title does not exists'
            return 'remove success'
        else:
            return 'error oprations'

该接口接收titletype作为参数,分别表示禁用词和对应操作(添加词条或删除词条),将其存入之前读取的文件内容的全局变量。

当程序中断时,需要将当前文件内容保存下来,因此需要捕获一些程序中断运行的各类异常信号,因此,新增程序中断的操作函数:

def signal_handler( sig, frame):
    print(sig)
    with open('./banned_list.pkl','wb') as f:
        pickle.dump(banned_list, f)
    raise KeyboardInterrupt

然后将信号捕捉语句添加到主函数内:

if __name__ == "__main__":
    signal.signal( signal.SIGINT, signal_handler )
    signal.signal( signal.SIGTERM, signal_handler )
    signal.signal( signal.SIGSEGV, signal_handler )
    
    app.run(
        host='0.0.0.0',
        port=5000
    )

插件运行

打开Chrome浏览器,点击右上角的按钮,依次选择更多工具->扩展程序

扩展程序

然后单击右上角的按钮打开开发者模式,点击左边的按钮加载已解压的扩展程序,选择我们建立的项目文件夹:

image.png

然后就可以看到插件加载成功,点击右下角的按钮启用插件:


插件加载成功

然后随意打开一个页面,这里以百度为例,在页面右键单击,选择检查,查看控制台,发现插件报错:

HTTPS提醒

报错提示ddblocker.js:32 Mixed Content: The page at 'https://www.baidu.com/' was loaded over HTTPS, but requested an insecure script 'http://ddblocker.xxxxx.com:5000/ddblocker?title=%E7%99%BE%E5%BA%A6%E4%B8%80%E4%B8%8B%EF%BC%8C%E4%BD%A0%E5%B0%B1%E7%9F%A5%E9%81%93&callback=dd_callback'. This request has been blocked; the content must be served over HTTPS.,大概意思就是因为百度页面为HTTPS,而插件请求的地址为http,请求被锁定,必须使用HTTPS才行。因此,这里需要给服务器配置HTTPS,需要一个SSL证书。

申请证书需要一个域名,我使用的是阿里云的域名服务,因此打开阿里云的控制台,搜索SSL证书,搜索完成后点击左侧SSL证书,然后点击免费证书,点击立即购买

阿里云SSL证书申请

在购买界面依次勾选以下选项(这里由于已经购买了因此无法重复申请):


购买SSL证书

点击购买后,由于金额为0,因此会直接跳转到成功界面。然后在之前的界面选择证书申请,填写自己的域名(这里填写一个子域名)和信息即可,然后点击下一步:


申请域名证书

然后按照图示内容新增一条解析记录,完事后点击验证按钮,通过后点击提交审核

操作指引

然后就会看到之前的列表处多出了一个证书,点击下载按钮:


证书申请成功

选择其他下载:

下载证书

下下来解压之后会看到两个后缀分别为pemkey的文件:

下载的证书

将其和服务器的脚本放在同一文件夹内,更改脚本主函数内容为:

if __name__ == "__main__":
    app.run(
        host='0.0.0.0',
        port=5000,
        ssl_context=('./7219406_ddblocker.xxx.com.pem','7219406_ddblocker.xxx.com.key')
    )

将前端的插件脚本请求的网址的请求头改成https

使用命令运行脚本:

python ddblocker.py

打开新的标签页,访问链接https://ddblocker.xxx.com:5000/Blocker_changer?type=add&title=熊出没,显示adding success

添加词条

然后重新导入前端的插件。打开新的标签页,访问百度,发现显示正常,然后打开B站,搜索熊出没,随意点开一个视频,发现页面跳转至了空白页,插件实现成功:


插件实现成功

后续迭代与展望

  • 可对插件进行一定的伪装,例如将其不显示在浏览器书签栏右侧,将其名称和图标更改为一些不易被察觉的东西
  • 可在前端再添加一个字段,将其浏览的网址也传输到服务器,服务器对每次收到的请求的标题和网址都进行存储,实现对弟弟浏览内容的监控
  • 可做一个前端可视化界面实现词条的添加与删除,更加方便
  • 对服务器进行鉴权操作,防止弟弟发觉之后反向操作删除所有字段hhhh

参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,230评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,261评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,089评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,542评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,542评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,544评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,922评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,578评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,816评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,576评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,658评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,359评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,920评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,859评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,381评论 2 342