今天看有人發文章專門介紹Chrome外掛,我必須要說,外掛開發就是一個擺弄一個小玩具,第一要素是實用,其次是好玩。 單純羅列各種功能是非常無趣的。 所以把一篇舊文拿出來與大家分享。
人,活著就是為了賴皮。
作為一個合格的開發人員,把30%的時間用來賴皮(上班偷懶)是值得推薦的。
因為,如果你工作時間無法賴皮,並不能說明你工作認真,只能說明你的工作自動化程度不夠。
賴皮狗,一般會在上班時間瀏覽:SGamer論壇、虎撲論壇、鬥魚、BiliBili這一類的網站。
但在瀏覽過程中會遇到以下痛點:
- 老闆查崗,貼子或直播間開啟太多,不能及時關閉全部的賴皮站點。
- 老闆走了重新賴皮,不記得之前開啟的貼子或直播間在哪裡。
- 每次在瀏覽器裡輸入賴皮網址,打字真的很麻煩!
- 工作時開啟了太多標籤頁,休息時很難找到想要的賴皮頁面。
所以,我們需要:
簡單的一鍵賴皮外掛功能:
- 開啟瀏覽器後,一個快捷鍵,立即開啟賴皮頁面,喜滋滋開始賴皮的一天。
- 老闆/leader查崗時,一個快捷鍵,立即關閉所有賴皮頁面。
- 老闆走後,或工作一段時間後,一個快捷鍵,立即開啟原來的賴皮貼子和直播間。
簡單的一鍵賴皮外掛功能:
- 包含簡單的一鍵賴皮站點功能
- 能自定義配置賴皮網站。
- 上傳Google,釋出外掛。
從零開始,開發簡單的一鍵賴皮外掛
90%的上班族都在使用Chrome瀏覽器賴皮,所以我們選擇採用Chrome外掛來實現功能。
Chrome外掛沒什麼大不了的,依然還是採用HTML\CSS\JS的組合。
在這裡,我將手把手帶你從零開始製作外掛。
mainfest.json
就像node.js的package.json一樣,每一個外掛必須有一個manifest.json,作為最初配置檔案。
我們建立一個新的專案,在根目錄下建立manifest.json,填入以下程式碼
==mainfest.json==
{
"name": "上班一鍵賴皮工具",
"version": "0.1",
"description": "windows:按Alt+S開啟、關閉賴皮網站\nmac:按Control+S開啟、關閉賴皮網站",
"manifest_version": 2
}
複製程式碼
解釋一下:
- name: 外掛的名字
- version: 外掛的版本
- description: 外掛簡介欄
- manifest_version: 這個是寫死的,每個檔案必須有
接下來請右鍵儲存虎撲logo到根目錄,名字還是如apple-touch-icon.png
就行吧。
也可以點選 user-gold-cdn.xitu.io/2018/12/15/… 儲存圖片
修改mainfest.json,設定四個尺寸的icon都變成apple-touch-icon.png,以及外掛欄也顯示apple-touch-icon.png。
==mainfest.json==
{
"name": "上班一鍵賴皮工具",
"version": "0.1",
"description": "windows:按Alt+S開啟、關閉賴皮網站 \nmac:按Control+S開啟、關閉賴皮網站",
"icons": {
"16": "apple-touch-icon.png",
"32": "apple-touch-icon.png",
"48": "apple-touch-icon.png",
"128": "apple-touch-icon.png"
},
"browser_action": {
"default_icon": "apple-touch-icon.png",
"default_popup": "popup.html"
},
"commands": {
"toggle-tags": {
"suggested_key": {
"default": "Alt+S",
"mac": "MacCtrl+S"
},
"description": "Toggle Tags"
}
},
"manifest_version": 2
}
複製程式碼
解釋一下:
icons
: 配置了顯示在不同地方的圖示browser_action
: 即右上角外掛,browser_action > default_icon
即右上角外掛圖示commands
:一般用於快捷鍵命令。commands > toggle-tags > suggested_key
之下,設定了快捷鍵,只要按下快捷鍵,即會向Chrome就會向後臺釋出一個command
,值為toggle-tags
。
在windows環境下,我們將快捷鍵設定成
Alt+S
,在mac環境下,我們將快捷鍵設定成Control+S
,配置檔案中寫作MacCtrl+S
現在我們有了命令,就需要後臺指令碼接收,在mainfest.json中新增後臺指令碼: ==mainfest.json==
...
"background": {
"scripts": [
"background.js"
]
}
...
複製程式碼
並在根目錄下建立background.js. ==background.js==
chrome.commands.onCommand.addListener(function(command) {
alert(command)
console.log(command)
})
複製程式碼
現在我們的目錄結構如下:
├── manifest.json
└── background.js
└── sgamers.png
複製程式碼
在chrome內載入外掛
點選Chorme右上角的三個點按鈕...
> More Tools
> Extensions
在右上角把Developer mode開啟
再找到頂部的LOAD UNPACKED
,把專案的根目錄匯入進去
專案匯入後會出現一個新的卡片,是這個效果:
這時,你如果在Windows中按下Alt+S
就會彈出訊息,訊息為toggle-tags
,正好是我們在mainfest.json中定義好的。
同時我們可以點選上圖藍色鍵頭所指示的background page
,開啟一個除錯工具,可以看到toggle-tags
的輸出。
我們在之後本地編輯外掛後,可以按灰色鍵頭所指的重新整理,新功能就能立即重新整理載入了!
有了這些工作,意味著你可以進入下一步了!
標籤頁配置
一鍵開啟/關閉賴皮網站,實現原理其實就是chrome的標籤頁功能。
標籤頁功能訪問需要在manifest.json中新增許可權 ==mainfest.json==
...
"permissions": ["tabs"]
...
複製程式碼
接下來,我們寫一下background.js實現通過快捷鍵(windows的Alt+S,或mac的Ctrl+S)建立新的主頁: ==background.js==
// 輸入你想要的網站主頁
const MainPageUrl = 'http://https://bbs.hupu.com/all-gambia'
chrome.commands.onCommand.addListener(function (command) {
if (command === 'toggle-tags') {
chrome.tabs.create({"url": MainPageUrl, "selected": true});
}
})
複製程式碼
其實實現很簡單,就是呼叫chrome.tabs.create介面,就建立了一個新的標籤頁。 重新整理一下外掛,再試一試快捷鍵功能————是不是已經能控制瀏覽器彈出標籤 頁了!
實現具體邏輯:
稍顯複雜的地方是標籤頁isOpen狀態的處理, 下圖主要關注isOpen狀態的變化,以及tabCache的值變化。
graph TD
A(開始步驟:判斷isOpen狀態)-->|true| Y(情形1:清空tabCache快取)
Y-->B(關閉所有符合域名的標籤頁)
A-->|false| C(情形2:檢查標籤頁狀態)
B --> X(將關閉的標籤存入tabCache快取陣列)
X --> D(將isOpen狀態改為false)
C --> |tabCache快取有資料|E(把tabCache快取的所有標籤開啟)
C --> |tabCache快取沒資料|F(檢視是否有域名內標籤)
F --> |沒有域名內標籤|G(新標籤頁開啟主頁)
F --> |有域名內標籤|H(檢視當前頁是否在域名內)
G --> I(將isOpen狀態改為true)
H --> |當前標籤頁不是域名內標籤|K(定位到最近開啟的域名內頁面)
H --> |當前標籤頁就在域名內|J(關閉所有符合標籤標籤頁)
E --> L(將isOpen狀態改為true)
K --> M(將isOpen狀態改為true)
J --> N(將isOpen狀態設定為false)
複製程式碼
具體後臺邏輯如下,可以跟據備註、對照流程圖進行理解:
//初始化isOpen和tabCache狀態
let isOpen = false
let tabCache = []
//新標籤開啟的主頁
const mainPageUrl = 'https://bbs.hupu.com/all-gambia'
//四個賴皮網站的正則匹配表示式
const myPattern = 'sgamer\.com/|douyu\.com|hupu\.com|bilibili\.com'
//當前頁面的Url
let currentPageUrl = ''
/**
* 開始步驟: 判斷isOpen狀態
* 情形一:isOpen為true,則移除頁面
* 情形二:isOpen為false,則過載頁面
*/
chrome.commands.onCommand.addListener(function (command) {
if (command === 'toggle-tags') {
if (isOpen) {
//情形一:isOpen為true
removePages(myPattern)
//情形二:isOpen為false
} else {
reloadPages(myPattern, mainPageUrl)
}
}
})
/**
* 情形1:移除頁面
* 1、清空tabCache快取
* 2、關閉所有域名內標籤
* 3、將關閉的標籤存入tabCache快取陣列
* 4、將isOpen狀態改為false
*/
function removePages(patternStr) {
tabCache = []
chrome.tabs.query({active: true}, function (tab) {
currentPageUrl = tab[0].url
})
let pattern = new RegExp(patternStr)
walkEveryTab(function (tab) {
if (pattern.test(tab.url)) {
chrome.tabs.remove(tab.id,function(){
tabCache.push(tab.url)
})
}
},function(){
isOpen = false
})
}
/**
* 情形2:過載頁面
* 判斷有沒有快取:
* 情形2-1無快取:開啟新標籤或定位到域名內的標籤
* 情形2-2有快取:開啟全部快取內的頁面
*/
function reloadPages(patternStr, mainPageUrl) {
if (tabCache.length === 0) {
focusOrCreateTab(patternStr, mainPageUrl)
} else {
openAllCachedTab(tabCache)
}
}
/**
* 情形2-1:開啟新標籤或定位到域名內的標籤
* 1、遍歷全部標籤,記錄符合域名的標籤的url,以及最後一個標籤頁
* 2、如果沒有符合域名的標籤,則建立主頁,並將isOpen狀態改為true
* 3、如果有符合域名的標籤:
* 1、獲取當前的頁面url
* 2、如果當前頁面url不符合域名,則定位到這個標籤頁,將isOpen狀態改為true
* 3、如果當前頁面url符合域名,則關閉所有標籤頁(按情形1處理),將isOpen狀態改為false
*/
function focusOrCreateTab(patternStr, url) {
let pattern = new RegExp(patternStr)
let theTabs = []
let theLastTab = null
walkEveryTab(function (tab) {
if (pattern.test(tab.url)) {
theTabs.push(tab.url)
theLastTab = tab
}
}, function () {
if (theTabs.length > 0) {
chrome.tabs.query({active: true}, function (tab) {
let currentUrl = tab[0].url
if (theTabs.indexOf(currentUrl) > -1) {
removePages(patternStr)
isOpen = false
} else {
chrome.tabs.update(theLastTab.id, {"selected": true});
isOpen = true
}
})
} else {
chrome.tabs.create({"url": url, "selected": true});
isOpen = true
}
}
)
}
/**
* 情形2-2:
* 1、把tabCache所有標籤頁重新開啟
* 2、將isOpen狀態改為true
*/
function openAllCachedTab(tabCache) {
let focusTab = null
tabCache.forEach(function (url, index) {
chrome.tabs.create({'url': url}, function (tab) {
if (tab.url === currentPageUrl) {
focusTab = tab.id
}
if (index === tabCache.length-1 - 1) {
if (focusTab) {
chrome.tabs.update(focusTab, {"selected": true},function(){
});
}
}
})
})
isOpen = true
}
/**
*
* @param callback
* @param lastCallback
* 包裝一下遍歷全部標籤的函式,建立兩個回撥。
* 一個回撥是每一次遍歷的過程中就執行一遍。
* 一個回撥是全部遍歷完後執行一遍。
*/
function walkEveryTab(callback, lastCallback) {
chrome.windows.getAll({"populate": true}, function (windows) {
for (let i in windows) {
let tabs = windows[i].tabs;
for (let j in tabs) {
let tab = tabs[j];
callback(tab)
}
}
if(lastCallback) lastCallback()
})
}
複製程式碼
上傳與釋出外掛
我們需要在Chrome的開發者中心釋出外掛,進入 Developer Dashboard
好了,一個簡單易用的上班賴皮外掛做好了!在除錯模式下,你可以用ctrl+s來快捷尋找、開啟、關閉、重新開啟賴皮頁面。隨時隨地、全方位賴皮,從容面對老闆查崗。
可配置的高階賴皮外掛
現在我希望我的外掛都可以隨時配置站點:
那麼就需要用到chrome.storage了。
你需要開啟storage許可權:
manifest.json內新增
...
"permissions": [
"tabs","storage"
],
...
複製程式碼
然後使用
chrome.storage.local.set({
'value1':theValue1,
'value2',theValue2
})
複製程式碼
這種形式來存放storage。 這後使用
chrome.storage.local.get(['value1'],(res)=>{
const theValue1 = res.value1
})
複製程式碼
這樣得到存放的value值。
開始改寫background.js
我們把mainPageUrl
和myPattern
改成從storage中獲取。
const INIT_SITES_LIST = ['bilibili.com','douyu.com','sgamer.com','hupu.com']
const INIT_MAIN_PAGE = 'https://bbs.hupu.com/all-gambia'
// 在安裝時即設定好storage
chrome.runtime.onInstalled.addListener(function() {
chrome.storage.local.set({
sites: INIT_SITES_LIST,
mainPage:INIT_MAIN_PAGE
})
});
//初始化isOpen和tabCache狀態
let isOpen = false
let tabCache = []
let currentPageUrl = ''
/**
* 開始步驟: 判斷isOpen狀態
* 情形一:isOpen為true,則移除頁面
* 情形二:isOpen為false,則過載頁面
*/
chrome.commands.onCommand.addListener(function (command) {
if (command === 'toggle-tags') {
chrome.storage.local.get(['sites','mainPage'],function(res){
let sites = res.sites
let mainPageUrl = res.mainPage
let myPattern = sites.map(item=>item.replace('.','\\.')).join('|')
console.log(myPattern)
if (isOpen) {
//情形一:isOpen為true
removePages(myPattern)
//情形二:isOpen為false
} else {
reloadPages(myPattern, mainPageUrl)
}
})
}
})
// ======================== 下面的部分不需要改動,看到這裡就夠了)
/**
* 情形1:移除頁面
* 1、清空tabCache快取
* 2、關閉所有域名內標籤
* 3、將關閉的標籤存入tabCache快取陣列
* 4、將isOpen狀態改為false
*/
function removePages(patternStr) {
tabCache = []
chrome.tabs.query({active: true}, function (tab) {
currentPageUrl = tab[0].url
})
let pattern = new RegExp(patternStr)
walkEveryTab(function (tab) {
if (pattern.test(tab.url)) {
chrome.tabs.remove(tab.id,function(){
tabCache.push(tab.url)
})
}
},function(){
isOpen = false
})
}
/**
* 情形2:過載頁面
* 判斷有沒有快取:
* 情形2-1無快取:開啟新標籤或定位到域名內的標籤
* 情形2-2有快取:開啟全部快取內的頁面
*/
function reloadPages(patternStr, mainPageUrl) {
if (tabCache.length === 0) {
focusOrCreateTab(patternStr, mainPageUrl)
} else {
openAllCachedTab(tabCache)
}
}
/**
* 情形2-1:開啟新標籤或定位到域名內的標籤
* 1、遍歷全部標籤,記錄符合域名的標籤的url,以及最後一個標籤頁
* 2、如果沒有符合域名的標籤,則建立主頁,並將isOpen狀態改為true
* 3、如果有符合域名的標籤:
* 1、獲取當前的頁面url
* 2、如果當前頁面url不符合域名,則定位到這個標籤頁,將isOpen狀態改為true
* 3、如果當前頁面url符合域名,則關閉所有標籤頁(按情形1處理),將isOpen狀態改為false
*/
function focusOrCreateTab(patternStr, url) {
let pattern = new RegExp(patternStr)
let theTabs = []
let theLastTab = null
walkEveryTab(function (tab) {
if (pattern.test(tab.url)) {
theTabs.push(tab.url)
theLastTab = tab
}
}, function () {
if (theTabs.length > 0) {
chrome.tabs.query({active: true}, function (tab) {
let currentUrl = tab[0].url
if (theTabs.indexOf(currentUrl) > -1) {
removePages(patternStr)
isOpen = false
} else {
chrome.tabs.update(theLastTab.id, {"selected": true});
isOpen = true
}
})
} else {
chrome.tabs.create({"url": url, "selected": true});
isOpen = true
}
}
)
}
/**
* 情形2-2:
* 1、把tabCache所有標籤頁重新開啟
* 2、將isOpen狀態改為true
*/
function openAllCachedTab(tabCache) {
let focusTab = null
tabCache.forEach(function (url, index) {
chrome.tabs.create({'url': url}, function (tab) {
if (tab.url === currentPageUrl) {
focusTab = tab.id
}
if (index === tabCache.length-1 - 1) {
if (focusTab) {
chrome.tabs.update(focusTab, {"selected": true},function(){
});
}
}
})
})
isOpen = true
}
/**
*
* @param callback
* @param lastCallback
* 包裝一下遍歷全部標籤的函式,建立兩個回撥。
* 一個回撥是每一次遍歷的過程中就執行一遍。
* 一個回撥是全部遍歷完後執行一遍。
*/
function walkEveryTab(callback, lastCallback) {
chrome.windows.getAll({"populate": true}, function (windows) {
for (let i in windows) {
let tabs = windows[i].tabs;
for (let j in tabs) {
let tab = tabs[j];
callback(tab)
}
}
if(lastCallback) lastCallback()
})
}
複製程式碼
那麼我們可以寫一個popup頁面,如果點選圖示就會顯示,如圖:
下面我們完善一下popup.html和popup.css和pupup.js頁面
所有js檔案都可以直接呼叫chrome.storage.local.get
只需要特別注意一下js檔案的chrome.storage
呼叫部分
其它的拷貝即可,我們不是來學頁面佈局的
popup.html
<html>
<head>
<title>常用網站配置頁面</title>
<link rel="stylesheet" href="popup.css">
</head>
<body>
<div class="container">
<h2 class="lapi-title">常用賴皮站點域名</h2>
<ul class="lapi-content">
</ul>
<p>
<label><input type="text" id="add" class="add"></label>
<button class="button add-button ">+</button>
</p>
<p></p>
<p></p>
<p></p>
<h2>我的賴皮主頁</h2>
<div id="change-content">
<span class="main-page-inactive" id="main-page-inactive"></span><button class="button change-button " id="change">✎</button>
</div>
<p class="lapi-tip">按<span class="lapi-key">Alt+S</span>快速開啟/關閉賴皮站點</p>
</div>
<script src="zepto.min.js"></script>
<script src="popup.js"></script>
</body>
</html>
複製程式碼
popup.css
* {
margin: 0;
padding: 0;
color:#6a6f77;
}
input, button, select, textarea {
outline: none;
-webkit-appearance: none;
border-radius: 0;
border: none;
}
input:focus{
list-style: none;
box-shadow: none;
}
ol, ul {
list-style: none;
}
li{
margin: 5px 0;
}
.container {
width: 200px;
padding: 10px;
}
.container h2{
margin: 10px;
text-align: center;
display: block;
}
.lapi-content li{
transition: opacity 1s;
}
.site{
cursor: pointer;
color: #00b0ff;
}
.add, .main-page{
box-sizing: border-box;
text-align:center;
font-size:14px;
/*height:27px;*/
border-radius:3px;
border:1px solid #c8cccf;
color:#6a6f77;
outline:0;
padding:0 10px;
text-decoration:none;
width: 170px;
}
#main-page{
font-size: 12px;
text-align: left;
width: 166px;
margin: 0;
padding: 2px;
}
.add{
height: 27px;
}
.main-page{
width: 170px;
outline: none;
resize: none;
}
.main-page-inactive{
width: 160px;
line-break: auto;
word-break: break-word;
overflow: hidden;
display: inline-block;
cursor: pointer;
color: #00b0ff;
margin: 3px;
}
.button{
font-size: 16px;
/*border: 1px solid #c8cccf;*/
color: #c8cccf;
/*border: none;*/
padding: 0 4px 1px 3px;
border-radius: 3px;
}
.close-button{
transition: all 1s;
}
.button:hover{
background: #E27575;
color: #FFF;
}
.add-button{
transition:all 1s;
font-size: 20px;
padding: 0 6px 1px 5px;
}
.change-button{
position: absolute;
transition:all 1s;
font-size: 20px;
padding: 0 6px 1px 5px;
}
.change-button:hover{
background: #f9a825;
color: #FFF;
}
#change-check{
color: #f9a825;
}
#change-check:hover{
color: #fff;
}
.add-button:hover{
background: #B8DDFF;
color: #FFF;
}
.submit{
transition: all 1s;
margin: 10px;
padding: 5px 10px;
font-size: 16px;
border-radius: 4px;
background: #B8DDFF;
border: 1px solid #B8DDFF;
color: #FFF;
}
.submit:hover{
border: 1px solid #B8DDFF;
background: #fff;
color: #B8DDFF;
}
.fade{
opacity: 0;
}
.add-wrong,.add-wrong:focus{
border: #e91e63 1px solid;
box-shadow:0 0 5px rgba(233,30,99,.3);
}
.lapi-tip{
margin-top: 20px;
border-top: 1px solid #c8cccf;
padding-top: 8px;
color: #c8cccf;
text-align: center;
}
.lapi-key{
color: #B8DDFF;
}
複製程式碼
重點關注:chrome.storage
部分
popup.js
let sites = []
let mainPage = ''
const isMac = /Macintosh/.test(navigator.userAgent)
let $lapiKey = $('.lapi-key')
isMac? $lapiKey.text('Control+S'):$lapiKey.text('Alt+S')
// 從storage中取出site和mainPage欄位,並設定在頁面上。
chrome.storage.local.get(['sites','mainPage'], function (res) {
if (res.sites) {
sites = res.sites
mainPage = res.mainPage
sites.forEach(function (item) {
let appendEl = '<li><span class="site">' + item + '</span>\n' +
'<button class="button close-button">×</button>\n' +
'</li>'
$('ul.lapi-content').append(appendEl)
})
}
$('#main-page').val(mainPage)
$('#main-page-inactive').html(mainPage)
})
$('#save').on('click', function () {
alert()
})
$('#change-content').delegate('#main-page-inactive','click',function(){
let mainPageUrl = $(this).html()
if(/^http:\/\/|^https:\/\//.test(mainPageUrl)){
chrome.tabs.create({"url": mainPageUrl, "selected": true})
}else{
chrome.tabs.create({"url": 'http://'+mainPageUrl, "selected": true})
}
})
let addEl = $('#add')
addEl.focus()
let lapiCon = $('ul.lapi-content')
lapiCon.delegate('.close-button', 'click', function () {
let $this = $(this)
let siteValue = $this.siblings().html()
sites = sites.filter(function (item) {
return item !== siteValue
})
chrome.storage.local.set({sites: sites})
$this.parent().addClass('fade')
setTimeout(function () {
$this.parent().remove()
}, 800)
})
$('.add-button').on('click',addEvent)
addEl.bind('keypress',function(event){
if(event.keyCode === 13) addEvent()
})
function addEvent(){
if(!validate(addEl.val())){
addEl.addClass('add-wrong')
}else{
let appendEl = '<li><span class="site">' + addEl.val() + '</span>\n' +
'<button class="button close-button">×</button>\n' +
'</li>'
$('ul.lapi-content').append(appendEl)
sites.push(addEl.val())
chrome.storage.local.set({sites:sites})
addEl.removeClass('add-wrong')
addEl.focus().val('')
}
}
function validate(value){
value = value.trim()
if(value.length ===0){
return false
}
return /^([\w_-]+\.)*[\w_-]+$/.test(value)
}
lapiCon.delegate('.site','click',function(){
let siteUrl = $(this).html()
chrome.tabs.create({"url": 'http://'+siteUrl, "selected": true})
})
$('#change-content').delegate('#change','click',function(){
changeMainPage($(this))
}).delegate('#change-check','click',function(){
changeCheck($('#change-check'))
}).delegate('#main-page','blur',function(){
changeCheck($('#change-check'))
})
function changeMainPage($this){
$this.siblings().remove()
$this.parent().prepend('<label><textarea id="main-page" class="main-page"></textarea></label>')
$this.parent().append('<button class="button change-button " id="change-check">✓</button>')
$('#main-page').val(mainPage).focus()
$this.remove()
}
function changeCheck($this){
let mainPageVal = $('#main-page').val()
$this.siblings().remove()
$this.parent().prepend('<span class="main-page-inactive" id="main-page-inactive"></span>')
$('#main-page-inactive').text(mainPageVal)
chrome.storage.local.set({mainPage:mainPageVal})
$this.parent().append('<button class="button change-button " id="change">✎</button>')
}
複製程式碼
好了,一個優雅的賴皮外掛就做好了,大家可以檢視 github.com/wanthering/…
你你你,別老躲廁所玩手機了,臭!同樣賴著把工資掙了,我們們賴皮外掛用起來!
祝大家上班賴得開心。