首页 > 网站开发 > JavaScript >
-
JavaScript教程之一个基于chrome扩展的自动答题器
1、写在前面
首先感谢小茗同学的文章-【干货】Chrome插件(扩展)开发全攻略,
基于这篇入门教程和demo,我才能写出这款
基于chrome扩展的自动答题器。
git地址: https://gitee.com/cifang/lighthouse_answering_machine.git
2、开发背景
去年12月,某省委组织部举办了一系列学习竞赛活动,第一期时,参加人数寥寥,在第二期时,便通过党组织渠道要求所有党员保质保量的参加。
该活动每期10天,每天有一次答题机会,每一期通过分享可获得额外两次。每次答题则是在题库中随机抽取(后来发现并不那么随机)单选和多选共20道题。
该活动可在专门的app上参加,也可通过官方网站参加。
既然是基于网页的并且支持chrome内核的考试系统,那自然能从前端入手进行操作。
3、主要功能迭代
1月11日,开发出脚本版本答题器。通过控制台(F12)运行脚本并自动作答。2月初,开始学习chrome扩展相关内容
2月21日,发布第一版答题器,主要功能有
- 1、打开活动主页、用户登录页;
- 2、清除登录信息;
- 3、记录并切换帐号;
- 4、自动标记正确答案;
- 5、自动答题并交卷。
3月4日,增加了了添加自定义试题及答案的功能。
3月12日,增加了用户信息导入导出功能,自动分享获取答题次数功能。
3月20日,增加了全自动答题功能。
4月20日,增加了伪造回传鼠标点击坐标的功能。
5月14日,增加了在线更新的功能
至此,答题器的功能已基本成熟,最终答题器的界面如下:
4、结构拆解与代码分析
chrome扩展的文档结构在小茗同学的文章中描述的很清楚了。为了便于开发,我最终决定使用popup,content 和 inject 相互配合通讯来实现本程序的功能。
整个程序的存储由 content 部分来处理,存放于 chrome.storage.local 中,popup和inject在需要时从 content 更新数据,同时如果用户修改了设置也及时反映给 content 进行保存。
popup的js代码如下:(我觉得我备注的还可以)
1 var config;//设置
2 var auto_all_ans=0;//全自动答题标志
3
4 $(function() {
5
6 // 加载设置
7 //config = {'set':{'save_login': 1, 'sign_ans': 1, 'auto_ans': 0}, 'login_info':{}, 'active':''}; // 默认配置
8
9 //打开活动页面
10 $('#open_page').click(function()
11 {
12 chrome.tabs.create({url: 'http://xxjs.dtdjzx.gov.cn/index.html'});
13 })
14 //打开登陆页面
15 $('#open_login_page').click(function()
16 {
17 getCurrentTabId(tabId => {
18 chrome.tabs.update(tabId, {url: 'https://sso.dtdjzx.gov.cn/sso/login'});
19 });
20 })
21 //清除登录信息
22 $('#open_logout_page').click(function()
23 {
24 sendMessageToContentScript(
25 {'cmd':'logout','data':{}},
26 //回调函数
27 function(response){if(response) {}}
28 );
29 //删除active类
30 $('.active').removeClass('active');
31 })
32
33 //显示、隐藏设置区域
34 $('#hide_config').click(function(){
35 $('#hide_config').hide();
36 $('#show_config').show();
37 $('#config').hide(500);
38 })
39 $('#show_config').click(function(){
40 $('#show_config').hide();
41 $('#hide_config').show();
42 $('#config').show(500);
43 })
44
45
46 //手动更新
47 $('#update').click(function(){
48 $(this).html('更新中...');
49 $(this).css('pointer-events','none');
50
51 var xhr = new XMLHttpRequest();
52 xhr.open("GET", "http://mydomain/dengta/update.php?v="+config['set']['date_version'], true);
53 xhr.onreadystatechange = function() {
54 if (xhr.readyState == 4) {
55 // JSON解析器不会执行攻击者设计的脚本.
56 //var resp = JSON.parse(xhr.responseText);
57 //console.log(resp);
58 if(resp=xhr.responseText)
59 {
60 //console.log(resp);
61
62 //清空原有扩展题库
63 sendMessageToContentScript({'cmd':'del_new_ques'}),
64
65 //第一行是最新的版本号,并保存设置
66 setTimeout(()=>{
67 config['set']['date_version']=resp.match(/(\/\/)(\S*)/)[2];
68 console.log(config);
69 save_set();
70 },1000);
71
72
73 //通过update函数向content更新补充题库
74 setTimeout(()=>{update(xhr.responseText);},2000);
75
76 //弹出提醒
77 //alert('已更新数据至'+config['set']['date_version'])
78 }
79 else
80 {
81 alert('已是最新版本')
82 }
83 }
84 }
85 xhr.send();
86
87 setTimeout(()=>{$(this).html('已更新'+config['set']['date_version']);},2000);
88 })
89
90 //切换上一人、下一人功能
91 $('#prev_one').click(function(){
92 $('#login_info_conf .active').prev().find('.login_info_change').click();
93 });
94 $('#next_one').click(()=>{
95 $('#login_info_conf .active').next().find('.login_info_change').click();
96 })
97
98 //导入导出功能
99 $('#input_login_info').click(()=>{
100
101 var new_login_info=$('#input_login_info_box').val();
102 //测试是否有效
103 try
104 {
105 new_login_info=JSON.parse(new_login_info);
106 }
107 catch (err)
108 {
109 txt="您输入的字符串有误,请重新查证。";
110 alert(txt);
111 }
112 //成功转化的字符串
113 //console.log(new_login_info);
114 if(typeof new_login_info === 'object')
115 {
116 console.log(new_login_info);
117 $.extend(config['login_info'],new_login_info);
118 //向content_script报告新加入的用户
119 sendMessageToContentScript(
120 {'cmd':'add','data':new_login_info},
121 //回调函数
122 function(response){if(response) {
123 }}
124 );
125 alert('导入完成');
126 }
127 });
128 //登录信息导出
129 $('#output_login_info').click(()=>{
130 $('#input_login_info_box').val(JSON.stringify(config['login_info']));
131 });
132 //全自动答题功能
133 $('#auto_all_ans').click(()=>{
134 auto_all_ans=1;
135 $('.login_info_change').each((i,v)=>{
136
137 setTimeout(()=>{
138 $(v).click();
139 },(config['set']['dtime']*1000+500)*53*i+1000);
140
141 });
142 })
143
144 //函数:向content保存设置
145 function save_set(){
146 var res={
147 'cmd':'set_conf',
148 'data':{
149 'save_login': $('#save_login').get(0).checked?1:0,
150 'sign_ans': $('#sign_ans').get(0).checked?1:0,
151 'sign_ans_mouseover': $('#sign_ans_mouseover').get(0).checked?1:0,
152 'auto_ans': $('#auto_ans').get(0).checked?1:0,
153 'dtime':parseFloat($('#dtime').val()?$('#dtime').val():3),
154 'date_version':config['set']['date_version']
155 }
156 };
157 console.log(res);
158 sendMessageToContentScript(
159 res,
160 //回调函数
161 function(response)
162 {
163 if(response)
164 {
165
166
167 }
168 }
169 );
170 //chrome.storage.local.set(res['data']);
171 config['set']=res['data'];
172 console.log(res);
173 }
174
175 //函数:向content递交补充题库
176 function update(data){
177 var new_data=data.split(/[\n]+/g);
178 console.log(new_data);
179 var len=new_data.length;
180 var j=0;//题目答案计数器
181 var new_question='';
182 var new_answer='';
183 var new_ques_arr=[];
184
185 //第一个不为空的数组为试题
186 for(var i=0;i<len;i++){
187 //如果是备注的话,就跳过改行
188 if(new_data[i].match(/^\/\//))
189 continue;
190 //第0、2、4、6..行是题目
191 //第1、3、5、7..行是答案
192 if(j%2==0)
193 {
194 new_question=new_data[i].replace(/[ABCD. \r\n]/g,'');
195 }
196 else
197 {
198 new_answer=new_data[i].replace(/[ABCD. \r\n]/g,'');
199 new_ques_arr.push([new_question,new_answer]);
200
201 new_question='';
202 new_answer='';
203 }
204 j++;
205 };
206 //向前端发送命令
207 if(new_ques_arr.length>0)
208 {
209 //对无关信息过滤
210 var res={
211 'cmd':'set_new_ques',
212 'data':new_ques_arr
213 };
214
215
216 sendMessageToContentScript(
217 res,
218