Mrli
别装作很努力,
因为结局不会陪你演戏。
Contacts:
QQ博客园

Chrome插件编写-天猫秒杀插件

2021/12/04 脚本
Word count: 2,732 | Reading time: 13min

chrome插件开发

借鉴TODO-List:Chrome插件开发简要指南

文件结构

在应用商店中下载下来的插件基本上都是以.crx为文件后缀,该文件其实就是一个压缩包(文件夹压缩而来),包括插件所需要的html、css、javascript、图片资源等等文件。

其中,

  • manifest.json是整个插件的功能及文件配置清单,非常重要。
  • static目录是放置整个插件的静态资源文件的,包括css、js、图片等等资源
  • template目录是放置整个插件的功能页面模板的。
  • _locales目录是放置整个插件的国际化语言脚本的。

一般来说,清单文件manifest.json文件是必须的,且必须放在插件开发目录的根目录上。其他的目录都可以自定义。

弹出窗口和后台页面

弹出窗口一般用于插件和用户的交互,而后台页面一般用于插件本身做一些额外的事情。比如有时候,插件需要联网进行数据同步等操作,这种操作用户是无感知的,所有就要求插件需要有一个后台页面来运行这部分的逻辑。

其实后台页面还可以分为持久运行的后端页面和事件页面,这里对这两者就不多做说明了,更多的内容可以参阅具体的文档。

弹出式插件教程TODO-LIST

manifest.json

1
2
3
4
5
6
7
8
9
10
11
{
"name": "todo-plugin",
"version": "0.9.0",
"manifest_version": 2,
"description": "chrome plugin demo",
"browser_action": {
"default_icon": "icon.png",
"default_title": "Todo List",
"default_popup": "popup.html"
}
}

popup.html

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
<style>
body {
width: 150px;
}
#add-new-item {
cursor: pointer;
color: #CCC;
}
.hide {
display: none;
}
.show {
display: block;
}
.item {
cursor: pointer;
margin: 5px 0;
}
.item input {
display: inline-block;
width: 12px;
height: 12px;
}
input {
width: 120px;
}
</style>
<div id="add-new-item">添加新项</div>
<div id="add-new-item-input" class="hide">
<input type="text" id="new-item-title" placeholder="添加新任务"/>
</div>
<div id="item-list"></div>
<script type="text/javascript" src="main.js"></script>

main.js

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
(function () {
var $ = function (id) {
return document.getElementById(id);
};
var Tasks = {
show: function (obj) {
obj.className = '';
return this;
},
hide: function (obj) {
obj.className = 'hide';
return this;
},
$addNewItem: $('add-new-item'),
$addNewItemInput: $('add-new-item-input'),
$itemList: $('item-list'),
$newItemTitle: $('new-item-title'),
init: function () {
//打开添加文本框
Tasks.$addNewItem.addEventListener('click', function () {
Tasks.show(Tasks.$addNewItemInput).hide(Tasks.$addNewItem);
Tasks.$newItemTitle.focus();
}, true);
//回车添加任务
Tasks.$newItemTitle.addEventListener('keyup', function (ev) {
var ev = ev || window.event;
if (ev.keyCode == 13) {
//TODO:写入本地数据
var task = Tasks.$newItemTitle.value;
Tasks.AppendHtml(task);
Tasks.$newItemTitle.value = '';
Tasks.hide(Tasks.$addNewItemInput).show(Tasks.$addNewItem);
}
ev.preventDefault();
}, true);
//取消添加
Tasks.$newItemTitle.addEventListener('blur', function () {
Tasks.$newItemTitle.value = '';
Tasks.hide(Tasks.$addNewItemInput).show(Tasks.$addNewItem);
}, true);
//TODO 初始化数据,加载本地数据,生成html
},
//增加
Add: function () {
//TODO
},
//修改
Edit: function () {
//TODO
},
//删除
Del: function () {
//TODO
},
AppendHtml: function (title) {
var oDiv = document.createElement('div');
oDiv.className = 'item item-todo';
var oInput = document.createElement('input');
oInput.type = 'checkbox';
var oTitle = document.createElement('span');
oTitle.innerHTML = title;
oDiv.appendChild(oInput);
oDiv.appendChild(oTitle);
Tasks.$itemList.appendChild(oDiv);
oDiv.addEventListener('click', function () {
//TODO
}, true);
},
RemoveHtml: function () {
//TODO
}
}
Tasks.init();
})();

天猫秒杀插件 Tmall_Kill

Code Template: https://link.zhihu.com/?target=https%3A//github.com/cehui0303/Tmall_Tickets

代码比较简单,实现原理为,打开浏览器后执行插件,JS脚本将会在指定时间点击“结算”按钮,然后发起订单,从而锁定库存抢货。

main.js

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
//定时器
var timer = null;

//检测状态
function checkElementState(path,callback){
var ele = document.querySelector(path);
if(ele){
callback && callback();
}else{
console.log('异步加载元素中....' + path );
setTimeout( function(){checkElementState(path,callback);},200);
}
}

//结算
function checkOut(){
console.log('结算开始....');
var btn = document.getElementById('J_Go');

if(btn){
btn.click();
}else{
console.log('结算按钮没找到');
}
}

// S1: 1. 点击结算按钮 => 2. checkElementState()检测结算按钮是否加载出来 --> 3.checkOut()点击结算按钮
function checkOutAsync(){
checkElementState('#J_Go',checkOut);
}

// S2: 提交订单
function submitOrder(){
console.log('提交订单开始....');
checkElementState('.go-btn',function(){
var btn = document.querySelector(".go-btn");
if(btn){
btn.click();
}else{
console.log('提交订单按钮没找到');
}
});
}

//目标时间
var dDate = new Date(); //10点和20点开抢
if( dDate.getHours() < 10 ){
dDate.setHours(9,59,59.2);
}else{
dDate.setHours(19,59,59.2);
}

//进入时间判断循环
function enterTimeCheckLoop(callback){
var date = new Date();
var diff = Date.parse(dDate) - Date.parse(date) ;
console.log(diff);
if(diff < -90 ){
console.log('时间过了!');
}else if(diff < 50 ) {
callback && callback();
console.log('时间到了!!!');
}else{
setTimeout(function(){ enterTimeCheckLoop(callback);},400);
}
}


//主要函数
(function main(){
console.log('############################天猫枪杀脚本############################');
var href = window.location.href;
//结算页面
if(href.indexOf('cart.tmall.com') > -1 ){
//进入时间判断
enterTimeCheckLoop( checkOutAsync );
}else if(href.indexOf('buy.tmall.com') > -1 ){
//提交订单页面
submitOrder();
}
})()

这种理论上是可行的,只不过肯定会有刷新上的问题,效率不及网络请求,但思路依旧是值得学习的,因此借此机会也是学下Chrome插件的编写。

manifest.json

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
{
"manifest_version": 2, // manifest文件版本号。Chrome18开始必须为2
"name": "Tmall-tickets", // crx扩展名称
"version": "1.0", // 插件版本
"description": "天猫超市枪杀脚本",
"browser_action": { // 地址栏右侧图标管理。含图标及弹出页面的设置等
"default_icon": "icon.png"
},
"content_scripts": [ // 指定要向Web页面内注入的脚本。可注入多个css与js。
{
"matches": ["https://chaoshi.detail.tmall.com/*","https://cart.tmall.com/*","https://buy.tmall.com/*"],
"js": ["maotai.js"],
"run_at": "document_idle"
}
],
"permissions": [
"https://www.baidu.com/*",
"background",
"tabs"
], //权限
"plugins": [{
"path": "extension_plugin.dll",
"public": true
}], // 扩展。可调用第三方扩展
}

content_scripts,其数组元素的字段有:

  1. matches:String数组,必须。定义content_scripts对哪些页面生效。其规则符合permissions的模式匹配。

  2. css:String数组,可选。定义哪些css文件在web页面DOM创建前注入到web页面中。

  3. js:String数组,可选。定义哪些js文件注入到web页面中。其js文件的注入顺序与数组中定义的顺序相同。至于这些js与web页面中所定义js的顺序关系,取决于run_at字段。

  4. run_at:String,可选。定义content_scripts的注入时机,从而影响到js与web页面所定义js的顺序关系。取值有:

    • document_start:所有css加载完毕,但DOM尚未创建时。

    • document_end:DOM创建完成,但图片及frame等子资源尚未加载时。

    • document_idle:document_end之后,window.onload之前。

      默认是document_idle,也就是content_scripts的js都罗列在web页面的js之后。

  5. all_frames:boolean,可选。是否运行在页面所有的frame中。若为false,则只运行在最上层的frame中。默认为false。

  6. include_globs:String数组,可选。用于规定页面匹配的白名单。一个URL,必须同时满足:匹配matches,匹配include_globs白名单,不匹配exclude_globs黑名单这三个条件才可以。注意include_globs和exclude_globs中的匹配语法与permissions和matches所用的匹配模式不同。

  7. exclude_globs:String数组,可选。用于规定页面匹配的黑名单。同⑥。

permissions:扩展所需要的权限。permissions是一个String数组,每一个权限都使用String来表示。某些权限在安装前会告知用户。

  1. 模式配匹:用于指定扩展会在哪些URL中生效。例如:
  2. background:启用扩展后端环境。即在浏览器运行期始终运行,与单个页面无关。可以在这里调用浏览器的API,通常在这里进行扩展主要逻辑的开发。配合manifest.json的background字段使用。

background: 这是一个比较重要的属性,如果你需要运行一些后台脚本,比如监听用户在扩展信息栏按下你的插件图标,或者你要监听用户新建tab页,这个时候你就需要有一个background的页面。background你可以指定一个HTML页面(如我的插件),也可以指定一个JS文件,如:

1
2
3
4
5
6
7
8
9
10
11
{
"name": "My extension",
...
"background": {
"scripts": ["background.js"]
},
...
}
// 需要注意两点:
// 1、是HTML不能写JS代码,JS代码需要写到JS文件中后引入;
// 2、不能使用jquery(没有详细测试,可能是我没用正确);

background.js

1
2
3
4
5
// background.js
// @describtion: 监听用户在扩展信息栏按下你的插件图标时,显示当前活动页的URL:
chrome.browserAction.onClicked.addListener(function (tab) {
alert(tab.url);
});

改进Tmall_Kill

需要修改的功能为:

  1. 插件中设定时间=>时间框选择时间
  2. 匹配网站自动运行->弹出页面手动点击运行
  3. 选中购物车商品后结算->到时间后自动勾选商品结算
  4. 弹出页+时间框 popup.html

HTML5日期输入框(date)

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
<!--
* @Author: Mrli
* @Date: 2021-03-02 17:19:02
* @LastEditTime: 2021-03-03 22:18:02
* @Description:
-->
<style>
body {
width: 250px;
}
#add-new-item {
cursor: pointer;
color: #CCC;
}
.hide {
display: none;
}
.show {
display: block;
}
.item {
cursor: pointer;
margin: 5px 0;
}
.item input {
display: inline-block;
width: 12px;
height: 12px;
}
input {
width: 200px;
}
</style>
<div id="add-new-item">add new item</div>
<div id="add-new-item-input" class="hide">
<input type="text" id="new-item-title" placeholder="add new"/>
</div>
<label for="meeting">killTime:</label><input id="meeting" type="datetime-local"/>
<div id="item-list"></div>

<script type="text/javascript" src="main.js"></script>

配套的manifest.json

1
2
3
4
5
6
7
8
9
10
11
{
"name": "todo-plugin",
"version": "0.9.0",
"manifest_version": 2,
"description": "chrome plugin demo",
"browser_action": {
"default_icon": "icon.png",
"default_title": "Todo List",
"default_popup": "popup.html"
}
}

main.js

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
(function () {
var $ = function (id) {
return document.getElementById(id);
};
var Tasks = {
show: function (obj) {
obj.className = '';
return this;
},
hide: function (obj) {
obj.className = 'hide';
return this;
},
$addNewItem: $('add-new-item'),
$addNewItemInput: $('add-new-item-input'),
$itemList: $('item-list'),
$newItemTitle: $('new-item-title'),
init: function () {
//打开添加文本框
Tasks.$addNewItem.addEventListener('click', function () {
Tasks.show(Tasks.$addNewItemInput).hide(Tasks.$addNewItem);
Tasks.$newItemTitle.focus();
}, true);
//回车添加任务
Tasks.$newItemTitle.addEventListener('keyup', function (ev) {
var ev = ev || window.event;
if (ev.keyCode == 13) {
//TODO:写入本地数据
var task = Tasks.$newItemTitle.value;
Tasks.AppendHtml(task);
Tasks.$newItemTitle.value = '';
Tasks.hide(Tasks.$addNewItemInput).show(Tasks.$addNewItem);
}
ev.preventDefault();
}, true);
//取消添加
Tasks.$newItemTitle.addEventListener('blur', function () {
Tasks.$newItemTitle.value = '';
Tasks.hide(Tasks.$addNewItemInput).show(Tasks.$addNewItem);
}, true);
//TODO 初始化数据,加载本地数据,生成html
},
//增加
Add: function () {
//TODO
},
//修改
Edit: function () {
//TODO
},
//删除
Del: function () {
//TODO
},
AppendHtml: function (title) {
var oDiv = document.createElement('div');
oDiv.className = 'item item-todo';
var oInput = document.createElement('input');
oInput.type = 'checkbox';
var oTitle = document.createElement('span');
oTitle.innerHTML = title;
oDiv.appendChild(oInput);
oDiv.appendChild(oTitle);
Tasks.$itemList.appendChild(oDiv);
oDiv.addEventListener('click', function () {
//TODO
}, true);
},
RemoveHtml: function () {
//TODO
}
}
Tasks.init();
})();

学习案例:

Author: Mrli

Link: https://nymrli.top/2021/03/03/天猫秒杀插件-Chrome插件编写/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

< PreviousPost
Shiro使用学习
NextPost >
认真学次DP——动态规划
CATALOG
  1. 1. chrome插件开发
    1. 1.1. 文件结构
    2. 1.2. 弹出窗口和后台页面
    3. 1.3. 弹出式插件教程TODO-LIST
      1. 1.3.1. manifest.json
      2. 1.3.2. popup.html
      3. 1.3.3. main.js
  2. 2. 天猫秒杀插件 Tmall_Kill
    1. 2.0.1. main.js
    2. 2.0.2. manifest.json
  3. 2.1. 改进Tmall_Kill
  4. 2.2. 学习案例: