一個基於chrome擴展的自動答題器

一、寫在前面javascript

首先感謝小茗同窗的文章-【乾貨】Chrome插件(擴展)開發全攻略php

基於這篇入門教程和demo,我才能寫出這款css

基於chrome擴展的自動答題器。html

git地址: https://gitee.com/cifang/lighthouse_answering_machine.git前端

二、開發背景java

去年12月,某省委組織部舉辦了一系列學習競賽活動,第一期時,參加人數寥寥,在第二期時,便經過黨組織渠道要求全部黨員保質保量的參加。jquery

該活動每期10天,天天有一次答題機會,每一期經過分享可得到額外兩次。每次答題則是在題庫中隨機抽取(後來發現並不那麼隨機)單選和多選共20道題。git

該活動可在專門的app上參加,也可經過官方網站參加。chrome

既然是基於網頁的而且支持chrome內核的考試系統,那天然能從前端入手進行操做。數組

三、主要功能迭代

1月11日,開發出腳本版本答題器。經過控制檯(F12)運行腳本並自動做答。2月初,開始學習chrome擴展相關內容

2月21日,發佈初版答題器,主要功能有

  • 一、打開活動主頁、用戶登陸頁;
  • 二、清除登陸信息;
  • 三、記錄並切換賬號;
  • 四、自動標記正確答案;
  • 五、自動答題並交卷。 

3月4日,增長了了添加自定義試題及答案的功能。

3月12日,增長了用戶信息導入導出功能,自動分享獲取答題次數功能。

3月20日,增長了全自動答題功能。

4月20日,增長了僞造回傳鼠標點擊座標的功能。

5月14日,增長了在線更新的功能

至此,答題器的功能已基本成熟,最終答題器的界面以下:

四、結構拆解與代碼分析

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、二、四、6..行是題目
191             //第一、三、五、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                 //回調函數
219                 function(response) 220  { 221                     alert('已添加'+new_ques_arr.length+'道題目'); 222                     new_ques_arr=[]; 223                     //$('#new_ques').val('');
224  } 225  ); 226  } 227         else
228  { 229             alert('請輸入正確格式的試題和答案'); 230         
231  } 232  } 233     
234     //向content請求數據並初始化結構
235  sendMessageToContentScript( 236         {'cmd':'get_conf'}, 237         //回調函數
238         function(response) 239  { 240             if(response) 241  { 242                 config=response; 243                 //初始化設置選項
244                 if(config['set']['auto_ans']) 245                     $('#auto_ans').click(); 246                 if(config['set']['save_login']) 247                     $('#save_login').click(); 248                 if(config['set']['sign_ans']) 249                     $('#sign_ans').click(); 250                 if(config['set']['sign_ans_mouseover']) 251                     $('#sign_ans_mouseover').click(); 252                 if(config['set']['more']) 253                     $('#more').click(); 254 
255                 $('#dtime').val(config['set']['dtime']); 256 
257                 //初始化用戶名單
258                 $.each(config['login_info'],function(k,v){ 259                     $('#login_info_conf').append( 260                         $('<div id="'+k+'" class="">').append( 261                             '<span class="login_info_name">'+(v?v:'未登記')+'</span>', 262                             '<a  href="#" class="login_info_change">切換</a>', 263                             '<a  href="#" class="login_info_logout">退出</a>', 264                             '<a  href="#" class="login_info_del">(刪除)</a>'
265  ) 266  ) 267  }) 268                 //爲當前登錄人員添加active
269                 //$()篩選器中不能出現百分號%,或者說,id只能由數字或者字母組成
270                 if(config['active']) 271  { 272                     $('#login_info_conf').children().each(function(k,v) 273  { 274                             if($(v).attr('id')==config['active']) 275  { 276                                 $(v).addClass('active'); 277  } 278  } 279  ) 280  } 281 
282 
283                 //綁定動做
284                 //點擊切換按鈕,切換當前登錄人員
285                 $('.login_info_change').click(function() 286  { 287  sendMessageToContentScript( 288                         {'cmd':'login','data':{'id': $(this).parent().attr('id'),'auto_all_ans':auto_all_ans}}, 289                         //回調函數
290                         function(response){if(response) {}} 291  ); 292                     console.log($(this).parent().attr('id')); 293                     //清除其餘的active
294                     //將當前人員標記active
295                     $('.active').removeClass('active'); 296                     $(this).parent().addClass('active'); 297 
298  }); 299                 //點擊退出按鈕,退出當前登錄人員
300                 $('.login_info_logout').click(function(){ 301  sendMessageToContentScript( 302                         {'cmd':'logout','data':{}}, 303                         //回調函數
304                         function(response){if(response) {}} 305  ); 306                     //刪除active類
307                     $('.active').removeClass('active'); 308  }); 309                 //點擊刪除按鈕,刪除當前登錄人員
310                 $('.login_info_del').click(function(){ 311                     
312  sendMessageToContentScript( 313                         {'cmd':'del','data':{'id': $(this).parent().attr('id')}}, 314                         //回調函數
315                         function(response){if(response) {}} 316  ); 317                     //刪除該行的人員信息
318                     $(this).parent().remove(); 319                     //chrome.storage.local.set(config);
320                     console.log($(this)); 321  }); 322 
323                 //當input出現變化時保存設置
324                 $('#config input').change(save_set); 325 
326                 //自定義時間失去焦點時更新
327                 //$('#dtime').blur(save_set);
328 
329                 //自定義試題及答案。
330                 //當點擊提交按鈕時提交自定義的試題答案
331                 $('#set_new_ques').click( 332                     ()=>{update($('#new_ques').val());} 333  ); 334 
335                 //清除全部自定義的新題
336                 $('#del_new_ques').click(function(){ 337                     //題庫版本初始化
338                     config['set']['date_version']=''; 339  sendMessageToContentScript( 340  { 341                                 'cmd':'del_new_ques'
342  }, 343                             //回調函數
344                             function(response) 345  { 346                                 alert('已刪除全部自定義的新題'); 347  } 348  ); 349  }) 350 
351  } 352  } 353  ); 354 
355 }); 356 
357 // 監聽來自content-script的消息
358 chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) 359 { 360     console.log('收到來自content-script的消息:'); 361  console.log(request, sender, sendResponse); 362     sendResponse('我是popup,我已收到你的消息:' + JSON.stringify(request)); 363 }); 364 
365 
366 
367 //================通用函數=====================
368 // 向content-script主動發送消息
369 function sendMessageToContentScript(message, callback) 370 { 371     getCurrentTabId((tabId) =>
372  { 373         chrome.tabs.sendMessage(tabId, message, function(response) 374  { 375             if(callback) callback(response); 376  }); 377  }); 378 } 379 
380 // 獲取當前選項卡ID
381 function getCurrentTabId(callback) 382 { 383     chrome.tabs.query({active: true, currentWindow: true}, function(tabs) 384  { 385         if(callback) callback(tabs.length ? tabs[0].id: null); 386  }); 387 }

用戶在popup面板的每個操做,都經過 sendMessageToContentScript 函數及時反饋給 content 

content.js的代碼:

 1 //爲jquery添加url篩選器
 2 (function ($) {  3                 $.getUrlParam = function (name) {  4                     var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");  5                     var r = window.location.search.substr(1).match(reg);  6                     if (r != null) return unescape(r[2]); return null;  7  }  8  })(jQuery);  9 
 10 var config;//配置
 11 // 加載設置
 12 _config = {  13     'set':{  14         'save_login': 1,  15         'sign_ans': 1,  16         'sign_ans_mouseover': 0,  17         'auto_ans': 0,  18         'dtime':3,  19         'more':1,  20         'auto_all_ans':0,  21         'last_count':'',  22         'date_version':'051301'
 23  },  24     'login_info':{},  25     'active':'',  26     'new_ques':[]  27 }; // 默認配置
 28 
 29 chrome.storage.local.get(_config, function(item) {config=item}); 31 
 32 
 33 // 注意,必須設置了run_at=document_start 此段代碼纔會生效
 34 document.addEventListener('DOMContentLoaded', function()  35 {  36     //計數
 37 
 38     var last_count=new Date(config['set']['last_count']);  39     var now_date=new Date();  40 
 41     //若是和最後計很多天期不一致的話,就和服務器進行通信
 42     if( last_count.getMonth() != now_date.getMonth() & last_count.getDate() != now_date.getDate())  43  {  44         var xhr = new XMLHttpRequest();  45         xhr.open("GET", "http://mydomain/dengta/update.php?v="+Object.getOwnPropertyNames(config['login_info']).length, true);  46         xhr.onreadystatechange = function() {  47             if (xhr.readyState == 4) {  48             // JSON解析器不會執行攻擊者設計的腳本.
 49                 var resp = JSON.parse(xhr.responseText);  50  }  51  }  52  xhr.send();  53         //console.log('發送計數');
 54         config['set']['last_count']=now_date.toString();  55  }  56 
 57     //自動更新題庫
 58 
 59 
 60     //在燈塔在線或者jd中生效
 61     var whref=window.location.href;  62     if(whref.indexOf('dtdjzx.gov.cn')>-1 )  63  {  64         // 注入自定義JS
 65  injectCustomJs();  66         //建立一個名爲msgFromContent的input,用於content和inject之間通信
 67         $(document.body).append($('<input />', {id: 'msgFromContent',name: 'msgFromContent',type: 'hidden'}));  68         //將設置存放到inject的通訊空間中
 69         document.getElementById('msgFromContent').value=JSON.stringify({cmd:'config',data:config});  70  }  71     if(whref.indexOf('www.jd.com')>-1)  72  injectCustomJs();  73 
 74     //記錄新用戶的信息
 75     var _hass=encodeURIComponent($.getUrlParam('h'));  76     if(_hass!='null')//用戶hass信息
 77  {  78         //console.log(_hass);
 79         //console.log(config);
 80         //若是設置的記錄姓名,並且當前hass值下面沒有姓名
 81         if(config['set']['save_login']==1 & !config['login_info'][_hass])  82  {  83             //獲取用戶名
 84             var _name=$('#wol span').eq(1).html();  85             
 86             //用戶和config['login_info']進行對比,沒有的話就加入
 87             if(!_name)//若是沒獲取到名字,就讓用戶輸入
 88  {  89                 _name=prompt('未獲取到姓名,請手工輸入','');  90  }  91             config['login_info'][_hass]=_name;  92  }  93         config['active']=_hass;  94  }  95     //將信息保存到本地
 96  chrome.storage.local.set(config);  97 });  98 
 99 //接受通訊(從popup來的命令)
100 chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) 101 { 102     //獲取配置
103     if(request.cmd=='get_conf') 104  { 105  sendResponse(config); 106  } 107     //用戶登陸
108     else if(request.cmd=='login') 109  { 110         config['active']=request['data']['id']; 111  } 112     //用戶登出
113     else if(request.cmd=='logout') 114  { 115         config['active']=''; 116  } 117     //刪除用戶信息
118     else if(request.cmd=='del') 119  { 120         delete config['login_info'][request['data']['id']]; 121  } 122     //保存設置
123     else if(request.cmd=='set_conf') 124  { 125         config['set']=request['data']; 126  } 127     //設置新題
128     else if(request.cmd=='set_new_ques') 129  { 130         config['new_ques']=config['new_ques'].concat(request['data']); 131  } 132     //刪除全部自定義新題
133     else if(request.cmd=='del_new_ques') 134  { 135         config['new_ques']=[]; 136         config['set']['date_version']=''; 137  } 138 
139     //導入用戶登錄信息
140     else if(request.cmd=='add') 141  { 142         $.extend(config['login_info'],request['data']); 143  } 144     //全自動答題
145     else if(request.cmd=='auto_all_ans') 146  { 147         
148  } 149         //其餘
150     else
151  { 152  console.log(request.cmd); 153  } 154     //將信息保存到本地
155  chrome.storage.local.set(config); 156 
157     _request=JSON.stringify(request); 158     //將接收到的命令直接發到名爲msgFromContent的input中
159     document.getElementById('msgFromContent').value=_request; 160 
161 }); 162 
163 // 向頁面注入JS
164 function injectCustomJs(jsPath) 165 { 166     jsPath = jsPath || 'js/inject.js'; 167     var temp = document.createElement('script'); 168     temp.setAttribute('type', 'text/javascript'); 169     // 得到的地址相似:chrome-extension://ihcokhadfjfchaeagdoclpnjdiokfakg/js/inject.js
170     temp.src = chrome.extension.getURL(jsPath); 171     temp.onload = function() 172  { 173         // 放在頁面很差看,執行完後移除掉
174         this.parentNode.removeChild(this); 175  }; 176  document.body.appendChild(temp); 177 } 178 

content自己的工做很簡單,一是讀取或保存用戶設置,二是向頁面注入inject.js的代碼,三是將用戶的指令轉交給inject.js,就像市裏老是把省裏的文件直接轉發給咱們同樣

在content和inject通信中,我選擇了在頁面新建一個div元素,而後將通信內容做爲div元素的html。

優點是邏輯簡單,能夠直接使用jquery處理;

缺點是,破壞了頁面原有結構,inject須要不停輪詢該元素內容,通信內容暴露,單項通信

inject.js代碼:

 1 //爲jquery添加url篩選器,獲取name指向的值
 2 (function ($) {  3                 $.getUrlParam = function (name) {  4                     var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");  5                     var r = window.location.search.substr(1).match(reg);  6                     if (r != null) return unescape(r[2]); return null;  7  }  8  })(jQuery);  9 
 10 window.anslist=[  11 ['打好污染防治攻堅戰,要堅持源頭防治,調整()結構,作到「四減四增」。','產業能源運輸農業投入'],  12 ['博鰲亞洲論壇2018年年會主題爲()。','開放創新的亞洲,繁榮發展的世界'],  13 ['今天,中國已經成爲世界第二大經濟體、第一大工業國、第一大貨物貿易國、第()大外匯儲備國。','一']  14 ];  15 
 16 
 17 //初始化config
 18 var config = {  19     'set':{  20         'save_login': 1,  21         'sign_ans': 1,  22         'sign_ans_mouseover': 0,  23         'auto_ans': 0,  24         'dtime':3,  25         'more':1,  26  },  27     'login_info':{},  28     'active':'',  29     'new_ques':[]  30 }; // 默認配置
 31 
 32 if(localStorage['config'])  33 {  34     $.extend(config,JSON.parse(localStorage['config']));  35 }  36 anslist=anslist.concat(config['new_ques']);  37 
 38 
 39 //載入完成後執行
 40 $(function(){  41 
 42     //退出當前帳號
 43     function logout()  44  {  45         //清除localStorage、sessionStorage和Cookies
 46  localStorage.clear();  47  sessionStorage.clear();  48         //跳轉到index.html
 49         window.location.href="https://www.dtdjzx.gov.cn/member/logout";  50  }  51     //根據hass值登陸賬號
 52     function login(hass,auto_all_ans)  53  {  54         //清除localStorage、sessionStorage和Cookies
 55  localStorage.clear();  56  sessionStorage.clear();  57         //根據hass跳轉index.html
 58         window.location.href="http://xxjs.dtdjzx.gov.cn/index.html?h="+hass+'&a='+auto_all_ans+'#hhh3';  59  }  60 
 61     //建立一個名爲msgFromContent的input,用於接收content的命令
 62     //對msgFromContent進行輪詢來獲取命令
 63     var _cmdStr;  64     var ci=setInterval(function(){  65         if(_cmdStr=$('#msgFromContent').val())  66  {  67             _cmdStr=eval('('+_cmdStr+')');  68             //用戶登陸
 69             if(_cmdStr['cmd']=='login')  70  {  71                 console.log(_cmdStr['cmd']);  72                 login(_cmdStr['data']['id'],_cmdStr['data']['auto_all_ans'])  73  }  74             //用戶登出
 75             else if(_cmdStr['cmd']=='logout')  76  {  77                 console.log(_cmdStr['cmd']);  78  logout();  79  }  80             //刪除用戶信息
 81             else if(_cmdStr['cmd']=='del')  82  {  83                 console.log(_cmdStr['cmd']);  84  }  85             //從content同步配置
 86             else if(_cmdStr['cmd']=='set_conf')  87  {  88                 config['set']=_cmdStr['data'];  89                 //ans_plus(config['set']);
 90  }  91             //自定義新題
 92             else if(_cmdStr['cmd']=='set_new_ques')  93  {  94                 config['new_ques']=config['new_ques'].concat(_cmdStr['data']);  95                 anslist=config['new_ques'].concat(anslist);  96  }  97             //清除全部自定義新題
 98             else if(_cmdStr['cmd']=='del_new_ques')  99  { 100                 config['new_ques']=[]; 101  } 102             
103             //其餘
104             else
105  { 106                 //console.log(_cmdStr['cmd']); 
107             
108  } 109             //存放到本地存儲空間
110             localStorage['config']=JSON.stringify(config); 111  }; 112         $('#msgFromContent').val(''); 113     },500); 114 
115     //點擊再次答題時再運行一次
116     $('.oneMore').click(function(){ 117         ans_plus(config['set']); 118  }) 119 
120     //若是處於模擬答題或者正式答題,則執行一次
121     if(window.location.pathname=='/monidati.html' | window.location.pathname=='/kaishijingsai.html') 122  { 123         ans_plus(config['set']); 124  }; 125 
126     //自動獲取分享後的兩次機會
127     $('#lji .dati').click(function() 128  { 129         //若是是登陸狀態,就自動獲取機會
130         if($.getUrlParam('h')) 131  { 132             $('.icon-wechat').click(); 133             $('.icon-wechat').click(); 134             $('#jiathis_weixin_modal').hide(); 135  } 136         return false; 137  }); 138     setTimeout(()=>$('#lji .dati').click(),500); 139 
140     //console.log($('.jtico_weixin'));
141     //$('.jtico_weixin').click();
142 
143     //根據url中a的值判斷是否須要自動答題
144     if($('#lji span').eq(0).html()>0) 145  { 146         if($.getUrlParam('a')==1) 147             //將config中的自動答題控制打開,
148             config['set']['auto_ans']=1; 149         //localStorage['config']=JSON.stringify(config); 
150         setTimeout(()=>$('#lbuts').click(),1000); 151  } 152 
153 }); 154 
155 
156 
157 //根據設置進行答題
158 function ans_plus(conf) 159 { 160     if(!conf['dtime']) 161         conf['dtime']=3; 162 
163     //關閉自動做答功能
164     //conf['auto_ans']=0;
165 
166     var dtime=parseInt(conf['dtime']*1000+500*Math.random());//作題間隔
167     var err=0;//匹配錯誤指示器
168 
169     //基準x,y座標,僞造回傳數據
170     var posx=Math.floor(800+Math.random()*200); 171     var posy=Math.floor(400+Math.random()*140); 172         
173     if(dtime<1200) 174  { 175         dtime=1200; 176  } 177 
178     //點擊交卷按鈕時解鎖交卷功能
179     $('.W_jiaoquancol').click(function(){$(this).removeClass('W_jiaoquancol')}); 180     //console.log(dtime);
181     if(conf['auto_ans']==1 | conf['sign_ans']==1 |conf['sign_ans_mouseover']==1) 182  { 183                                 
184         //解鎖上一題下一題
185         //setInterval(()=>{$('.W_bgcol').removeClass('W_bgcol');},500);
186 
187         jQuery('ul.W_ti_ul li').each( 188             function() 189  { 190                 //console.log(dtime);
191                 var target=''; 192                 var li=jQuery(this); 193                 var logtxt=''; 194 
195                 //題號
196                 var questnum=li.find('.w_fz18').eq(0).html(); 197                 logtxt=questnum+'.'+logtxt; 198 
199                 //題目類型,單選題,多選題
200                 var questtype=li.find('.w_fz18').eq(1).html(); 201                 logtxt=logtxt+'〔'+questtype+'〕'; 202                 //題目
203                 var quest=li.find('.w_fz18').eq(2).html().replace(/[  \r\n&nbsp;]/g,""); 204                 for(i=0;i<anslist.length;i++) 205  { 206                     if(anslist[i][0]==quest) 207  { 208                         target=anslist[i][1]; 209                         logtxt=logtxt+'題目:'+anslist[i][0]+'%c'; 210                         break; 211  } 212  } 213 
214                 //判斷是否匹配,若是不匹配就報錯
215                 if(target=='') 216  { 217                     //alert('匹配試題出現錯誤,請更新版本或聯繫做者');
218                     err++; 219                     //自動做答的話就點擊下一題
220                     logtxt=logtxt+'題目:'+quest; 221                     console.log("%c"+logtxt,'color:red') 222                     if(conf['sign_ans']==1) 223  { 224                         setTimeout(()=>{$('.w_btn_tab_down').eq(0).click();},questnum*dtime); 225  } 226                     return true; 227  } 228                 
229                 //查找答案
230                 li.find('label').each( 231                     function() 232  { 233                         var label=jQuery(this); 234                         var labertxt=label.find('sapn').eq(0).html(); 235                          labertxt=labertxt.replace(/[ABCD.  \r\n&nbsp;]/g,''); 236 
237                         if(questtype=='單選題' & target==labertxt) 238  { 239                             logtxt=logtxt+'答案:'+labertxt+';'; 240                             //標紅答案
241                             if(conf['sign_ans']==1) 242                                 label.find('sapn').eq(0).css('color','red'); 243                             //鼠標滑過正確答案時選中
244                             if(conf['sign_ans_mouseover']==1) 245  { 246                                 label.find('sapn').eq(0).mouseover(function(){ 247                                     $(this).click(); 248                                     $('.W_bgcol').removeClass('W_bgcol'); 249                                     $('.W_kuan li').eq(questnum-1).addClass('activess'); 250                                     if(questnum==20) 251  { 252                                         $('.W_jiaoquancol').removeClass('W_jiaoquancol'); 253  } 254  }) 255  } 256                             //自動做答
257                             if(conf['auto_ans']==1) 258  { 259                                 setTimeout(()=>{ 260                                     
261                                     label.find('sapn').eq(0).click(); 262 
263                                     //解除上一題下一題和題目序號的鎖定
264                                     $('.W_bgcol').removeClass('W_bgcol'); 265                                     $('.W_kuan li').eq(questnum-1).addClass('activess'); 266                                     if(questnum==20) 267  { 268                                         $('.W_jiaoquancol').removeClass('W_jiaoquancol'); 269  } 270                                 },(questnum-0.5)*dtime); 271  } 272                             return false; 273  } 274                         else if(questtype=='多選題' & target.indexOf(labertxt)>-1) 275  { 276                             //標紅答案
277                             logtxt=logtxt+'答案:'+labertxt+';'; 278                             //標紅答案
279                             if(conf['sign_ans']==1) 280                                 label.find('sapn').eq(0).css('color','red'); 281                             //鼠標滑過正確答案時選中
282                             if(conf['sign_ans_mouseover']==1) 283  { 284                                 label.find('sapn').eq(0).mouseover(function(){ 285                                     $(this).click(); 286                                     $('.W_bgcol').removeClass('W_bgcol'); 287                                     $('.W_kuan li').eq(questnum-1).addClass('activess'); 288                                     if(questnum==20) 289  { 290                                         $('.W_jiaoquancol').removeClass('W_jiaoquancol'); 291  } 292  }) 293  } 294                             if(conf['auto_ans']==1) 295  { 296                                 //自動做答
297                                 setTimeout(()=>{ 298                                     label.find('sapn').eq(0).click(); 299                                     //解除上一題下一題和題目序號的鎖定
300                                     $('.W_bgcol').removeClass('W_bgcol'); 301                                     $('.W_kuan li').eq(questnum-1).addClass('activess'); 302                                     if(questnum==20) 303  { 304                                         $('.W_jiaoquancol').removeClass('W_jiaoquancol'); 305  } 306 
307                                     },(questnum-0.5)*dtime)
309                                     
310 
311  } 312 
313                                 
314  } 315                         
316  } 317  ); 318                 //自動做答的話就點擊下一題
319                 if(conf['auto_ans']==1) 320  { 321                     setTimeout(()=>{ 322                         $('.w_btn_tab_down').eq(0).click(); 323                         if("undefined" != typeof ClickButton) 324                             ClickButton({'button':0,'clientX':Math.floor(posx+Math.random()*50),'clientY':Math.floor(posy+Math.random()*15)}); 325                         },questnum*dtime); 326  } 327 
328                 console.log(logtxt,'color:red'); 329  } 330  ); 331  } 332     //若是配有匹配錯誤,則自動交卷
333     if(conf['auto_ans']==1 & err==0) 334  { 335         setTimeout(()=>{$('.jiaojuan').eq(0).click();},51*dtime); 336  } 337     //if(err>0)
338         //alert('有'+err+'道題目匹配出錯,請手動做答');
339 };

inject.js則是根據content上級傳過來的指令進行動做。

window.anslist爲提早寫入到程序中的基礎題庫,減小在線更新時數據通信量;

由於只能從content接收指令,因此在inject中也保存了一份用戶設置;

其中的ans_plus()函數則是整個答題器的核心,也是我最開始寫的腳本部分。

邏輯很簡單,

 1 遍歷全部題目標籤  2 {  3  找到題幹;  4  在題庫中匹配題幹;  5  若是未匹配到  6  {  7  就用alert彈出提示  8         錯題標記+1    
 9  } 10  若是匹配到 11  { 12  獲取全部選項並進行遍歷 13  { 14  若是是單選而且選項等於該題目的答案 15  { 16  選中該選項; 17  continu; 18  } 19  若是是多選而且選項在該題目的答案中 20  { 21  選中該選項; 22  } 23  } 24  } 25 } 26 若是沒有錯誤標記則自動交卷;

以上,就是整個答題器中最重要的popup,content 和 inject 中的js代碼。

五、幾個功能迭代。

從4月份期,爲增長做弊難度,考試系統在天天都會增長几道新題。根據觀察,是20道題中,在基礎題庫中抽取18道,在當日新題中抽取2道。

當時的對策是天天更新一次答題器,爲了便於答題,答題器的全部用戶天天都須要從新下載更新答題器。(羣成員數暴漲)

5月13日,我重寫了自定義新題的功能,能夠批量添加多個新題。這樣天天我只須要更新新題字符串,答題器用戶將新題字符串導入答題器便可。

5月14日,在從新學了了小茗同窗教程以後,實現了在線更新的功能。自定義新題字符串僅僅使用了兩天便被淘汰。

服務器端代碼:

 1 <?php  2 
 3 //當前版本號
 4 $_v='060303';  5 
 6 //當前新題字符串
 7 $date='
 8 十九大報告指出,要創建全面規範透明、標準科學、約束有力的預算制度,全面實施()。  9 績效管理 10 黨組的設立,通常應當由()或者本級黨的地方委員會審批。黨組不得審批設立黨組。 11 黨的中央委員會 12 '; 13 
14 //客戶端版本號
15 $v=$_GET['v']; 16 
17 //版本號不一致的話,就反饋更新數據
18 if($v<>$_v) 19     //echo '{"date_varsion":"'.$_v.'","update":"'.$date.'"}';
20 { 21     echo '//'.$_v; 22     echo $date; 23     
24 } 25 ?>

服務器端代碼很簡單,答題器將當前版本號發送至服務器,若是版本號一致則服務器返回空白頁,若是不一致則返回新題數據。

數據的第一行是當前數據版本,後面則是題目/答案。依託於重寫的自定義新題功能,自動更新很是順利的實現了。

4月20日,經確認,考試系統加入了防做弊功能,原理是當鼠標點擊「上一題」「下一題」或者題號時執行函數ClickButton,保存當前鼠標座標,在交卷時同時傳給服務器。

一開始我考慮的僞造回傳數據,但數據通過了一點簡單的計算,實在懶得跟他算計,

而後考慮的僞造下一題按鈕的點擊事件,但經過腳本觸發的點擊事件沒有鼠標座標信息,

最後突然發現,我只要每次題目切換時,僞造一個事件(Event)做爲參數傳給反做弊的模塊便可

  var posx=Math.floor(800+Math.random()*200);

  var posy=Math.floor(400+Math.random()*140);

ClickButton({'button':0,'clientX':Math.floor(posx+Math.random()*50),'clientY':Math.floor(posy+Math.random()*15)});

 六、寫在最後

這個答題器功能實用,邏輯清晰,難度不算大,很是適合chrome擴展的學習和練手。

當前,本次競賽的線上部分已經結束,經歷了幾個月的學習和使用,我也收穫的4個微信羣,全部羣內用戶近2000人。最高安裝量6000,最高惠及黨員80000餘人(一人一塊錢我就發了!)

最後,仍是感謝小茗同窗的教程。

以上!

相關文章
相關標籤/搜索