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

来源:https://www.cnblogs.com/cifang/archive/2018/06/04/9133868.html
-Advertisement-
Play Games

1、寫在前面 首先感謝小茗同學的文章-【乾貨】Chrome插件(擴展)開發全攻略, 基於這篇入門教程和demo,我才能寫出這款 基於chrome擴展的自動答題器。 git地址: https://gitee.com/cifang/lighthouse_answering_machine.git 2、開 ...


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                 //回調函數
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
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.前言 學習Vue前端框架已經一個月了,作為一個web剛入門的菜鳥,在學習的過程中,網上有些技術博客往往沒有什麼可以借鑒的地方,在這裡 我特意將我從開始一直到登錄的過程記錄下來。希望看到我的文章的朋友們能夠少走點彎路,不喜勿噴喲! 2.開發依賴工具 2.1 node.js 2.2 git 2.3  ...
  • Javascript: 網頁可見區域寬: document.body.clientWidth 網頁可見區域高: document.body.clientHeight 網頁可見區域寬: document.body.offsetWidth (包括邊線的寬) 網頁可見區域高: document.body. ...
  • 各位前輩好,如題,不知道是HTML解析順序造成的,還是JS預編譯的結果(見註釋)。 煩請各位前輩進行指導。 ...
  • CSS學習摘要 定位 === 註:全文摘自 "MDN CSS定位" 定位允許您從正常的文檔流佈局中取出元素,並使它們具有不同的行為,例如放在另一個元素的上面,或者始終保持在瀏覽器視窗內的同一位置。 本文解釋的是定位( " " )的各種不同值,以及如何使用它們。 文檔流 定位是一個相當複雜的話題,所以 ...
  • 一、jQuery介紹 二、jQuery的優勢 三、jQuery版本 1.x:相容IE678,使用最為廣泛的,官方只做BUG維護,功能不再新增。因此一般項目來說,使用1.x版本就可以了,最終版本:1.12.4 (2016年5月20日) 2.x:不相容IE678,很少有人使用,官方只做BUG維護,功能不 ...
  • 初學之初需要 安裝nodejs軟體,在vscode中安裝vue和webpack即可,但是經常遇到問題, 集中體現ESlint之中,如自閉合標簽的問題,paeseing,和控制台提示ESlint來ignore的問題 所以用cnpm安裝,用腳手架搭建 https://segmentfault.com/a ...
  • 1 2 3 4 5 6 14 15 16 17 18 19 java 20 21 單價... ...
  • CSS頁面佈局技術允許我們拾取網頁中的元素,並且控制它們相對正常佈局流、周邊元素、父容器或者主視口/視窗的位置。在這個模塊中將涉及更多關於頁面[佈局技術](https://developer.mozilla.org/zh-CN/docs/Web/CSS/Layout_mode)的細節: * 浮... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...