├── README.md ├── data.json ├── doc └── README_CN.md └── plugins ├── bishijie.js ├── chromeflags.js ├── coin.js ├── console.js ├── cryptos.js ├── douban.book.js ├── douban.movie.js ├── douban.music.js ├── epl.js ├── fbrank.js ├── firefox.js ├── hotrank.js ├── ipsearch.js ├── jenkins.js ├── juejin.js ├── newtabsrc.js ├── queryzmzhot.js ├── restartext.js ├── runcommand.js ├── sspai.js ├── stock.js ├── tabplus.js ├── theme.js ├── times.js ├── userscripts.js ├── wiki.js ├── windowtabs.js └── zimuzu.js /README.md: -------------------------------------------------------------------------------- 1 | [中文](./doc/README_CN.md) 2 | 3 | This is the official plugin repository for [Steward](https://github.com/solobat/Steward) 4 | 5 | ## Install Steward: 6 | - [Steward](https://chrome.google.com/webstore/detail/dnkhdiodfglfckibnfcjbgddcgjgkacd) 7 | - [Browser Alfred -- Without NewTab](https://chrome.google.com/webstore/detail/browser-alfred-a-command/jglmompgeddkbcdamdknmebaimldkkbl) 8 | - [Offline](http://owsjc7iz3.bkt.clouddn.com/steward-3.5.5.crx)。 9 | 10 | --- 11 | 12 | Plugins are a core feature of Steward, and plugins make it quick and easy to do most of your daily browser operations. 13 | 14 | Steward comes with a series of plugins, and also provides the plugin api, you can complete the development of the plugin you want with just a few lines of code. 15 | 16 | ## Plugins in Steward 17 | Steward comes with plugins that can be divided into the following 4 categories. 18 | 19 | - Browser plugin -- Convenience and enhancements to browser features such as bookmarks, history, etc. 20 | - Extension's plugins -- Control other browser plugins via Steward, such as word cards 21 | - Steward plugin -- Manage the features of Steward itself through plugins, such as NewTab, Wallpaper, etc. 22 | - Other plugins -- Browser-independent plugins such as URL Block, Pocket Query, TODOList, etc. 23 | 24 | ## Plugin development 25 | Since V3.5.1, Steward has opened the api and provided the plugin editor. 26 | 27 | [Steward Plugins Repo](https://github.com/Steward-launcher/steward-plugins) 28 | 29 | You can add help documentation for your plugin in [this repository](https://github.com/Steward-launcher/steward-documents). 30 | 31 | ### Data structure 32 | - `steward` Object 33 | 34 | Plugin code is wrappered with `module.exports = function(steward) {}`,`steward` is the `api` 35 | 36 | ```javascript 37 | { 38 | mode, // steward application mode: 'newTab' | 'popup' | 'content' 39 | config, // user config 40 | data, // steward data: { page: Object } 41 | chrome, // chrome api 42 | util, // 43 | dayjs, // date library 44 | $, // jquery 45 | axios, // http library 46 | constant, // 47 | storage, // promise style 48 | browser // https://github.com/mozilla/webextension-polyfill 49 | } 50 | ``` 51 | 52 | - `plugin` 53 | 54 | ```javascript 55 | { 56 | author, // id of author,such as your email or github account 57 | version, // version 58 | name, // plugin name,componse the uniqe id with `author` 59 | category: 'other', // category of plugin,just use `other` 60 | icon, // plugin icon 61 | title, // plugin title 62 | commands, // commaneds of plugin, if null, means a plugin of search type 63 | onInit, // triggered when plugin is first used 64 | onInput, // core api, triggered when user enters 65 | onEnter, // core api,triggered when user clicks one item or press enter / return 66 | onLeave // triggered when another commaned will be applyed 67 | } 68 | 69 | ``` 70 | 71 | - `command` data structure 72 | 73 | ```javascript 74 | { 75 | // command 76 | key, // { String } trigger of command 77 | type, // { String } type of command, enums,includes ['always', 'regexp', 'keyword', 'other', 'search'] 78 | // For different query types and stages, usually fill in `keyword` 79 | title, // { String } title of command 80 | subtitle, // { String } subtitle or description of command 81 | icon // { String } icon of command 82 | } 83 | ``` 84 | 85 | - Query result `item` data structure 86 | 87 | ```javascript 88 | { 89 | // item 90 | key, // { String} item type: `CONSTANT.BASE.ITEM_TYPE`,enums, includes ['plugins', 'url', 'copy', 'action', 'app'] 91 | // When the value is `plugins`, apply `item.id` as the new command 92 | // When the value is `url`,open `item.url` in a new tab 93 | // When the value is `copy`, copy `item.title` to the clipboard 94 | // When the value is `action`,emit 'action' event,usually processed by the page mode, todo 95 | // When the value is `app`,emit ''app:handle' event,such as 'Backup' 96 | universal, // { Boolean } Whether or not be universal item,when `true`,Steward will handle the item clicked by user 97 | icon, // { String } item icon url,usually the same as the plugin / commands icon 98 | title, // { String } 99 | desc // { String } 100 | } 101 | 102 | ``` 103 | 104 | ### functions 105 | 106 | #### plugin 107 | - `onInput` 108 | 109 | > core api,triggered by user input trigger + space 110 | 111 | ```javascript 112 | /** 113 | * @param { String } query User-entered string 114 | * @param { Object } command The command currently triggered by the user 115 | * @return { Promise[Array] | Array } Query results, support Promise and normal Array array 116 | */ 117 | function onInput(query, command) { 118 | return Promise.resolve([Array[Item]]); 119 | } 120 | ``` 121 | 122 | - `onEnter` 123 | 124 | > core api,Called when the user presses Enter/Return or clicks on a query result 125 | 126 | ```javascript 127 | /** 128 | * @param { Object } item Selected query result entry 129 | * @param { Object } command Currently triggered command 130 | * @param { String } query Current query string 131 | * @param { Object } keyStatus Include ctrlKey / metaKey / altKey / shiftKey 132 | * @param { Array } list All query results 133 | * @return { Promise[String | Boolean]} 134 | * Promise[String] Applied as a new command to the input box 135 | * Promise[Boolean] Only valid for page mode, Steward frame will be delayed when Boolean is false or Number value 136 | */ 137 | function onEnter(item, command, query, keyStatus, list) { 138 | return Promise.resolve([String | Boolean | Number]); 139 | } 140 | ``` 141 | 142 | #### steward.util 143 | - `createTab` 144 | ```javascript 145 | /** 146 | * create a new tab depending on the `keyStatus` 147 | * @param { Object } item from onEnter 148 | * @param { Object } keyStatus from onEnter 149 | */ 150 | function createTab(item, keyStatus) { 151 | // tabs.update() or tabs.create() 152 | } 153 | ``` 154 | 155 | 156 | - `getDefaultResult` 157 | 158 | ```javascript 159 | /** 160 | * Generate default query results based on command 161 | * @param { Object } command Command defined in plugin 162 | * @return { Array[Item] } Contains a default query result that is transparent to onEnter 163 | */ 164 | function getDefaultResult(command) { 165 | } 166 | ``` 167 | 168 | - `getEmptyResult` 169 | ```javascript 170 | /** 171 | * Generate default empty query results based on command 172 | * @param { Object } command Command defined in plugin 173 | * @param { String | Optional } msg Empty query tip 174 | * @return { Array[Item] } Contains a default empty query result that is transparent to onEnter 175 | */ 176 | function getEmptyResult(command, msg) { 177 | } 178 | ``` 179 | 180 | - `copyToClipboard` 181 | 182 | ```javascript 183 | /** 184 | * Copy text to the clipboard 185 | * @param { String } text The text to be copied 186 | * @param { Boolean } showMsg Whether to pop up a prompt 187 | * @return 188 | */ 189 | function copyToClipboard(text, showMsg) { 190 | } 191 | ``` 192 | 193 | - `getParameterByName` 194 | 195 | ```javascript 196 | /** 197 | * @param { String } name param name 198 | * @param { String } search default value: `window.location.search` 199 | * @return { String } 200 | */ 201 | function getParameterByName(name, search = window.location.search) { 202 | } 203 | ``` 204 | 205 | - `toast` -- https://github.com/CodeSeven/toastr -------------------------------------------------------------------------------- /data.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | { 4 | "version": 1, 5 | "name": "douban music", 6 | "icon": "https://www.douban.com/favicon.ico", 7 | "title": "在 music.douban.com 查找音乐", 8 | "subtitle": "按 Enter / Return 跳转到豆瓣音乐网页", 9 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/douban.music.js", 10 | "author": "lifesign" 11 | }, 12 | { 13 | "version": 2, 14 | "name": "douban book", 15 | "icon": "https://www.douban.com/favicon.ico", 16 | "title": "在 book.douban.com 查找书籍", 17 | "subtitle": "按 Enter / Return 跳转到豆瓣读书网页", 18 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/douban.book.js", 19 | "author": "solobat" 20 | }, 21 | { 22 | "version": 2, 23 | "name": "douban movie", 24 | "icon": "https://www.douban.com/favicon.ico", 25 | "title": "在 movie.douban.com 查找电影", 26 | "subtitle": "按 Enter / Return 跳转到豆瓣电影网页", 27 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/douban.movie.js", 28 | "author": "solobat" 29 | }, 30 | { 31 | "version": 1, 32 | "name": "Epl Competitions", 33 | "icon": "http://staticsports.pplive.cn/2018/interim/app/pc_league/static/v_20180905135831/images/yc_logo.png", 34 | "title": "英超赛事查询", 35 | "subtitle": "英超最近比赛查询,按 Enter 键打开相应的比赛页面,数据来自 PPTV", 36 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/epl.js", 37 | "author": "solobat" 38 | }, 39 | { 40 | "version": 1, 41 | "name": "Soccer Rank", 42 | "icon": "http://img1.gtimg.com/sports/pics/hv1/196/95/2276/148021321.png", 43 | "title": "足球积分榜查询", 44 | "subtitle": "积分榜,数据来自腾讯体育", 45 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/fbrank.js", 46 | "author": "solobat" 47 | }, 48 | { 49 | "version": 1, 50 | "name": "IP Search", 51 | "icon": "http://static.oksteward.com/ip.png", 52 | "title": "查询 ip", 53 | "subtitle": "输入 ipv4 地址,查询 ip 所在地点及运营商", 54 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/ipsearch.js", 55 | "author": "solobat" 56 | }, 57 | { 58 | "version": 3, 59 | "name": "zimuzu movie", 60 | "icon": "http://static.oksteward.com/icon-movie.png", 61 | "title": "在 zimuzu.io 查找电影", 62 | "subtitle": "按 Enter / Return 跳转到 zimuzu 网页", 63 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/zimuzu.js", 64 | "author": "solobat" 65 | }, 66 | { 67 | "version": 3, 68 | "name": "User Scripts", 69 | "icon": "https://greasyfork.org/assets/blacklogo96-e0c2c76180916332b7516ad47e1e206b42d131d36ff4afe98da3b1ba61fd5d6c.png", 70 | "title": "根据 host 查找 userscripts", 71 | "subtitle": "数据来自 https://greasyfork.org/", 72 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/userscripts.js", 73 | "author": "solobat" 74 | }, 75 | { 76 | "version": 3, 77 | "name": "Chrome Flags", 78 | "icon": "https://i.imgur.com/zU9XGzr.png", 79 | "title": "Chrome Flags", 80 | "platform": "chrome", 81 | "subtitle": "查找并打开 chrome flags 设置", 82 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/chromeflags.js", 83 | "author": "solobat" 84 | }, 85 | { 86 | "version": 3, 87 | "name": "Tabs in window", 88 | "icon": "https://i.imgur.com/QcoukjA.png", 89 | "title": "Manage tabs in the window", 90 | "subtitle": "Detach a tab and also to attach all tabs from a window.", 91 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/windowtabs.js", 92 | "author": "solobat" 93 | }, 94 | { 95 | "version": 3, 96 | "name": "Jenkins", 97 | "icon": "https://i.imgur.com/Xpgr4TK.png", 98 | "title": "Jenkins", 99 | "subtitle": "Find and open jenkins jobs", 100 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/jenkins.js", 101 | "author": "solobat" 102 | }, 103 | { 104 | "version": 4, 105 | "name": "Coin Market", 106 | "icon": "https://i.imgur.com/mmVqAlB.png", 107 | "title": "Query digital currency info", 108 | "subtitle": "Enter btc/eth/eos/ft, etc. to check their global prices...", 109 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/coin.js", 110 | "author": "solobat" 111 | }, 112 | { 113 | "version": 2, 114 | "name": "Times", 115 | "icon": "https://i.imgur.com/S8dvYkh.png", 116 | "title": "Add times", 117 | "subtitle": "Add times, the format is: {task}/{m|w|d}/{times},Enter times +1,Shift + Enter times -1", 118 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/times.js", 119 | "author": "solobat" 120 | }, 121 | { 122 | "version": 2, 123 | "name": "Hot Rank", 124 | "icon": "https://i.imgur.com/CDBL9t5.jpg", 125 | "title": "果汁排行榜", 126 | "subtitle": "数据来自 http://guozhivip.com", 127 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/hotrank.js", 128 | "author": "solobat" 129 | }, 130 | { 131 | "version": 2, 132 | "name": "Coin News", 133 | "icon": "https://www.bishijie.com/favicon.ico", 134 | "title": "币世界新闻", 135 | "subtitle": "按 Enter / Return 跳转到币世界网页", 136 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/bishijie.js", 137 | "author": "solobat" 138 | }, 139 | { 140 | "version": 3, 141 | "name": "sspai articles", 142 | "icon": "https://cdn.sspai.com/sspai/assets/img/favicon/icon.ico", 143 | "title": "少数派首页文章", 144 | "subtitle": "按 Enter / Return 跳转到少数派文章页", 145 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/sspai.js", 146 | "author": "solobat" 147 | }, 148 | { 149 | "version": 6, 150 | "name": "Stocks", 151 | "icon": "https://i.imgur.com/Qc7XZqB.png", 152 | "title": "股票行情", 153 | "subtitle": "数据来自新浪财经, 按 Enter / Return 跳转到少数派文章页", 154 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/stock.js", 155 | "author": "solobat" 156 | }, 157 | { 158 | "version": 5, 159 | "name": "Console", 160 | "icon": "https://i.imgur.com/Z2qwSNT.png", 161 | "title": "A simple console", 162 | "subtitle": "execute your javascript code and show the result", 163 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/console.js", 164 | "author": "solobat" 165 | }, 166 | { 167 | "version": 1, 168 | "name": "Juejin", 169 | "icon": "https://i.loli.net/2019/01/09/5c35880f1b8be.png", 170 | "title": "掘金文章搜索(0:推荐,1:前端,2:后端)", 171 | "subtitle": "按 Enter / Return 跳转到对应网页", 172 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/juejin.js", 173 | "author": "clearives" 174 | }, 175 | { 176 | "version": 1, 177 | "name": "Zimuzu hot movies", 178 | "icon": "https://ws1.sinaimg.cn/large/6836364ely1fzopj47on0j205k05kq2w.jpg", 179 | "title": "查看字幕组热门影视", 180 | "subtitle": "按 Enter / Return 跳转到对应网页", 181 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/queryzmzhot.js", 182 | "author": "solobat" 183 | }, 184 | { 185 | "version": 1, 186 | "name": "Wiki documents", 187 | "icon": "https://wx1.sinaimg.cn/large/6836364ely1ge0iyhz1zrj2074074jr6.jpg", 188 | "title": "View wiki documents", 189 | "subtitle": "press Enter / Return to open the url", 190 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/wiki.js", 191 | "author": "solobat" 192 | }, 193 | { 194 | "version": 1, 195 | "name": "NewTab Src", 196 | "icon": "https://wx1.sinaimg.cn/large/6836364ely1ge0j4vfvzfj203k03k3yb.jpg", 197 | "title": "Customize the URL of newtab", 198 | "subtitle": "Type reset to restore the default page", 199 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/newtabsrc.js", 200 | "author": "solobat" 201 | }, 202 | { 203 | "version": 3, 204 | "name": "Themes", 205 | "icon": "https://i.imgur.com/zU9XGzr.png", 206 | "title": "Switch theme", 207 | "subtitle": "Press return/enter to switch the status of the current theme", 208 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/theme.js", 209 | "author": "solobat" 210 | } 211 | , 212 | { 213 | "version": 3, 214 | "name": "Run Command", 215 | "icon": "https://i.imgur.com/iBTbPYs.png", 216 | "title": "Run Steward Helper Command", 217 | "subtitle": "Please install Steward Helper first", 218 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/runcommand.js", 219 | "author": "solobat" 220 | } 221 | , 222 | { 223 | "version": 1, 224 | "name": "Tabs Plus", 225 | "icon": "https://i.imgur.com/QcoukjA.png", 226 | "title": "Manage tabs in all the windows", 227 | "subtitle": "Manage tabs in all the windows", 228 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/tabplus.js", 229 | "author": "solobat" 230 | }, 231 | { 232 | "version": 2, 233 | "name": "Crypto Prices", 234 | "icon": "https://assets.coingecko.com/coins/images/1/small/bitcoin.png", 235 | "title": "Cryptocurrency prices", 236 | "subtitle": "List Cryptocurrency Prices", 237 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/cryptos.js", 238 | "author": "solobat" 239 | }, 240 | { 241 | "version": 1, 242 | "name": "restartext", 243 | "icon": "https://i.imgur.com/zU9XGzr.png", 244 | "title": "重启 Chrome 扩展", 245 | "subtitle": "重启指定的 Chrome 扩展", 246 | "source": "https://raw.githubusercontent.com/Steward-launcher/steward-plugins/master/plugins/restartext.js", 247 | "author": "solobat" 248 | } 249 | ] 250 | } 251 | -------------------------------------------------------------------------------- /doc/README_CN.md: -------------------------------------------------------------------------------- 1 | 从 V3.5.1 以后,Steward 开放了 api,并提供了 plugin 编辑器。 2 | 3 | ## 插件开发说明 4 | 你可以在[这个仓库](https://github.com/Steward-launcher/steward-documents)添加你的插件的帮助文档 5 | 6 | ### 数据结构 7 | - `steward` 对象 8 | 9 | 代码包裹在 `module.exports = function(steward) {}` 中,`steward` 为注入的 `api` 10 | 11 | ```javascript 12 | { 13 | mode, // steward application mode: 'newTab' | 'popup' | 'content' 14 | config, // user config 15 | data, // steward data: { page: Object } 16 | chrome, // chrome api 17 | util, // 工具 api 18 | dayjs, // 日期库 19 | $, // jquery 20 | axios, // http 库 21 | constant, // 常量 22 | storage // 支持 promise 的 chrome.storage 23 | browser // https://github.com/mozilla/webextension-polyfill 24 | } 25 | ``` 26 | 27 | - `plugin` 组成结构 28 | 29 | ```javascript 30 | { 31 | author, // 开发者 id,比如邮箱或 github 账号 32 | version, // 版本号 33 | name, // 插件名,与 author 一起组成唯一识别 id(uid) 34 | category: 'other', // 插件类别,填 ‘other’ 就好,暂时没用 35 | icon, // 插件 icon 36 | title, // 插件标题 37 | commands, // 插件命令列表,不能为空 38 | onInit, // 插件初次使用时调用 39 | onInput, // 核心 api, 输入事件函数 40 | onEnter, // 核心 api,选中事件函数 41 | onLeave // 调用其它 command 时触发 42 | } 43 | 44 | ``` 45 | 46 | - `command` 数据结构 47 | 48 | ```javascript 49 | { 50 | // command 51 | key, // { String } command 的 trigger 52 | type, // { String } command 的类型,枚举值,包含['always', 'regexp', 'keyword', 'other', 'search'] 53 | // 对应不同的查询类型及阶段,通常填 `keyword` 就好 54 | title, // { String } command 的标题 55 | subtitle, // { String } command 的副标题/描述 56 | icon // { String } command 的图标 url 57 | } 58 | ``` 59 | 60 | - 查询结果 `item` 数据结构 61 | 62 | ```javascript 63 | { 64 | // item 65 | key, // { String} 条目类型`CONSTANT.BASE.ITEM_TYPE`,枚举值有['plugins', 'url', 'copy', 'action', 'app'] 66 | // 为 plugins 时,将 item.id 作为新的命令应用 67 | // 为 url 时,在新标签页打开 item.url 68 | // 为 copy 时,将 item.title 拷贝到剪贴板 69 | // 为 action 时,emit 'action' 事件,通常由页面模式的 website 接收处理,此处文档待完善 70 | // 为 app 时,emit ''app:handle' 事件,比如 'Backup' 备份 71 | universal, // { Boolean } 是否为通用条目,为 true 时,onEnter 将由 Steward 根据item.key 值处理 72 | icon, // { String } 条目图标 url,通常与 plugin / commands 图标相同 73 | title, // { String } 条目标题 74 | desc // { String } 条目副标题/描述 75 | } 76 | 77 | ``` 78 | 79 | ### 函数方法 80 | 81 | #### plugin 82 | - `onInput` 83 | 84 | > 核心 api,用户输入 trigger + space 后触发 85 | 86 | ```javascript 87 | /** 88 | * @param { String } query 用户输入的字符串,比如输入框中为 `ip 192.168.1.1`,那么 `query` 就是 `192.168.1.1` 89 | * @param { Object } command 用户当前触发的命令,由于 plugin 支持多 commands,因此可用此参数来具体识别 90 | * @return { Promise[Array] | Array } 查询结果,支持 Promise 以及普通 Array 数组 91 | */ 92 | function onInput(query, command) { 93 | return Promise.resolve([Array[Item]]); 94 | } 95 | ``` 96 | 97 | - `onEnter` 98 | 99 | > 核心 api,用户按 Enter/Return 键或点击某条查询结果时调用 100 | 101 | ```javascript 102 | /** 103 | * @param { Object } item 选中的查询结果条目 104 | * @param { Object } command 当前触发的命令 105 | * @param { String } query 当前的查询字符串 106 | * @param { Object } keyStatus 用户按键状态: ctrlKey / shiftKey / metaKey / altKey 107 | * @param { Array } list 全部的查询结果 108 | * @return { Promise[String | Boolean]} 109 | * Promise[String] 作为新的命令被应用到输入框中 110 | * Promise[Boolean] 只对页面模式有效,在Boolean 为 false 时,Steward 弹框将延迟关闭 111 | */ 112 | function onEnter(item, command, query, keyStatus, list) { 113 | return Promise.resolve([String | Boolean]); 114 | } 115 | ``` 116 | 117 | #### steward.util 118 | - `createTab` 119 | ```javascript 120 | /** 121 | * 打开新标签页,根据 keyStatus.metaKey 为 true 时,在当前标签页打开 122 | * @param { Object } item from onEnter 123 | * @param { Object } keyStatus from onEnter 124 | */ 125 | function createTab(item, keyStatus) { 126 | // tabs.update() or tabs.create() 127 | } 128 | ``` 129 | 130 | - `getDefaultResult` 131 | 132 | ```javascript 133 | /** 134 | * 根据 command 生成默认查询结果 135 | * @param { Object } command 在 plugin 里定义的 command 136 | * @return { Array[Item] } 包含一条对 onEnter 透明的默认查询结果 137 | */ 138 | function getDefaultResult(command) { 139 | } 140 | ``` 141 | 142 | - `getEmptyResult` 143 | ```javascript 144 | /** 145 | * 根据 command 生成默认空查询结果 146 | * @param { Object } command 在 plugin 里定义的 command 147 | * @param { String | Optional } msg 空查询提示 148 | * @return { Array[Item] } 包含一条对 onEnter 透明的默认空查询结果 149 | */ 150 | function getEmptyResult(command, msg) { 151 | } 152 | ``` 153 | 154 | - `copyToClipboard` 155 | 156 | ```javascript 157 | /** 158 | * 将 text 拷贝到剪贴板 159 | * @param { String } text 将要拷贝的文本 160 | * @param { Boolean } showMsg 是否弹出提示 161 | * @return 162 | */ 163 | function copyToClipboard(text, showMsg) { 164 | } 165 | ``` 166 | 167 | - `getParameterByName` 168 | 169 | ```javascript 170 | /** 171 | * @param { String } name param name 172 | * @param { String } search 默认为 window.location.search 173 | * @return { String } 174 | */ 175 | function getParameterByName(name, search = window.location.search) { 176 | } 177 | ``` 178 | 179 | - `toast` -- https://github.com/CodeSeven/toastr 180 | 181 | - 其它 182 | 183 | #### steward.axios -- http库 184 | https://github.com/axios/axios 185 | 186 | #### steward.$ -- jquery 187 | 188 | #### steward.dayjs -- 日期库 189 | https://github.com/iamkun/dayjs 190 | 191 | #### steward.constant -- steward 内置常量 192 | 具体可以自行 `console.log` 查看 193 | 194 | ### 图示 195 | ![](https://i.imgur.com/Au6AdPA.png) 196 | 197 | ## 示例 198 | ```javascript 199 | module.exports = function(steward) { 200 | const version = 1; 201 | const author = 'solobat'; 202 | const name = 'IP Search'; 203 | const key = 'ip'; 204 | const type = 'keyword'; 205 | const icon = 'http://static.oksteward.com/ip.png'; 206 | const title = '查询 ip'; 207 | const subtitle = '输入 ipv4 地址,查询 ip 所在地点及运营商'; 208 | 209 | // commands 为命令的数组,支持多个命令 210 | const commands = [{ 211 | key, 212 | type, 213 | title, 214 | subtitle, 215 | icon 216 | }]; 217 | 218 | //============== 插件逻辑 =============// 219 | const APP_KEY = 'xxxxx'; 220 | const ipRegexp = /\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}\b/; 221 | 222 | function searchIp(ip) { 223 | const url = `http://apis.juhe.cn/ip/ip2addr?ip=${ip}&key=${APP_KEY}`; 224 | 225 | return steward.axios.get(url); 226 | } 227 | 228 | function dataFormat(data) { 229 | return [ 230 | { 231 | key, 232 | universal, 233 | icon, 234 | title: `${data.area} -- ${data.location}`, 235 | desc: '按 Enter 复制到剪贴板' 236 | } 237 | ]; 238 | } 239 | 240 | function onInput(query, command) { 241 | const str = query.trim(); 242 | 243 | if (str && ipRegexp.test(str)) { 244 | return searchIp(str).then(results => { 245 | const resp = results.data; 246 | 247 | if (resp.resultcode == 200) { 248 | return dataFormat(resp.result); 249 | } else { 250 | return []; 251 | } 252 | }).catch(results => { 253 | return steward.util.getDefaultResult(command); 254 | }); 255 | } else { 256 | return steward.util.getDefaultResult(command); 257 | } 258 | } 259 | 260 | function onEnter(item, command, query, keyStatus, list) { 261 | steward.util.copyToClipboard(item.title, true); 262 | } 263 | 264 | return { 265 | author, 266 | version, 267 | name, 268 | category: 'other', 269 | icon, 270 | title, 271 | commands, 272 | onInput, 273 | onEnter 274 | }; 275 | } 276 | ``` 277 | 278 | ## 更多 plugin 示例 279 | 见 `./plugins` -------------------------------------------------------------------------------- /plugins/bishijie.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (steward) { 3 | const version = 2; 4 | const author = 'solobat'; 5 | const name = 'Coin News'; 6 | const key = 'bi'; 7 | const type = 'keyword'; 8 | const icon = 'https://www.bishijie.com/favicon.ico'; 9 | const title = '币世界新闻'; 10 | const subtitle = '按 Enter / Return 跳转到币世界网页'; 11 | const commands = [{ 12 | key, 13 | type, 14 | title, 15 | subtitle, 16 | icon, 17 | shiftKey: true 18 | }]; 19 | 20 | const api = { 21 | list: 'https://www.bishijie.com/api/newsv17/index', 22 | search: 'https://www.bishijie.com/api/newsv17/search' 23 | }; 24 | const newsBaseURL = 'https://www.bishijie.com/kuaixun_'; 25 | 26 | function createRequest(query) { 27 | const request = {}; 28 | 29 | if (query) { 30 | request.url = api.search; 31 | request.params = { 32 | key: query, 33 | size: 50, 34 | from: 0 35 | }; 36 | request.respHandle = resp => resp.data.express; 37 | } else { 38 | request.url = api.list; 39 | request.params = { 40 | size: 50, 41 | client: 'pc' 42 | }; 43 | request.respHandle = resp => { 44 | return resp.data.reduce((list, day) => { 45 | list = list.concat(day.buttom ? day.buttom : []); 46 | 47 | return list; 48 | }, []); 49 | } 50 | } 51 | 52 | return request; 53 | } 54 | 55 | function fetchNews(query) { 56 | const request = createRequest(query); 57 | 58 | return steward.axios.get(request.url, { 59 | params: request.params 60 | }).then(({ data }) => { 61 | if (data.error === 0) { 62 | return request.respHandle(data); 63 | } else { 64 | return Promise.reject(null); 65 | } 66 | }); 67 | } 68 | 69 | function dataFormat(list) { 70 | return list.map(item => { 71 | return { 72 | key: 'url', 73 | universal: true, 74 | icon, 75 | title: item.title, 76 | desc: item.content, 77 | url: `${newsBaseURL}${item.newsflash_id}` 78 | }; 79 | }); 80 | } 81 | 82 | function onInput(query, command) { 83 | const queryString = query.trim(); 84 | 85 | return fetchNews(queryString).then(results => { 86 | if (results && results.length) { 87 | return dataFormat(results); 88 | } else { 89 | return steward.util.getDefaultResult(command); 90 | } 91 | }).catch(() => { 92 | return steward.util.getDefaultResult(command); 93 | }); 94 | } 95 | 96 | function onEnter(item, command, query, shiftKey, list) { 97 | 98 | } 99 | 100 | return { 101 | author, 102 | version, 103 | name, 104 | category: 'other', 105 | icon, 106 | title, 107 | commands, 108 | onInput, 109 | onEnter 110 | }; 111 | } 112 | -------------------------------------------------------------------------------- /plugins/chromeflags.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (steward) { 3 | const version = 2; 4 | const author = 'solobat'; 5 | const name = 'Chrome Flags'; 6 | const type = 'search'; 7 | const icon = steward.chrome.extension.getURL('iconfont/chrome.svg'); 8 | const title = 'Chrome Flags'; 9 | const subtitle = 'search and open chrome flags'; 10 | 11 | const chromeFlags = ["#enable-webassembly","#enable-devtools-experiments","#ntp-ui-md","#ignore-gpu-blacklist","#enable-canvas-2d-image-chromium","#disable-accelerated-2d-canvas","#composited-layer-borders","#overlay-strategies","#tint-gl-composited-content","#show-overdraw-feedback","#enable-draw-occlusion","#ui-disable-partial-swap","#enable-webrtc-remote-event-log","#enable-webrtc-srtp-aes-gcm","#enable-webrtc-srtp-encrypted-headers","#enable-webrtc-stun-origin","#WebRtcUseEchoCanceller3","#enable-webrtc-new-encode-cpu-load-estimator","#enable-nacl","#enable-nacl-debug","#force-pnacl-subzero","#nacl-debug-mask","#extension-apis","#extensions-on-chrome-urls","#enable-fast-unload","#enable-history-entry-requires-user-gesture","#disable-pushstate-throttle","#disable-hyperlink-auditing","#show-autofill-type-predictions","#save-page-as-mhtml","#mhtml-generator-option","#enable-quic","#disable-javascript-harmony-shipping","#enable-javascript-harmony","#enable-asm-webassembly","#enable-webassembly-streaming","#enable-webassembly-baseline","#enable-webassembly-threads","#shared-array-buffer","#enable-future-v8-vm-features","#enable-v8-orinoco","#disable-software-rasterizer","#enable-gpu-rasterization","#enable-oop-rasterization","#gpu-rasterization-msaa-sample-count","#enable-experimental-web-platform-features","#silent-debugger-extension-api","#enable-scroll-prediction","#top-chrome-md","#close-buttons-inactive-tabs","#new-tab-button-position","#single-tab-mode","#site-settings","#secondary-ui-md","#touch-events","#allow-nacl-socket-api","#disable-accelerated-video-decode","#debug-packed-apps","#automatic-password-generation","#PasswordForceSaving","#new-password-form-parsing","#enable-show-autofill-signatures","#AffiliationBasedMatching","#wallet-service-use-sandbox","#enable-navigation-tracing","#trace-upload-url","#enable-service-worker-script-full-code-cache","#enable-service-worker-servicification","#enable-pwa-full-code-cache","#enable-suggestions-with-substring-match","#lcd-text-aa","#enable-offer-store-unmasked-wallet-cards","#enable-offline-auto-reload","#enable-offline-auto-reload-visible-only","#show-saved-copy","#default-tile-width","#default-tile-height","#enable-simple-cache-backend","#device-discovery-notifications","#enable-print-preview-register-promos","#enable-spelling-feedback-field-trial","#enable-webgl-draft-extensions","#account-consistency","#enable-zero-copy","#bookmark-apps","#disable-hosted-apps-in-windows","#disable-hosted-app-shim-creation","#enable-hosted-app-quit-notification","#translate-ranker-enforcement","#translate","#enable-native-notifications","#in-product-help-demo-mode-choice","#num-raster-threads","#disable-cast-streaming-hw-encoding","#disable-threaded-scrolling","#extension-content-verification","#extension-active-script-permission","#enable-embedded-extension-options","#enable-message-center-new-style-notification","#enable-policy-tool","#enable-tab-audio-muting","#reduced-referrer-granularity","#committed-interstitials","#enable-site-per-process","#site-isolation-trial-opt-out","#enable-top-document-isolation","#enable-use-zoom-for-dsf","#enable-harfbuzz-rendertext","#allow-previews","#data-saver-server-previews","#ignore-previews-blocklist","#enable-data-reduction-proxy-server-experiment","#enable-client-lo-fi","#enable-noscript-previews","#enable-optimization-hints","#enable-heavy-page-capping","#allow-insecure-localhost","#enable-app-banners","#enable-experimental-app-banners","#bypass-app-banner-engagement-checks","#enable-desktop-pwas","#enable-desktop-pwas-link-capturing","#use-sync-sandbox","#load-media-router-component-extension","#media-router-cast-allow-all-ips","#mac-v2-sandbox","#mac-views-native-app-windows","#mac-views-task-manager","#show-all-dialogs-with-views-toolkit","#app-window-cycling","#views-browser-windows","#enable-gamepad-extensions","#enable-gamepad-vibration","#enable-webvr","#webxr","#webxr-gamepad-support","#webxr-orientation-sensor-device","#webxr-hit-test","#v8-cache-options","#keyboard-lock-api","#system-keyboard-lock","#save-previous-document-resources-until","#disallow-doc-written-script-loads","#enable-autofill-credit-card-upload","#safe-search-url-reporting","#force-ui-direction","#force-text-direction","#enable-origin-trials","#enable-brotli","#enable-image-capture-api","#automatic-tab-discarding","#tls13-variant","#enable-token-binding","#enable-scroll-anchor-serialization","#disable-audio-support-for-desktop-share","#tab-for-desktop-share","#user-activation-v2","#enable-webrtc-h264-with-openh264-ffmpeg","#protect-sync-credential","#ProtectSyncCredentialOnReauth","#PasswordExport","#PasswordImport","#enable-google-branded-context-menu","#enable-fullscreen-in-tab-detaching","#enable-content-fullscreen","#enable-fullscreen-toolbar-reveal","#remove-navigation-history","#passive-listener-default","#document-passive-event-listeners","#passive-event-listeners-due-to-fling","#enable-font-cache-scaling","#enable-framebusting-needs-sameorigin-or-usergesture","#web-payments","#web-payments-modifiers","#service-worker-payment-apps","#enable-web-payments-single-app-ui-skip","#just-in-time-service-worker-payment-app","#fill-on-account-select","#new-audio-rendering-mixing-strategy","#disable-background-video-track","#enable-new-remote-playback-pipeline","#enable-surfaces-for-videos","#enable-generic-sensor","#enable-generic-sensor-extra-classes","#expensive-background-timer-throttling","#enable-audio-focus","#enable-new-print-preview","#enable-nup-printing","#enable-cloud-printer-handler","#cross-process-guests","#enable-nostate-prefetch","#enable-autofill-credit-card-ablation-experiment","#enable-autofill-credit-card-local-card-migration","#enable-autofill-credit-card-upload-editable-cardholder-name","#enable-autofill-credit-card-upload-send-pan-first-six","#enable-autofill-credit-card-upload-update-prompt-explanation","#enable-autofill-native-dropdown-views","#enable-autofill-save-card-dialog-unlabeled-expiration-date","#enable-autofill-send-experiment-ids-in-payments-rpcs","#enable-zero-suggest-redirect-to-chrome","#left-to-right-urls","#omnibox-new-answer-layout","#omnibox-reverse-answers","#omnibox-rich-entity-suggestions","#omnibox-tail-suggestions","#omnibox-tab-switch-suggestions","#enable-new-app-menu-icon","#enable-speculative-service-worker-start-on-query-input","#tab-strip-keyboard-focus","#omnibox-display-title-for-current-url","#force-color-profile","#autoplay-policy","#force-effective-connection-type","#sampling-heap-profiler","#memlog","#memlog-keep-small-allocations","#memlog-sampling","#memlog-stack-mode","#omnibox-ui-elide-suggestion-url-after-host","#omnibox-ui-hide-steady-state-url-scheme-and-subdomains","#omnibox-ui-jog-textfield-on-popup","#omnibox-ui-show-suggestion-favicons","#omnibox-ui-max-autocomplete-matches","#omnibox-ui-vertical-margin","#omnibox-ui-swap-title-and-url","#use-suggestions-even-if-few","#use-new-accept-language-header","#use-google-local-ntp","#one-google-bar-on-local-ntp","#mac-rtl","#enable-picture-in-picture","#mac-touchbar","#dialog-touchbar","#network-service","#network-service-in-process","#out-of-blink-cors","#use-ddljson-api","#enable-resource-load-scheduler","#enable-async-image-decoding","#voice-search-on-local-ntp","#click-to-open-pdf","#direct-manipulation-stylus","#remove-deprecared-gaia-signin-endpoint","#doodles-on-local-ntp","#sound-content-setting","#enable-improved-language-settings","#enable-regional-locales-as-display-ui","#enable-module-scripts-dynamic-import","#enable-v8-context-snapshot","#enable-pixel-canvas-recording","#enable-parallel-downloading","#enable-html-base-username-detector","#mac-system-share-menu","#enable-new-preconnect","#enable-overflow-icons-for-media-controls","#enable-block-tab-unders","#top-sites-from-site-engagement","#enable-ntlm-v2","#enable-module-scripts-import-meta-url","#stop-in-background","#clipboard-content-setting","#enable-modern-media-controls","#enable-network-logging-to-file","#enable-mark-http-as","#enable-web-authentication-api","#enable-web-authentication-testing-api","#enable-web-authentication-ctap2-support","#enable-viz-display-compositor","#unified-consent","#simplify-https-indicator","#BundledConnectionHelp","#enable-signed-http-exchange","#enable-viz-hit-test-draw-quad","#pdf-isolation","#use-pdf-compositor-service-for-print","#mac-views-autofill-popup","#autofill-dynamic-forms","#autofill-prefilled-fields","#autofill-rationalize-repeated-server-predictions","#autofill-restrict-formless-form-extraction","#views-cast-dialog","#enable-emoji-context-menu","#SupervisedUserCommittedInterstitials","#enable-layered-api","#enable-layout-ng","#enable-lazy-frame-loading","#autofill-cache-query-responses","#autofill-enforce-min-required-fields-for-heuristics","#autofill-enforce-min-required-fields-for-query","#autofill-enforce-min-required-fields-for-upload","#single-click-autofill","#enable-sync-user-consent-separate-type","#enable-sync-uss-sessions","#enable-experimental-productivity-features","#ntp-backgrounds","#ntp-custom-links","#ntp-icons","#enable-recurrent-interstitial","#disallow-unsafe-http-downloads","#enable-websocket-auth-connection-reuse","#unsafely-treat-insecure-origin-as-secure","#enable-accessibility-object-model","#enable-autoplay-ignore-web-audio","#upcoming-ui-features","#enable-blink-heap-incremental-marking","#enable-css-fragment-identifiers","#enable-ephemeral-flash-permission","#infinite-session-restore","#page-almost-idle","#proactive-tab-freeze-and-discard","#site-characteristics-database","#enable-suggested-text-touch-bar","#gamepad-polling-rate","#allow-sxg-certs-without-extension","#enable-web-authentication-touch-id","#enable-autofill-account-wallet-storage","#enable-bloated-renderer-detection","#disable-webrtc-hw-decoding","#disable-webrtc-hw-encoding","#enable-webrtc-hw-h264-encoding","#enable-webrtc-hw-vp8-encoding","#smooth-scrolling","#enable-ble-advertising-in-apps","#disable-touch-adjustment","#enable-touch-drag-drop","#touch-selection-strategy","#enable-tcp-fast-open","#enable-memory-coordinator","#enable-md-incognito-ntp","#enable-autofill-credit-card-upload-google-pay-on-android-branding","#enable-experimental-fullscreen-exit-ui","#stop-non-timers-in-background"]; 12 | 13 | function onInput(text, command) { 14 | if (text.startsWith('#')) { 15 | const filterByName = suggestions => steward.util.getMatches(suggestions, text); 16 | const mapTo = key => item => { 17 | return { 18 | icon, 19 | key, 20 | title: item, 21 | desc: item, 22 | universal: true, 23 | url: `chrome://flags${item}` 24 | } 25 | }; 26 | 27 | return Promise.resolve(filterByName(chromeFlags).map(mapTo('url'))); 28 | } else { 29 | return; 30 | } 31 | } 32 | 33 | function onEnter(item, command, query, shiftKey, list) { 34 | 35 | } 36 | 37 | return { 38 | author, 39 | version, 40 | name, 41 | type, 42 | category: 'browser', 43 | icon, 44 | title, 45 | onInput, 46 | onEnter 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /plugins/coin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description powered by coinmarketcap.com api 3 | * @author tomasy 4 | * @email solopea@gmail.com 5 | */ 6 | 7 | module.exports = function(steward) { 8 | const author = 'solobat'; 9 | const version = '4'; 10 | const name = 'coin'; 11 | const type = 'keyword'; 12 | 13 | const util = steward.util; 14 | const storage = steward.storage; 15 | const axios = steward.axios; 16 | const root = 'https://api.coinmarketcap.com/v2'; 17 | const STORAGE_KEY = 'coin_list'; 18 | const coinApi = { 19 | list: () => { 20 | return storage.local.get(STORAGE_KEY).then(resp => { 21 | if (resp && resp.length) { 22 | return resp; 23 | } else { 24 | return axios.get(`${root}/listings`).then(({ data }) => { 25 | if (data.data) { 26 | storage.local.set({ 27 | [STORAGE_KEY]: data.data 28 | }); 29 | 30 | return data.data; 31 | } else { 32 | return []; 33 | } 34 | }); 35 | } 36 | }); 37 | }, 38 | 39 | price: (coinId = 1, convertTo = 'BTC') => { 40 | return axios.get(`${root}/ticker/${coinId}`, { 41 | params: { 42 | convert: convertTo 43 | } 44 | }); 45 | } 46 | } 47 | 48 | const keys = [{ key: 'coins'}, { key: 'coin'}]; 49 | const icon = chrome.extension.getURL('iconfont/btc.svg'); 50 | const title = chrome.i18n.getMessage(`${name}_title`); 51 | const commands = util.genCommands(name, icon, keys, type); 52 | const supportConverts = ['AUD', 'BRL', 'CAD', 'CHF', 'CLP', 'CNY', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD', 'HUF', 'IDR', 'ILS', 'INR', 'JPY', 'KRW', 'MXN', 'MYR', 'NOK', 'NZD', 'PHP', 'PKR', 'PLN', 'RUB', 'SEK', 'SGD', 'THB', 'TRY', 'TWD', 'ZAR', 'BTC', 'ETH', 'XRP', 'LTC', 'BCH']; 53 | 54 | let coinList; 55 | let coinMap; 56 | 57 | function getCoinState(coinSymbol) { 58 | const fixedSymbol = coinSymbol.toUpperCase(); 59 | 60 | if (coinMap) { 61 | return Promise.resolve(coinMap[fixedSymbol]); 62 | } else { 63 | return storage.local.get('coin_map').then(resp => { 64 | coinMap = resp || {}; 65 | 66 | return coinMap[fixedSymbol]; 67 | }); 68 | } 69 | } 70 | 71 | function setCoinState(coinSymbol, newState) { 72 | const oldState = coinMap[coinSymbol] || {}; 73 | 74 | coinMap[coinSymbol] = Object.assign(oldState, newState); 75 | 76 | return storage.local.set({ 77 | coin_map: coinMap 78 | }); 79 | } 80 | 81 | function getCachedCoinState(coinSymbol) { 82 | if (Number(coinSymbol)) { 83 | return Promise.resolve({ 84 | id: Number(coinSymbol) 85 | }); 86 | } else { 87 | return getCoinState(coinSymbol).then(coinState => { 88 | return coinState || {}; 89 | }); 90 | } 91 | } 92 | 93 | function getCoinInfoBySymbol(coinSymbol) { 94 | function searchInList() { 95 | return getCachedCoinState(coinSymbol).then(coinState => { 96 | const findFn = coinState.id ? coin => coin.id === coinState.id : coin => coin.symbol === coinSymbol; 97 | 98 | return { 99 | info: coinList.find(findFn), 100 | state: coinState 101 | }; 102 | }); 103 | } 104 | 105 | if (coinList) { 106 | return searchInList(); 107 | } else { 108 | return coinApi.list().then(coins => { 109 | coinList = coins; 110 | 111 | return searchInList(); 112 | }); 113 | } 114 | } 115 | 116 | function queryCoins(query) { 117 | return coinApi.list().then(coins => { 118 | return coins.filter(function (coin) { 119 | return util.matchText(query, coin.symbol + coin.name + coin.website_slug); 120 | }).map(coin => { 121 | return { 122 | key: 'coins', 123 | id: coin.id, 124 | icon, 125 | title: coin.symbol, 126 | desc: coin.name 127 | }; 128 | }).slice(0, 50); 129 | }); 130 | } 131 | 132 | function queryCoin(query) { 133 | const arr = query.split(/[_\s]/); 134 | const coinSymbol = (arr[0] || 'BTC').toUpperCase(); 135 | const convertTo = (arr[1] || '').toUpperCase(); 136 | const ex = arr[2]; 137 | 138 | if (!convertTo || supportConverts.indexOf(convertTo) !== -1) { 139 | return getCoinInfoBySymbol(coinSymbol).then(({ info, state }) => { 140 | if (info) { 141 | const fixedConvert = convertTo || state.convertTo || 'BTC'; 142 | const fixedEx = ex || state.ex || 'okex'; 143 | 144 | return coinApi.price(info.id, fixedConvert).then(resp => { 145 | const data = resp.data.data; 146 | 147 | if (data.quotes) { 148 | const items = []; 149 | 150 | for (const key in data.quotes) { 151 | const item = data.quotes[key]; 152 | 153 | items.push({ 154 | key: 'coins', 155 | id: key, 156 | icon, 157 | title: `${info.symbol} ==> ${item.price}/${key}`, 158 | desc: `${item.percent_change_1h || 0}%[1h] -- ${item.percent_change_24h}%[24h] -- ${item.percent_change_7d}%[7d]`, 159 | state: { 160 | id: info.id, 161 | coinSymbol: data.symbol, 162 | convertTo: key, 163 | ex: fixedEx 164 | } 165 | }); 166 | } 167 | 168 | return items; 169 | } else { 170 | return []; 171 | } 172 | }); 173 | } else { 174 | return []; 175 | } 176 | }); 177 | } 178 | } 179 | 180 | function onInput(query, command) { 181 | const { orkey } = command; 182 | 183 | if (orkey === 'coins') { 184 | return queryCoins(query); 185 | } else if (orkey === 'coin') { 186 | return queryCoin(query); 187 | } 188 | } 189 | 190 | const exMap = { 191 | okex: { 192 | urlFn: (coinSymbol, convertTo) => `https://www.okex.com/spot/trade#product=${coinSymbol}_${convertTo}`, 193 | converts: ['btc', 'usdt', 'eth', 'okb'] 194 | }, 195 | huobi: { 196 | urlFn: (coinSymbol, convertTo) => `https://www.huobi.br.com/${coinSymbol}_${convertTo}/exchange/`, 197 | converts: ['btc', 'usdt', 'eth', 'ht'] 198 | }, 199 | hadax: { 200 | urlFn: (coinSymbol, convertTo) => `https://www.hadax.com/coin_coin/exchange/#${coinSymbol}_${convertTo}`, 201 | converts: ['btc', 'eth', 'ht'] 202 | }, 203 | binance: { 204 | urlFn: (coinSymbol, convertTo) => `https://www.binance.com/trade/${coinSymbol}_${convertTo}`, 205 | converts: ['btc', 'eth', 'usdt', 'bnb'] 206 | }, 207 | fcoin: { 208 | urlFn: (coinSymbol, convertTo) => `https://exchange.fcoin.com/ex/main/${coinSymbol.toLowerCase()}-${convertTo.toLowerCase()}`, 209 | converts: ['btc', 'eth', 'usdt'] 210 | } 211 | }; 212 | 213 | function getTradeUrl(coinSymbol = 'btc', convertTo = 'usdt', ex = 'okex') { 214 | const exConf = exMap[ex]; 215 | const realConvert = convertTo.toLowerCase() === 'usd' ? 'usdt' : convertTo.toLowerCase(); 216 | const fixedConvert = exConf.converts.indexOf(realConvert) !== -1 ? realConvert : exConf.converts[0]; 217 | 218 | return exConf.urlFn(coinSymbol, fixedConvert); 219 | } 220 | 221 | function onEnter(item, { orkey }, query, keyStatus) { 222 | if (orkey === 'coins') { 223 | setCoinState(item.title.toUpperCase(), { 224 | id: item.id 225 | }); 226 | 227 | return Promise.resolve(`coin ${item.id}`); 228 | } else if (orkey === 'coin') { 229 | steward.util.createTab({ url: getTradeUrl(item.state.coinSymbol, item.state.convertTo, item.state.ex), active: true }, 230 | keyStatus); 231 | setCoinState(item.state.coinSymbol, item.state); 232 | } 233 | } 234 | 235 | return { 236 | author, 237 | version, 238 | name: 'Coin Market', 239 | category: 'other', 240 | icon, 241 | title, 242 | commands, 243 | onInput, 244 | onEnter 245 | }; 246 | } -------------------------------------------------------------------------------- /plugins/console.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (steward) { 3 | const version = 5; 4 | const author = 'solobat'; 5 | const name = 'Console'; 6 | const key = '>'; 7 | const type = 'keyword'; 8 | const icon = 'https://i.imgur.com/Z2qwSNT.png'; 9 | const title = 'console'; 10 | const subtitle = 'execute javascript code'; 11 | const commands = [{ 12 | key, 13 | type, 14 | title, 15 | subtitle, 16 | icon 17 | }]; 18 | 19 | const results = []; 20 | 21 | function dataFormat(list) { 22 | return list.map(item => { 23 | return { 24 | title: item.result, 25 | desc: item.code, 26 | icon 27 | } 28 | }); 29 | } 30 | 31 | function onInput(query, command) { 32 | if (results.length) { 33 | return Promise.resolve(dataFormat(results)); 34 | } else { 35 | return steward.util.getDefaultResult(command); 36 | } 37 | } 38 | 39 | const $ = new Proxy({}, { 40 | get(target, prop) { 41 | console.log(prop); 42 | 43 | switch (prop) { 44 | case 'now': 45 | return steward.dayjs().format("YYYY-MM-DD HH:mm:ss"); 46 | case 'unix': 47 | return steward.dayjs().unix(); 48 | default: 49 | return void 0; 50 | } 51 | } 52 | }); 53 | 54 | function onEnter(item, command, query, shiftKey, list) { 55 | if (query) { 56 | let result; 57 | const day = steward.dayjs; 58 | const http = steward.axios; 59 | 60 | try { 61 | result = eval(query); 62 | 63 | if (result === null) { 64 | result = 'null'; 65 | } 66 | 67 | if (result === undefined) { 68 | result = 'undefined'; 69 | } 70 | } catch (error) { 71 | result = error.message; 72 | } finally { 73 | results.unshift({ 74 | result: result.toString(), 75 | code: query 76 | }); 77 | 78 | window.stewardApp.app.applyCommand('> '); 79 | } 80 | } else { 81 | steward.util.copyToClipboard(item.title, true); 82 | } 83 | 84 | return Promise.resolve(true); 85 | } 86 | 87 | return { 88 | author, 89 | version, 90 | name, 91 | category: 'other', 92 | icon, 93 | title, 94 | commands, 95 | onInput, 96 | onEnter 97 | }; 98 | } 99 | -------------------------------------------------------------------------------- /plugins/cryptos.js: -------------------------------------------------------------------------------- 1 | module.exports = function (steward) { 2 | const version = 2; 3 | const author = "solobat"; 4 | const name = "Crypto Prices"; 5 | const key = "cc"; 6 | const type = "keyword"; 7 | const icon = "https://assets.coingecko.com/coins/images/1/small/bitcoin.png"; 8 | const title = "Cryptocurrency prices"; 9 | const subtitle = "List Cryptocurrency Prices"; 10 | const commands = [ 11 | { 12 | key, 13 | type, 14 | title, 15 | subtitle, 16 | icon, 17 | shiftKey: true, 18 | }, 19 | ]; 20 | 21 | // coingecko coin ids 22 | const idNameMap = { 23 | "immutable-x": "IMX", 24 | "ethereum-name-service": "ENS", 25 | "bitcoin": "BTC", 26 | "ethereum": "ETH", 27 | "filecoin": "FIL", 28 | "solana": "SOL", 29 | "fantom": "FTM", 30 | "uniswap": "UNI" 31 | } 32 | 33 | const tokens = [ 34 | "bitcoin", 35 | "ethereum", 36 | "immutable-x", 37 | "ethereum-name-service", 38 | "filecoin", 39 | "solana", 40 | "fantom", 41 | "uniswap", 42 | ]; 43 | 44 | const icons = { 45 | bitcoin: 'https://assets.coingecko.com/coins/images/1/small/bitcoin.png', 46 | binancecoin: 'https://assets.coingecko.com/coins/images/825/small/binance-coin-logo.png', 47 | ethereum: 'https://assets.coingecko.com/coins/images/279/small/ethereum.png', 48 | uniswap: 'https://assets.coingecko.com/coins/images/12504/small/uniswap-uni.png', 49 | solana: 'https://assets.coingecko.com/coins/images/4128/small/coinmarketcap-solana-200.png', 50 | "immutable-x": 'https://assets.coingecko.com/coins/images/17233/small/immutable-x.jpeg', 51 | "ethereum-name-service": "https://assets.coingecko.com/coins/images/19785/small/acatxTm8_400x400.jpg", 52 | filecoin: 'https://assets.coingecko.com/coins/images/12817/small/filecoin.png', 53 | aave: 'https://assets.coingecko.com/coins/images/12645/small/AAVE.png', 54 | dydx: 'https://assets.coingecko.com/coins/images/17500/small/hjnIm9bV.jpg?1628009360', 55 | fantom: 'https://assets.coingecko.com/coins/images/4001/small/Fantom.png?1558015016', 56 | } 57 | 58 | function formatName(name) { 59 | return idNameMap[name]; 60 | } 61 | 62 | function fetchList() { 63 | return steward.axios.get("https://api.coingecko.com/api/v3/simple/price", { 64 | params: { 65 | ids: tokens.join(","), 66 | vs_currencies: "usd", 67 | }, 68 | }); 69 | } 70 | 71 | function dataFormat(list) { 72 | return list.map((item) => { 73 | return { 74 | key: "coins", 75 | universal: true, 76 | icon: icons[item[0]] || icon, 77 | title: formatName(item[0]), 78 | desc: `Price: ${item[1].usd}`, 79 | }; 80 | }); 81 | } 82 | 83 | function onInput(query, command) { 84 | return fetchList() 85 | .then((results) => { 86 | const resp = results.data; 87 | 88 | if (resp) { 89 | return dataFormat(Object.entries(resp)); 90 | } else { 91 | return steward.util.getDefaultResult(command); 92 | } 93 | }) 94 | .catch(() => { 95 | return steward.util.getDefaultResult(command); 96 | }); 97 | } 98 | 99 | function onEnter(item, command, query, shiftKey, list) {} 100 | 101 | return { 102 | author, 103 | version, 104 | name, 105 | category: "other", 106 | icon, 107 | title, 108 | commands, 109 | onInput, 110 | onEnter, 111 | }; 112 | }; 113 | -------------------------------------------------------------------------------- /plugins/douban.book.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (steward) { 3 | const version = 2; 4 | const author = 'solobat'; 5 | const name = 'douban book'; 6 | const key = 'dbb'; 7 | const type = 'keyword'; 8 | const icon = 'https://www.douban.com/favicon.ico'; 9 | const title = '在 book.douban.com 查找书籍'; 10 | const subtitle = '按 Enter / Return 跳转到豆瓣读书网页'; 11 | const commands = [{ 12 | key, 13 | type, 14 | title, 15 | subtitle, 16 | icon, 17 | shiftKey: true 18 | }]; 19 | 20 | function searchKeyword(keyword) { 21 | return steward.axios.get('https://book.douban.com/j/subject_suggest', { 22 | params: { 23 | q: keyword 24 | } 25 | }); 26 | } 27 | 28 | function dataFormat(list) { 29 | return list.map(item => { 30 | return { 31 | key: 'url', 32 | universal: true, 33 | icon: item.pic || icon, 34 | title: item.title, 35 | desc: `${item.year} ${item.author_name}`, 36 | url: item.url 37 | }; 38 | }); 39 | } 40 | 41 | function onInput(query, command) { 42 | if (query) { 43 | return searchKeyword(query).then(results => { 44 | const resp = results.data; 45 | 46 | if (resp && resp.length) { 47 | return dataFormat(resp); 48 | } else { 49 | return steward.util.getDefaultResult(command); 50 | } 51 | }).catch(() => { 52 | return steward.util.getDefaultResult(command); 53 | }); 54 | } else { 55 | return steward.util.getDefaultResult(command); 56 | } 57 | } 58 | 59 | function onEnter(item, command, query, shiftKey, list) { 60 | 61 | } 62 | 63 | return { 64 | author, 65 | version, 66 | name, 67 | category: 'other', 68 | icon, 69 | title, 70 | commands, 71 | onInput, 72 | onEnter 73 | }; 74 | } 75 | -------------------------------------------------------------------------------- /plugins/douban.movie.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (steward) { 3 | const version = 2; 4 | const author = 'solobat'; 5 | const name = 'douban movie'; 6 | const key = 'dbm'; 7 | const type = 'keyword'; 8 | const icon = 'https://www.douban.com/favicon.ico'; 9 | const title = '在 movie.douban.com 查找电影'; 10 | const subtitle = '按 Enter / Return 跳转到豆瓣电影网页'; 11 | const commands = [{ 12 | key, 13 | type, 14 | title, 15 | subtitle, 16 | icon, 17 | shiftKey: true 18 | }]; 19 | 20 | function searchKeyword(keyword) { 21 | return steward.axios.get('https://movie.douban.com/j/subject_suggest', { 22 | params: { 23 | q: keyword 24 | } 25 | }); 26 | } 27 | 28 | function dataFormat(list) { 29 | return list.map(item => { 30 | const url = `http://movie.douban.com/subject/${item.id}?from=chrome_steward`; 31 | 32 | return { 33 | key: 'url', 34 | universal: true, 35 | icon: item.img || icon, 36 | title: item.title, 37 | desc: `${item.year} ${item.sub_title}`, 38 | url 39 | }; 40 | }); 41 | } 42 | 43 | function onInput(query, command) { 44 | if (query) { 45 | return searchKeyword(query).then(results => { 46 | const resp = results.data; 47 | 48 | if (resp && resp.length) { 49 | return dataFormat(resp); 50 | } else { 51 | return steward.util.getDefaultResult(command); 52 | } 53 | }).catch(() => { 54 | return steward.util.getDefaultResult(command); 55 | }); 56 | } else { 57 | return steward.util.getDefaultResult(command); 58 | } 59 | } 60 | 61 | function onEnter(item, command, query, shiftKey, list) { 62 | 63 | } 64 | 65 | return { 66 | author, 67 | version, 68 | name, 69 | category: 'other', 70 | icon, 71 | title, 72 | commands, 73 | onInput, 74 | onEnter 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /plugins/douban.music.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (steward) { 3 | const version = 1; 4 | const author = 'lifesign'; 5 | const name = 'douban music'; 6 | const key = 'dbmc'; 7 | const type = 'keyword'; 8 | const icon = 'https://www.douban.com/favicon.ico'; 9 | const title = '在 music.douban.com 查找音乐'; 10 | const subtitle = '按 Enter / Return 跳转到豆瓣音乐网页'; 11 | const commands = [{ 12 | key, 13 | type, 14 | title, 15 | subtitle, 16 | icon 17 | }]; 18 | 19 | function searchKeyword(keyword) { 20 | return steward.axios.get('https://music.douban.com/j/subject_suggest', { 21 | params: { 22 | q: keyword 23 | } 24 | }); 25 | } 26 | 27 | function dataFormat(list) { 28 | return list.map(item => { 29 | return { 30 | key: 'url', 31 | universal: true, 32 | icon: item.pic || icon, 33 | title: item.title + (item.other_title ? ' 其他名称: ' + item.other_title : ''), 34 | desc: item.performer ? '表演者: ' + item.performer : '(音乐人)', 35 | url: item.url 36 | }; 37 | }); 38 | } 39 | 40 | function onInput(query, command) { 41 | if (query) { 42 | return searchKeyword(query).then(results => { 43 | const resp = results.data; 44 | 45 | if (resp && resp.length) { 46 | return dataFormat(resp); 47 | } else { 48 | return steward.util.getDefaultResult(command); 49 | } 50 | }).catch(() => { 51 | return steward.util.getDefaultResult(command); 52 | }); 53 | } else { 54 | return steward.util.getDefaultResult(command); 55 | } 56 | } 57 | 58 | function onEnter(item, command, query, shiftKey, list) { 59 | 60 | } 61 | 62 | return { 63 | author, 64 | version, 65 | name, 66 | category: 'other', 67 | icon, 68 | title, 69 | commands, 70 | onInput, 71 | onEnter 72 | }; 73 | } 74 | -------------------------------------------------------------------------------- /plugins/epl.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (steward) { 3 | const version = 1; 4 | const author = 'solobat'; 5 | const name = 'Epl Competitions'; 6 | const key = 'epl'; 7 | const type = 'keyword'; 8 | const icon = 'http://staticsports.pplive.cn/2018/interim/app/pc_league/static/v_20180905135831/images/yc_logo.png'; 9 | const title = '英超赛事查询'; 10 | const subtitle = '英超最近比赛查询,按 Enter 键打开相应的比赛页面,数据来自 PPTV'; 11 | const commands = [{ 12 | key, 13 | type, 14 | title, 15 | subtitle, 16 | icon 17 | }]; 18 | 19 | function fetchMatchList(startDate) { 20 | return steward.axios.get('http://sportlive.suning.com/slsp-web/lms/all/list/matchList.do', { 21 | params: { 22 | pageIndex: 1, 23 | timeSort: 1, 24 | version: 1, 25 | competitionId: 5, 26 | startDate 27 | } 28 | }); 29 | } 30 | 31 | const dateRegexp = /\d{8}/; 32 | const MATCH_STATUS = { 33 | 0: '未开始', 34 | 1: '比赛中', 35 | 2: '已结束' 36 | }; 37 | 38 | function dataFormat(list) { 39 | const dates = Object.keys(list).sort().slice(0, 3); 40 | 41 | return dates.reduce((arr, date) => { 42 | return arr.concat(list[date]); 43 | }, []).map(item => { 44 | const matchInfo = item.matchInfo; 45 | const home = matchInfo.homeTeam; 46 | const away = matchInfo.guestTeam; 47 | 48 | return { 49 | key: 'url', 50 | icon, 51 | url: `http://sports.pptv.com/sportslive?sectionid=${item.sectionInfo.id}&matchid=${matchInfo.matchId}`, 52 | universal: true, 53 | title: `${home.title} ${home.score || 0} : ${away.score || 0} ${away.title}`, 54 | desc: `${matchInfo.matchDatetime} ${item.cataTitle}${matchInfo.stageName} ${matchInfo.roundName} ${MATCH_STATUS[matchInfo.status]}` 55 | }; 56 | }); 57 | } 58 | 59 | function onInput(query, command) { 60 | let startDate; 61 | 62 | if (dateRegexp.test(query)) { 63 | startDate = query; 64 | } else { 65 | startDate = steward.dayjs().add(-2, 'day').format('YYYYMMDD'); 66 | } 67 | 68 | return fetchMatchList(startDate).then(results => { 69 | const resp = results.data; 70 | 71 | if (resp.retCode == 0) { 72 | return dataFormat(resp.data.list); 73 | } else { 74 | return steward.util.getDefaultResult(command); 75 | } 76 | }).catch(results => { 77 | return steward.util.getDefaultResult(command); 78 | }); 79 | } 80 | 81 | function onEnter(item, command, query, shiftKey, list) { 82 | steward.util.copyToClipboard(item.title, true); 83 | } 84 | 85 | return { 86 | author, 87 | version, 88 | name, 89 | category: 'other', 90 | icon, 91 | title, 92 | commands, 93 | onInput, 94 | onEnter 95 | }; 96 | } -------------------------------------------------------------------------------- /plugins/fbrank.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (steward) { 3 | const version = 1; 4 | const author = 'solobat'; 5 | const name = 'Soccer Rank'; 6 | const key = 'rank'; 7 | const type = 'keyword'; 8 | const icon = 'http://img1.gtimg.com/sports/pics/hv1/196/95/2276/148021321.png'; 9 | const title = '足球积分榜查询'; 10 | const subtitle = '积分榜,数据来自腾讯体育'; 11 | const commands = [{ 12 | key, 13 | type, 14 | title, 15 | subtitle, 16 | icon 17 | }]; 18 | 19 | const MATCH_TYPE = { 20 | '英超': 8, 21 | '西甲': 23, 22 | '意甲': 21 23 | }; 24 | 25 | function fetchRank(type = '英超') { 26 | const cid = MATCH_TYPE[type] || 8; 27 | 28 | return steward.axios.get('http://matchweb.sports.qq.com/team/rank', { 29 | params: { 30 | competitionId: cid, 31 | from: 'sporthp' 32 | } 33 | }); 34 | } 35 | 36 | function dataFormat(list) { 37 | return list.map((item, index) => { 38 | return { 39 | icon, 40 | title: `${index + 1}. ${item.teamName} -- ${item.score}`, 41 | desc: `场次: ${item.winMatchCount}/${item.planishMatchCount}/${item.lostMatchCount} 得失球: ${item.goals}/${item.goalsConceded}/${item.goalDifference}` 42 | }; 43 | }); 44 | } 45 | 46 | function onInput(query, command) { 47 | return fetchRank(query).then(results => { 48 | const resp = results.data; 49 | 50 | if (resp.code === 0) { 51 | return dataFormat(resp.data.list); 52 | } else { 53 | return steward.util.getDefaultResult(command); 54 | } 55 | }).catch(results => { 56 | return steward.util.getDefaultResult(command); 57 | }); 58 | } 59 | 60 | function onEnter(item, command, query, shiftKey, list) { 61 | steward.util.copyToClipboard(item.title, true); 62 | } 63 | 64 | return { 65 | author, 66 | version, 67 | name, 68 | category: 'other', 69 | icon, 70 | title, 71 | commands, 72 | onInput, 73 | onEnter 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /plugins/firefox.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (steward) { 3 | const util = steward.util; 4 | const version = 1; 5 | const name = 'firefox'; 6 | const type = 'search'; 7 | const icon = chrome.extension.getURL('iconfont/chrome.svg'); 8 | const title = chrome.i18n.getMessage(`${name}_title`); 9 | const settingUrls = []; 10 | const firefoxUrls = settingUrls.concat(["about:about", "about:addons", "about:buildconfig", "about:cache", "about:checkerboard", "about:config", "about:crashes", "about:credits", "about:debugging", "about:devtools", "about:downloads", "about:home", "about:license", "about:logo", "about:memory", "about:mozilla", "about:networking", "about:newtab", "about:performance", "about:plugins", "about:policies", "about:preferences", "about:privatebrowsing", "about:profiles", "about:restartrequired", "about:reader", "about:rights", "about:robots", "about:serviceworkers", "about:studies", "about:sessionrestore", "about:support", "about:sync-log", "about:telemetry", "about:url-classifier", "about:webrtc", "about:welcome", "about:welcomeback"]); 11 | 12 | function onInput(text) { 13 | const filterByName = suggestions => util.getMatches(suggestions, text); 14 | const mapTo = key => item => { 15 | return { 16 | icon, 17 | key, 18 | title: item.split(':')[1].replace('/', ' '), 19 | desc: item, 20 | url: item 21 | } 22 | }; 23 | 24 | const pages = filterByName(firefoxUrls).map(mapTo('url')); 25 | 26 | return Promise.resolve(pages); 27 | } 28 | 29 | function onEnter(item, command, query, keyStatus) { 30 | util.createTab(item, keyStatus); 31 | } 32 | 33 | return { 34 | author: 'solobat', 35 | version, 36 | name: 'Firefox', 37 | category: 'browser', 38 | type, 39 | icon, 40 | title, 41 | onInput, 42 | onEnter, 43 | canDisabled: false 44 | }; 45 | } -------------------------------------------------------------------------------- /plugins/hotrank.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (steward) { 3 | const version = 2; 4 | const author = 'solobat'; 5 | const name = 'Hot Rank'; 6 | const key = 'hot'; 7 | const type = 'keyword'; 8 | const icon = 'https://i.imgur.com/CDBL9t5.jpg'; 9 | const title = '果汁排行榜'; 10 | const subtitle = '数据来自 http://guozhivip.com'; 11 | const commands = [{ 12 | key, 13 | type, 14 | title, 15 | subtitle, 16 | icon, 17 | shiftKey: true 18 | }]; 19 | const items = [{"title":"微博热搜","url":"https://s.weibo.com/top/summary?cate=realtimehot","icon":"http://guozhivip.com/rank/images/r1.jpg"},{"title":"百度风云榜","url":"http://top.baidu.com/","icon":"http://guozhivip.com/rank/images/r2.jpg"},{"title":"搜狗热搜榜","url":"http://top.sogou.com/home.html","icon":"http://guozhivip.com/rank/images/r3.jpg"},{"title":"360实时热点","url":"https://trends.so.com/hot","icon":"http://guozhivip.com/rank/images/r4.jpg"},{"title":"头条指数","url":"https://index.toutiao.com/","icon":"http://guozhivip.com/rank/images/r5.jpg"},{"title":"知乎热榜","url":"https://www.zhihu.com/billboard","icon":"http://guozhivip.com/rank/images/r6.jpg"},{"title":"贴吧热议榜","url":"http://c.tieba.baidu.com/hottopic/browse/topicList?res_type=1","icon":"http://guozhivip.com/rank/images/r7.jpg"},{"title":"抽屉新热榜","url":"https://dig.chouti.com/all/hot/24hr/1","icon":"http://guozhivip.com/rank/images/r8.jpg"},{"title":"豆瓣讨论精选","url":"https://www.douban.com/group/explore","icon":"http://guozhivip.com/rank/images/r6.jpg"},{"title":"V2EX最热","url":"https://www.v2ex.com/?tab=hot","icon":"http://guozhivip.com/rank/images/r7.jpg"},{"title":"胡润百富榜","url":"http://www.hurun.net/CN/HuList/Index","icon":"http://guozhivip.com/rank/images/r5.jpg"},{"title":"财富榜单","url":"http://www.fortunechina.com/rankings/index.htm","icon":"http://guozhivip.com/rank/images/r4.jpg"},{"title":"福布斯榜单","url":"http://www.forbeschina.com/list/","icon":"http://guozhivip.com/rank/images/r3.jpg"},{"title":"全网影视热搜榜","url":"https://v.qq.com/x/hotlist/search/?channel=555&source=common_nav_vs","icon":"http://guozhivip.com/rank/images/r2.jpg"},{"title":"电影票房榜","url":"http://www.cbooo.cn/","icon":"http://guozhivip.com/rank/images/b12.jpg"},{"title":"豆瓣电影榜","url":"https://movie.douban.com/chart","icon":"http://guozhivip.com/rank/images/b6.jpg"},{"title":"优酷指数榜","url":"http://top.youku.com/rank/?spm=a2htp.20023922.m_232715.5~5~5~5!2~5~H2~A","icon":"http://guozhivip.com/rank/images/b4.jpg"},{"title":"爱奇艺风云榜","url":"http://top.iqiyi.com/rebobang.html","icon":"http://guozhivip.com/rank/images/b7.jpg"},{"title":"搜狐视频榜","url":"https://tv.sohu.com/hothdtv/","icon":"http://guozhivip.com/rank/images/b3.jpg"},{"title":"热门短视频","url":"https://www.vmovier.com/hot#rotate-nav","icon":"http://guozhivip.com/rank/images/b2.jpg"},{"title":"B站热门排行","url":"https://www.bilibili.com/ranking?spm_id_from=333.334.banner_link.1","icon":"http://guozhivip.com/rank/images/b8.jpg"},{"title":"QQ音乐巅峰榜","url":"https://y.qq.com/n/yqq/toplist/4.html#stat=y_new.index.toplist.detail.4","icon":"http://guozhivip.com/rank/images/b9.jpg"},{"title":"云音乐飙升榜","url":"https://music.163.com/#/discover/toplist","icon":"http://guozhivip.com/rank/images/r3.jpg"},{"title":"虾米官方榜","url":"https://www.xiami.com/chart?spm=a1z1s.6843761.1110925385.2.8vQoSa","icon":"http://guozhivip.com/rank/images/r4.jpg"},{"title":"酷狗TOP500","url":"http://www.kugou.com/yy/rank/home/1-8888.html?from=rank","icon":"http://guozhivip.com/rank/images/r5.jpg"},{"title":"千千音乐榜","url":"http://music.taihe.com/top/","icon":"http://guozhivip.com/rank/images/r6.jpg"},{"title":"POCO人气摄影","url":"http://www.poco.cn/works/works_list?classify_type=0&works_type=week","icon":"http://guozhivip.com/rank/images/r7.jpg"},{"title":"泼辣有图","url":"http://www.polayoutu.com/collections","icon":"http://guozhivip.com/rank/images/r8.jpg"},{"title":"站酷摄影榜","url":"https://www.zcool.com.cn/top/product.do?rankId=76&rankProductCategory=33","icon":"http://guozhivip.com/rank/images/b10.jpg"},{"title":"京东排行榜","url":"http://top.jd.com/","icon":"http://guozhivip.com/rank/images/r7.jpg"},{"title":"界面金榜","url":"https://www.jiemian.com/lists/171.html","icon":"http://guozhivip.com/rank/images/b11.jpg"},{"title":"公众号排名","url":"https://www.newrank.cn/public/info/list.html?period=day&type=data","icon":"http://guozhivip.com/rank/images/r4.jpg"},{"title":"网站排行榜","url":"http://top.chinaz.com/","icon":"http://guozhivip.com/rank/images/r3.jpg"},{"title":"艾媒金榜","url":"http://www.iimedia.cn/#xinsan","icon":"http://guozhivip.com/rank/images/r2.jpg"},{"title":"卡思榜单","url":"https://www.caasdata.com/index/rank/index.html","icon":"http://guozhivip.com/rank/images/r1.jpg"},{"title":"App排行榜","url":"https://www.qimai.cn/rank","icon":"http://guozhivip.com/rank/images/r8.jpg"},{"title":"小程序排行榜","url":"http://www.aldzs.com/toplist","icon":"http://guozhivip.com/rank/images/r7.jpg"},{"title":"游戏排行榜","url":"http://top.17173.com/","icon":"http://guozhivip.com/rank/images/b10.jpg"},{"title":"小游戏排行榜","url":"http://www.4399.com/flash/ph.htm","icon":"http://guozhivip.com/rank/images/r8.jpg"},{"title":"主播排行榜","url":"http://www.xiaohulu.com/anchor2/","icon":"http://guozhivip.com/rank/images/r7.jpg"}]; 20 | 21 | function dataFormat(items) { 22 | return items.map(item => { 23 | return { 24 | key: 'url', 25 | universal: true, 26 | title: item.title, 27 | icon: item.icon, 28 | url: item.url 29 | }; 30 | }); 31 | } 32 | function onInput(query, command) { 33 | const result = dataFormat(items.filter(item => steward.util.matchText(query, item.title))); 34 | return Promise.resolve(result); 35 | } 36 | 37 | function onEnter(item, command, query, shiftKey, list) { 38 | } 39 | 40 | return { 41 | author, 42 | version, 43 | name, 44 | category: 'other', 45 | icon, 46 | title, 47 | commands, 48 | onInput, 49 | onEnter 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /plugins/ipsearch.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(steward) { 3 | const version = 1; 4 | const author = 'solobat'; 5 | const name = 'IP Search'; 6 | const key = 'ip'; 7 | const type = 'keyword'; 8 | const icon = 'http://static.oksteward.com/ip.png'; 9 | const title = '查询 ip'; 10 | const subtitle = '输入 ipv4 地址,查询 ip 所在地点及运营商'; 11 | const commands = [{ 12 | key, 13 | type, 14 | title, 15 | subtitle, 16 | icon 17 | }]; 18 | 19 | const APP_KEY = '8465a1b30b97dc10237731bf940c8902'; 20 | const ipRegexp = /\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}\b/; 21 | 22 | function searchIp(ip) { 23 | const url = `http://apis.juhe.cn/ip/ip2addr?ip=${ip}&key=${APP_KEY}`; 24 | 25 | return steward.axios.get(url); 26 | } 27 | 28 | function dataFormat(data) { 29 | return [{ 30 | key, 31 | icon, 32 | title: `${data.area} -- ${data.location}`, 33 | desc: '按 Enter 复制到剪贴板' 34 | }]; 35 | } 36 | 37 | function onInput(query, command) { 38 | const str = query.trim(); 39 | 40 | if (str && ipRegexp.test(str)) { 41 | return searchIp(str).then(results => { 42 | const resp = results.data; 43 | 44 | if (resp.resultcode == 200) { 45 | return dataFormat(resp.result); 46 | } else { 47 | return []; 48 | } 49 | }).catch(results => { 50 | return steward.util.getDefaultResult(command); 51 | }); 52 | } else { 53 | return steward.util.getDefaultResult(command); 54 | } 55 | } 56 | 57 | function onEnter(item, command, query, shiftKey, list) { 58 | steward.util.copyToClipboard(item.title, true); 59 | } 60 | 61 | return { 62 | author, 63 | version, 64 | name, 65 | category: 'other', 66 | icon, 67 | title, 68 | commands, 69 | onInput, 70 | onEnter 71 | }; 72 | } -------------------------------------------------------------------------------- /plugins/jenkins.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description jenkins plugin 3 | * @author tomasy 4 | * @email solopea@gmail.com 5 | */ 6 | module.exports = function(steward) { 7 | 8 | const util = steward.util; 9 | const Toast = steward.util.toast; 10 | const author = 'solobat'; 11 | const version = 3; 12 | const name = 'jenkins' 13 | //'jk', 'jkb', 'jkc', 'jkw', 'jkset' 14 | const keys = [ 15 | { key: 'jk' }, 16 | { key: 'jkb' }, 17 | { key: 'jkc' }, 18 | { key: 'jkw' }, 19 | { key: 'jkset' } 20 | ]; 21 | const type = 'keyword'; 22 | const icon = chrome.extension.getURL('iconfont/jenkins.svg') 23 | const title = chrome.i18n.getMessage(`${name}_title`); 24 | let SERVER_URL = window.localStorage.jenkins_url || '' 25 | const commands = util.genCommands(name, icon, keys, type); 26 | const keyUrlMap = { 27 | 'jk': '', 28 | 'jkb': 'build', 29 | 'jkc': 'configure', 30 | 'jkw': 'ws' 31 | } 32 | const actionTips = { 33 | seturl: { 34 | key: 'jkset', 35 | icon: icon, 36 | id: 'action-seturl', 37 | title: 'set jenkins url', 38 | desc: 'input jenkins url,then press enter' 39 | }, 40 | errorurl: { 41 | key: 'jk', 42 | icon: icon, 43 | id: 'action-error', 44 | title: 'can not fetch jenkins jobs', 45 | desc: 'please use jkset to set a valid jenkins url', 46 | isWarn: true 47 | } 48 | }; 49 | 50 | let jobs = []; 51 | 52 | function getJobs() { 53 | if (jobs.length) { 54 | return new Promise(resolve => { 55 | resolve(jobs) 56 | }) 57 | } 58 | 59 | return new Promise((resolve, reject) => { 60 | fetch(`${SERVER_URL}/api/json?tree=jobs[name,url,color,healthReport[description,score,iconUrl]]`) 61 | .then(resp => resp.json()) 62 | .then(results => { 63 | jobs = results.jobs 64 | resolve(jobs) 65 | }) 66 | .catch(() => { 67 | reject([actionTips.errorurl]) 68 | }) 69 | }) 70 | } 71 | 72 | function setUrl(query, callback) { 73 | let iptval = query; 74 | 75 | if (!iptval) { 76 | callback('please input an url'); 77 | return 78 | } 79 | 80 | if (iptval.endsWith('/')) { 81 | iptval = iptval.slice(0, -1) 82 | } 83 | 84 | if (iptval.indexOf('http://') === -1) { 85 | iptval = `http://${iptval}` 86 | } 87 | 88 | SERVER_URL = iptval 89 | window.localStorage.jenkins_url = SERVER_URL 90 | callback(); 91 | } 92 | 93 | function showActionTips(action) { 94 | const actionTip = actionTips[action]; 95 | 96 | if (!actionTip) { 97 | return; 98 | } 99 | 100 | return [{ 101 | key: 'jk', 102 | icon: icon, 103 | id: actionTip.id, 104 | title: actionTip.title, 105 | desc: actionTip.desc 106 | }]; 107 | } 108 | 109 | function onInput(key) { 110 | if (this.cmd === 'jkset') { 111 | return showActionTips('seturl'); 112 | } 113 | 114 | if (!SERVER_URL) { 115 | this.render('jkset '); 116 | 117 | return; 118 | } 119 | 120 | return getJobs().then(results => { 121 | const filterRE = new RegExp(Reflect.apply([].slice, key, []).join('\.\*')); 122 | 123 | const filteredJobs = results.filter(job => { 124 | return key ? Boolean(job.name.match(filterRE)) : true 125 | }).slice(0, 50) 126 | 127 | return filteredJobs.map(item => { 128 | const iconUrl = item.healthReport.length ? item.healthReport[0].iconUrl : 'nobuilt.png'; 129 | 130 | return { 131 | key: key, 132 | id: item.url + keyUrlMap[this.cmd], 133 | icon: `/img/jenkins/${iconUrl}`, 134 | title: item.name, 135 | desc: item.healthReport.length ? item.healthReport[0].description : 'no build history', 136 | isWarn: item.healthReport.length && item.healthReport[0].score !== 100 137 | } 138 | }); 139 | }).catch(results => { 140 | return Promise.resolve(results); 141 | }); 142 | } 143 | 144 | function onEnter({ id }, command, query, keyStatus) { 145 | if (id.startsWith('action-')) { 146 | const actionName = id.split('-')[1]; 147 | 148 | if (actionName === 'seturl') { 149 | return new Promise(resolve => { 150 | setUrl(this.query, error => { 151 | if (error) { 152 | Toast.error(error); 153 | 154 | resolve(true); 155 | } else { 156 | Toast.success('set successfully'); 157 | jobs = []; 158 | 159 | resolve('jk '); 160 | } 161 | }); 162 | }); 163 | } 164 | } else if (id) { 165 | steward.util.createTab({ url: id }, keyStatus); 166 | } 167 | } 168 | 169 | return { 170 | author, 171 | version, 172 | name: 'Jenkins', 173 | category: 'other', 174 | icon, 175 | title, 176 | commands, 177 | onInput, 178 | onEnter 179 | }; 180 | 181 | } -------------------------------------------------------------------------------- /plugins/juejin.js: -------------------------------------------------------------------------------- 1 | module.exports = function(steward) { 2 | const version = 1; 3 | const author = 'clearives'; 4 | const name = 'Juejin'; 5 | const key = 'jj'; 6 | const type = 'keyword'; 7 | const icon = 'https://i.loli.net/2019/01/09/5c35880f1b8be.png'; 8 | const title = '掘金文章搜索(0:推荐,1:前端,2:后端)'; 9 | const subtitle = '按 Enter / Return 跳转到对应网页'; 10 | const commands = [{ 11 | key, 12 | type, 13 | title, 14 | subtitle, 15 | icon 16 | }]; 17 | 18 | function searchByType(type) { 19 | let url = 'https://timeline-merger-ms.juejin.im/v1/get_entry_by_rank?src=web&limit=50&category=all' 20 | switch (type) { 21 | case '0': 22 | url = 'https://timeline-merger-ms.juejin.im/v1/get_entry_by_rank?src=web&limit=50&category=all' 23 | break; 24 | case '1': 25 | url = 'https://timeline-merger-ms.juejin.im//v1/get_entry_by_rank?src=web&limit=50&category=5562b415e4b00c57d9b94ac8' 26 | break; 27 | case '2': 28 | url = 'https://timeline-merger-ms.juejin.im//v1/get_entry_by_rank?src=web&limit=50&category=5562b419e4b00c57d9b94ae2' 29 | break; 30 | default: 31 | } 32 | return steward.axios.get(url); 33 | } 34 | 35 | function dataFormat(list) { 36 | return list.map(item => { 37 | return { 38 | key: 'url', 39 | universal: true, 40 | icon: item.pic || icon, 41 | title: item.title, 42 | desc: '点赞数' + item.collectionCount + '评论数' + item.commentsCount + '作者: ' + item.user.username, 43 | url: item.originalUrl 44 | }; 45 | }); 46 | } 47 | 48 | function onInput(query, command) { 49 | if (query) { 50 | return searchByType(query).then(results => { 51 | const resp = results.data.d.entrylist; 52 | 53 | if (resp && resp.length) { 54 | return dataFormat(resp); 55 | } else { 56 | return steward.util.getDefaultResult(command); 57 | } 58 | }).catch(() => { 59 | return steward.util.getDefaultResult(command); 60 | }); 61 | } else { 62 | return steward.util.getDefaultResult(command); 63 | } 64 | } 65 | 66 | function onEnter(item, command, query, shiftKey, list) { 67 | 68 | } 69 | 70 | return { 71 | author, 72 | version, 73 | name, 74 | category: 'other', 75 | icon, 76 | title, 77 | commands, 78 | onInput, 79 | onEnter 80 | }; 81 | } -------------------------------------------------------------------------------- /plugins/newtabsrc.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (steward) { 3 | const version = 1; 4 | const author = 'solobat'; 5 | const name = 'NewTab Src'; 6 | const key = 'ntsrc'; 7 | const type = 'keyword'; 8 | const icon = 'https://wx1.sinaimg.cn/large/6836364ely1ge0j4vfvzfj203k03k3yb.jpg'; 9 | const title = 'Customize the URL of newtab'; 10 | const subtitle = 'Type "reset" to restore the default page'; 11 | const commands = [{ 12 | key, 13 | type, 14 | title, 15 | subtitle, 16 | icon 17 | }]; 18 | 19 | function onInput(query, command) { 20 | return steward.util.getDefaultResult(command); 21 | } 22 | 23 | const STORAGE_KEY = 'newtabSrc' 24 | 25 | function onEnter(item, command, query, keyStatus, list) { 26 | if (query === 'reset') { 27 | steward.chrome.storage.sync.remove(STORAGE_KEY, function() { 28 | steward.util.toast.success('Set successfully') 29 | }) 30 | } else { 31 | steward.chrome.storage.sync.set({ 32 | [STORAGE_KEY]: query 33 | }, function() { 34 | steward.util.toast.success('Set successfully') 35 | }) 36 | } 37 | } 38 | 39 | return { 40 | author, 41 | version, 42 | name, 43 | category: 'other', 44 | icon, 45 | title, 46 | commands, 47 | onInput, 48 | onEnter 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /plugins/queryzmzhot.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (steward) { 3 | const version = 1; 4 | const author = 'solobat'; 5 | const name = 'Zimuzu hot movies'; 6 | const key = 'zmhot'; 7 | const type = 'keyword'; 8 | const icon = 'https://ws1.sinaimg.cn/large/6836364ely1fzopj47on0j205k05kq2w.jpg'; 9 | const title = 'your plugin'; 10 | const subtitle = '基于 graphql 的网页查询'; 11 | const commands = [{ 12 | key, 13 | type, 14 | title, 15 | subtitle, 16 | icon 17 | }]; 18 | 19 | function queryHotMovies() { 20 | return steward.axios.post('http://site.oksteward.com/graphql', { 21 | query: `{ 22 | site(url: "http://www.zimuzu.io") { 23 | hot: selectAll(elem: ".top24 ul li") { 24 | title: text(elem: "a") 25 | url: href(elem: "a") 26 | } 27 | } 28 | }` 29 | }); 30 | } 31 | 32 | function onInput(query, command) { 33 | return queryHotMovies().then(({ data }) => { 34 | const movies = data.data.site.hot; 35 | 36 | return movies.map(movie => { 37 | return { 38 | universal: true, 39 | key: 'url', 40 | icon, 41 | title: movie.title, 42 | url: `http://zimuzu.io${movie.url}` 43 | } 44 | }); 45 | }); 46 | } 47 | 48 | function onEnter(item, command, query, keyStatus, list) { 49 | steward.util.copyToClipboard(item.title, true); 50 | } 51 | 52 | return { 53 | author, 54 | version, 55 | name, 56 | category: 'other', 57 | icon, 58 | title, 59 | commands, 60 | onInput, 61 | onEnter 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /plugins/restartext.js: -------------------------------------------------------------------------------- 1 | module.exports = function (steward) { 2 | const util = steward.util; 3 | const version = 1; 4 | const name = 'restartext'; 5 | const type = 'keyword'; 6 | const icon = steward.chrome.extension.getURL('iconfont/chrome.svg'); 7 | const title = chrome.i18n.getMessage('restartext_title'); 8 | const commands = [{ 9 | key: 're', 10 | type: 'keyword', 11 | title: '重启扩展', 12 | subtitle: '重启指定的 Chrome 扩展', 13 | icon 14 | }]; 15 | 16 | function onInput(query) { 17 | return new Promise((resolve, reject) => { 18 | try { 19 | steward.chrome.management.getAll(extensions => { 20 | if (steward.chrome.runtime.lastError) { 21 | util.toast.error('获取扩展列表失败'); 22 | resolve([]); 23 | return; 24 | } 25 | 26 | const items = extensions 27 | .filter(ext => ext.type === 'extension' && ext.enabled) 28 | .map(ext => ({ 29 | icon: ext.icons && ext.icons[0] ? ext.icons[0].url : icon, 30 | key: 'action', 31 | title: ext.name, 32 | desc: ext.description || ext.id, 33 | id: ext.id 34 | })); 35 | 36 | if (!query) { 37 | resolve(items); 38 | return; 39 | } 40 | 41 | const filtered = items.filter(item => { 42 | return item.title.toLowerCase().includes(query.toLowerCase()) || 43 | item.desc.toLowerCase().includes(query.toLowerCase()); 44 | }); 45 | 46 | resolve(filtered); 47 | }); 48 | } catch (error) { 49 | util.toast.error('插件执行出错'); 50 | resolve([]); 51 | } 52 | }); 53 | } 54 | 55 | function onEnter(item, command, query, keyStatus) { 56 | return new Promise((resolve, reject) => { 57 | try { 58 | steward.chrome.management.setEnabled(item.id, false, () => { 59 | if (steward.chrome.runtime.lastError) { 60 | util.toast.error('禁用扩展失败'); 61 | resolve(false); 62 | return; 63 | } 64 | 65 | setTimeout(() => { 66 | steward.chrome.management.setEnabled(item.id, true, () => { 67 | if (steward.chrome.runtime.lastError) { 68 | util.toast.error('启用扩展失败'); 69 | resolve(false); 70 | return; 71 | } 72 | util.toast.success('扩展已重启'); 73 | resolve(true); 74 | }); 75 | }, 500); 76 | }); 77 | } catch (error) { 78 | util.toast.error('插件执行出错'); 79 | resolve(false); 80 | } 81 | }); 82 | } 83 | 84 | return { 85 | author: 'solobat', 86 | version, 87 | name, 88 | category: 'browser', 89 | type, 90 | icon, 91 | title, 92 | commands, 93 | onInput, 94 | onEnter, 95 | canDisabled: true 96 | }; 97 | }; -------------------------------------------------------------------------------- /plugins/runcommand.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (steward) { 3 | const version = 3; 4 | const author = 'solobat'; 5 | const name = 'Run Command'; 6 | const key = 'start'; 7 | const type = 'keyword'; 8 | const icon = 'https://i.imgur.com/iBTbPYs.png'; 9 | const title = 'Run Steward Helper Command'; 10 | const subtitle = 'Please install Steward Helper first'; 11 | const commands = [{ 12 | key, 13 | type, 14 | title, 15 | subtitle, 16 | icon 17 | }]; 18 | // args: extId 19 | const extId = 'hcnekoladldejmeindnhpjkfhjadcick' 20 | 21 | function queryByFilter(query = '') { 22 | const filterByName = suggestions => steward.util.getMatches(suggestions, query, 'title'); 23 | return new Promise(resolve => { 24 | chrome.runtime.sendMessage(extId, { 25 | action: 'listActions' 26 | }, ({ data = [] }) => { 27 | resolve((query ? filterByName(data) : data).map(actionFormat)); 28 | }); 29 | }); 30 | } 31 | 32 | function actionFormat(item) { 33 | return { 34 | key, 35 | id: item.name, 36 | icon, 37 | title: item.name, 38 | desc: item.title 39 | } 40 | } 41 | 42 | function notice2RunCommand(item) { 43 | chrome.runtime.sendMessage(extId, { 44 | action: 'runCommand', 45 | data: { 46 | command: item.id 47 | } 48 | }, ({ data = [] }) => { 49 | }); 50 | } 51 | 52 | function onInput(query, command) { 53 | return queryByFilter(query).then(items => { 54 | if (items && items.length) { 55 | return items 56 | } else { 57 | return steward.util.getDefaultResult(command); 58 | } 59 | }) 60 | } 61 | 62 | function onEnter(item, command, query, keyStatus, list) { 63 | notice2RunCommand(item) 64 | } 65 | 66 | return { 67 | mode: 'content', 68 | author, 69 | version, 70 | name, 71 | category: 'other', 72 | icon, 73 | title, 74 | commands, 75 | onInput, 76 | onEnter 77 | }; 78 | } 79 | -------------------------------------------------------------------------------- /plugins/sspai.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (steward) { 3 | const version = 3; 4 | const author = 'solobat'; 5 | const name = 'sspai articles'; 6 | const key = 'ssp'; 7 | const type = 'keyword'; 8 | const icon = 'https://cdn.sspai.com/sspai/assets/img/favicon/icon.ico'; 9 | const title = '少数派首页文章'; 10 | const subtitle = '按 Enter / Return 跳转到少数派网页'; 11 | const commands = [{ 12 | key, 13 | type, 14 | title, 15 | subtitle, 16 | icon, 17 | shiftKey: true 18 | }]; 19 | 20 | function fetchList() { 21 | return steward.axios.get('https://sspai.com/api/v1/articles', { 22 | params: { 23 | offset: 0, 24 | limit: 20, 25 | type: 'recommend_to_home', 26 | sort: 'recommend_to_home_at', 27 | include_total: false 28 | } 29 | }); 30 | } 31 | 32 | function dataFormat(list) { 33 | return list.map(item => { 34 | const url = `https://sspai.com/post/${item.id}`; 35 | 36 | return { 37 | key: 'url', 38 | universal: true, 39 | icon: `https://cdn.sspai.com/${item.author.avatar}`, 40 | title: item.promote_intro, 41 | desc: item.summary || url, 42 | url 43 | }; 44 | }); 45 | } 46 | 47 | function onInput(query, command) { 48 | return fetchList().then(results => { 49 | const resp = results.data; 50 | 51 | if (resp.list && resp.list.length) { 52 | return dataFormat(resp.list); 53 | } else { 54 | return steward.util.getDefaultResult(command); 55 | } 56 | }).catch(() => { 57 | return steward.util.getDefaultResult(command); 58 | }); 59 | } 60 | 61 | function onEnter(item, command, query, shiftKey, list) { 62 | 63 | } 64 | 65 | return { 66 | author, 67 | version, 68 | name, 69 | category: 'other', 70 | icon, 71 | title, 72 | commands, 73 | onInput, 74 | onEnter 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /plugins/stock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * stocks: 我的自选股, ctrl + Enter 移除选中的自选 3 | * code: 查询股票代码, Enter 添加自选 4 | */ 5 | 6 | module.exports = function (steward) { 7 | const version = 6; 8 | const author = 'solobat'; 9 | const name = 'Stocks'; 10 | const type = 'keyword'; 11 | const icon = 'https://i.imgur.com/Qc7XZqB.png'; 12 | const title = '股票行情'; 13 | const commands = [{ 14 | key: 'stocks', 15 | type, 16 | title: '股票行情', 17 | subtitle: '数据来自新浪财经', 18 | icon 19 | }, { 20 | key: 'code', 21 | type, 22 | title: '股票代码查询', 23 | subtitle: '数据来自雪球', 24 | icon 25 | }]; 26 | const icons = { 27 | stockRed: 'https://i.imgur.com/oXSZKcG.png', 28 | stockGreen: 'https://i.imgur.com/bdF3bGs.png' 29 | }; 30 | 31 | let codeList; 32 | const STORAKE_KEY = 'stocks_codelist'; 33 | const browser = steward.browser; 34 | 35 | function restoreCodeList() { 36 | return browser.storage.sync.get(STORAKE_KEY).then(resp => { 37 | codeList = resp[STORAKE_KEY] || [ 38 | 'sh601238', 39 | 'sh600816', 40 | 'sh600332', 41 | 'sh600703', 42 | 'sh600487', 43 | 'sh600887', 44 | 'sz000895', 45 | 'sh601668', 46 | 'sh600999', 47 | 'sh601006', 48 | 'sz000651', 49 | 'sz002142', 50 | 'sz000963' 51 | ]; 52 | 53 | return codeList; 54 | }); 55 | } 56 | 57 | function getCodeList() { 58 | if (codeList) { 59 | return Promise.resolve(codeList.map(item => item.toLowerCase())); 60 | } else { 61 | return restoreCodeList().then(() => { 62 | return Promise.resolve(codeList.map(item => item.toLowerCase())); 63 | }); 64 | } 65 | } 66 | 67 | function saveCodeList() { 68 | return browser.storage.sync.set({ 69 | [STORAKE_KEY]: codeList 70 | }); 71 | } 72 | 73 | function updateCodeList(code, action = 'add') { 74 | const fixedCode = code.toLowerCase(); 75 | const index = codeList.indexOf(fixedCode); 76 | 77 | if (index === -1) { 78 | if (action === 'add') { 79 | codeList.push(fixedCode); 80 | 81 | return saveCodeList(); 82 | } else { 83 | return Promise.reject('无效的操作'); 84 | } 85 | } else { 86 | if (action === 'remove') { 87 | codeList.splice(index, 1); 88 | 89 | return saveCodeList(); 90 | } else { 91 | return Promise.reject('股票代码已经存在'); 92 | } 93 | } 94 | } 95 | 96 | const baseStockUrl = 'https://xueqiu.com/S'; 97 | const api = { 98 | list: 'https://hq.sinajs.cn/list=' 99 | } 100 | 101 | function dataFormat(list) { 102 | return list.map(item => { 103 | return { 104 | key: 'url', 105 | code: item.code, 106 | icon: item.isUp ? icons.stockRed : icons.stockGreen, 107 | title: `${item.name}[${item.code}]`, 108 | desc: `涨幅: ${item.isUp ? '+' : ''}${item.percent} -- 当前价格: ${item.price} -- 昨日收盘价${item.predayPrice}`, 109 | url: `${baseStockUrl}/${item.code}` 110 | }; 111 | }); 112 | } 113 | 114 | function respHandle(resp) { 115 | try { 116 | return resp.replace(/var\s/g, '').split(';') 117 | .filter(str => str.indexOf('=') !== -1) 118 | .map(str => { 119 | const arr = str.split('='); 120 | const data = arr[1].split(','); 121 | const code = arr[0].split('_').pop().toUpperCase(); 122 | const percent = ((data[3] / data[2] - 1)* 100).toFixed(2); 123 | const isUp = percent >= 0; 124 | 125 | return { 126 | code, 127 | name: data[0].substr(1), 128 | predayPrice: data[2], 129 | price: data[3], 130 | percent: `${((data[3] / data[2] - 1)* 100).toFixed(2)}%`, 131 | isUp 132 | }; 133 | }) 134 | .sort((a, b) => parseFloat(a.percent, 10) > parseFloat(b.percent, 10) ? -1 : 1); 135 | } catch (error) { 136 | return Promise.reject(null); 137 | } 138 | } 139 | 140 | function fetchStocksData() { 141 | return getCodeList().then(list => { 142 | const codes = list.join(','); 143 | 144 | return steward.axios.request({ 145 | method: 'GET', 146 | url: `${api.list}${codes}`, 147 | responseType: 'text' 148 | }).then(({ data }) => { 149 | return respHandle(data); 150 | }); 151 | }); 152 | } 153 | 154 | function handleStocksInput(command) { 155 | return fetchStocksData() 156 | .then(list => { 157 | if (list && list.length) { 158 | return dataFormat(list); 159 | } else { 160 | return steward.util.getEmptyResult(command); 161 | } 162 | }) 163 | .catch(() => { 164 | return steward.util.getEmptyResult(command); 165 | }); 166 | } 167 | 168 | function queryCode(query) { 169 | return steward.axios.get('https://xueqiu.com/stock/search.json', { 170 | params: { 171 | code: query, 172 | size: 50 173 | } 174 | }).then(({ data }) => { 175 | return data.stocks; 176 | }); 177 | } 178 | 179 | function handleCodeInput(query, command) { 180 | if (query) { 181 | return queryCode(query).then(stocks => { 182 | return stocks.map(item => { 183 | return { 184 | icon, 185 | title: `${item.name}: ${item.code}`, 186 | desc: `${item.ind_name}`, 187 | code: `${item.code}` 188 | } 189 | }); 190 | }); 191 | } else { 192 | return steward.util.getDefaultResult(command); 193 | } 194 | } 195 | 196 | function onInput(query, command) { 197 | if (command.key === 'stocks') { 198 | return handleStocksInput(command); 199 | } else if (command.key === 'code') { 200 | return handleCodeInput(query, command); 201 | } 202 | } 203 | 204 | function handleStocksEnter(item, keyStatus) { 205 | if (keyStatus.shiftKey) { 206 | // 打开主要财务指标页面,方便查看 ROA/ROE/PE 207 | chrome.tabs.create({ url: item.url + '/detail#/ZYCWZB' }); 208 | } else if (keyStatus.ctrlKey) { 209 | updateCodeList(item.code, 'remove').then(() => { 210 | steward.util.toast.success('删除自选成功'); 211 | window.stewardApp.app.applyCommand('stocks '); 212 | }); 213 | } else { 214 | chrome.tabs.create({ url: item.url }); 215 | } 216 | } 217 | 218 | function handleCodeEnter(item) { 219 | return updateCodeList(item.code).then(() => { 220 | steward.util.toast.success('添加自选成功'); 221 | window.stewardApp.app.applyCommand('code '); 222 | }).catch(msg => { 223 | steward.util.toast.warning(msg); 224 | }); 225 | } 226 | 227 | function onEnter(item, command, query, keyStatus, list) { 228 | if (command.key === 'stocks') { 229 | handleStocksEnter(item, keyStatus); 230 | } else if (command.key === 'code') { 231 | handleCodeEnter(item); 232 | } 233 | } 234 | 235 | return { 236 | author, 237 | version, 238 | name, 239 | category: 'other', 240 | icon, 241 | title, 242 | commands, 243 | onInput, 244 | onEnter 245 | }; 246 | } 247 | -------------------------------------------------------------------------------- /plugins/tabplus.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (steward) { 3 | const version = 1; 4 | const author = 'solobat'; 5 | const name = 'Tabs plus'; 6 | const type = 'keyword'; 7 | const icon = 'https://i.imgur.com/QcoukjA.png'; 8 | const title = 'Manage tabs in all the window'; 9 | const subtitle = 'Manage tabs in all the window.'; 10 | const commands = [{ 11 | key: 'atab', 12 | type, 13 | title, 14 | subtitle, 15 | icon 16 | }]; 17 | 18 | function getAllTabs(query) { 19 | return new Promise((resolve) => { 20 | chrome.windows.getAll({ populate: true }, function (wins) { 21 | if (wins.length) { 22 | const tabs = wins.reduce((memo, win) => { 23 | memo.push(...win.tabs) 24 | return memo 25 | }, []) 26 | 27 | const results = tabs.filter(function (tab) { 28 | return steward.util.matchText(query, `${tab.title}${tab.url}`); 29 | }); 30 | 31 | resolve(results) 32 | } else { 33 | resolve([]); 34 | } 35 | }); 36 | }) 37 | } 38 | 39 | function dataFormat(list, command) { 40 | return list.map(function (item, index) { 41 | let desc = command.subtitle; 42 | 43 | return { 44 | key: command.key, 45 | id: item.id, 46 | icon: item.favIconUrl || chrome.extension.getURL('img/icon.png'), 47 | title: `[Win: ${item.windowId}]${item.title}`, 48 | desc, 49 | isWarn: item.active, 50 | raw: item 51 | }; 52 | }); 53 | } 54 | 55 | function onInput(query, command) { 56 | return getAllTabs(query).then(tabs => { 57 | return dataFormat(tabs, command) 58 | }) 59 | } 60 | 61 | function updateWindow(winId, updateProperties) { 62 | return chrome.windows.update(winId, updateProperties); 63 | } 64 | 65 | function updateTab(id, updateProperties, winId) { 66 | if (updateProperties.active) { 67 | updateWindow(winId, { 68 | focused: true 69 | }) 70 | } 71 | return chrome.tabs.update(id, updateProperties); 72 | } 73 | 74 | function activeOneTab(item) { 75 | updateTab(item.id, { 76 | active: true 77 | }, item.raw.windowId); 78 | } 79 | 80 | function onEnter(item, command, query, shiftKey, list) { 81 | if (command.key === 'atab') { 82 | activeOneTab(item); 83 | } 84 | } 85 | 86 | return { 87 | author, 88 | version, 89 | name, 90 | category: 'browser', 91 | icon, 92 | title, 93 | commands, 94 | onInput, 95 | onEnter 96 | }; 97 | } 98 | -------------------------------------------------------------------------------- /plugins/theme.js: -------------------------------------------------------------------------------- 1 | module.exports = function (steward) { 2 | const version = 3; 3 | const author = 'solobat'; 4 | const name = 'Themes'; 5 | const type = 'keyword'; 6 | const icon = 'https://s1.ax1x.com/2020/06/22/NGUkWT.png'; 7 | const title = 'Switch theme'; 8 | const subtitle = 'Press return/enter to switch the status of the current theme'; 9 | const commands = [{ 10 | key: 'ts', 11 | type, 12 | title, 13 | subtitle, 14 | icon 15 | }]; 16 | 17 | function setEnabled(id, enabled) { 18 | steward.chrome.management.setEnabled(id, enabled, function () {}); 19 | } 20 | 21 | function getThemes(query, callback) { 22 | steward.chrome.management.getAll(function (themeList) { 23 | const matchThemes = themeList.filter(function (theme) { 24 | return theme.type === 'theme'; 25 | }); 26 | console.log("getThemes -> matchThemes", matchThemes) 27 | 28 | callback(matchThemes); 29 | }); 30 | } 31 | 32 | function dataFormat(rawList) { 33 | return rawList.map(function (item) { 34 | const url = item.icons instanceof Array ? item.icons[item.icons.length - 1].url : ''; 35 | const isWarn = item.enabled; 36 | 37 | return { 38 | key: 'ts', 39 | id: item.id, 40 | icon: url || icon, 41 | title: item.name, 42 | desc: item.description, 43 | meta: item, 44 | isWarn 45 | }; 46 | }); 47 | } 48 | 49 | function onInput(query) { 50 | return new Promise(resolve => { 51 | getThemes(query.toLowerCase(), function (matchThemes) { 52 | resolve(dataFormat(matchThemes)); 53 | }); 54 | }); 55 | } 56 | 57 | function onEnter(item) { 58 | if (item && item.id) { 59 | setEnabled(item.id, !item.meta.enabled); 60 | window.slogs.push(`Enable: ${item.title}`); 61 | window.stewardApp.app.refresh(); 62 | } 63 | } 64 | 65 | return { 66 | author, 67 | version, 68 | name, 69 | category: 'browser', 70 | icon, 71 | title, 72 | commands, 73 | onInput, 74 | onEnter 75 | }; 76 | } -------------------------------------------------------------------------------- /plugins/times.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description todo 3 | * @author tomasy 4 | * @email solopea@gmail.com 5 | */ 6 | 7 | module.exports = function(steward) { 8 | const util = steward.util; 9 | const Toast = steward.util.toast; 10 | const STORAGE = steward.constant.STORAGE; 11 | const browser = steward.browser; 12 | const dayjs = steward.dayjs; 13 | 14 | const author = 'solobat'; 15 | const version = 2; 16 | const name = 'times'; 17 | const keys = [ 18 | { key: 'ts' }, 19 | { key: 'tsd' } 20 | ]; 21 | const type = 'keyword'; 22 | const icon = chrome.extension.getURL('iconfont/plusone.svg'); 23 | const title = chrome.i18n.getMessage(`${name}_title`); 24 | const commands = util.genCommands(name, icon, keys, type); 25 | const formatWarningMsg = chrome.i18n.getMessage('times_ts_warn_format'); 26 | 27 | const queryReg = /^([^/]+)\/([mwd])\/([1-9]\d*)(?:\/([+-]))?$/i; 28 | let intervalMap; 29 | 30 | if (chrome.i18n.getUILanguage().indexOf('zh') > -1) { 31 | intervalMap = { 32 | m: '每月', 33 | w: '每周', 34 | d: '每天' 35 | }; 36 | } else { 37 | intervalMap = { 38 | m: 'Month', 39 | w: 'Week', 40 | d: 'Day' 41 | }; 42 | } 43 | 44 | const titleTemplate = chrome.i18n.getMessage(`times_ts_tpl_title`); 45 | const subtitleMoreTemplate = chrome.i18n.getMessage(`times_ts_tpl_subtitle_more`); 46 | const subtitleLessTemplate = chrome.i18n.getMessage(`times_ts_tpl_subtitle_less`); 47 | 48 | function handleTimesInput(query, command) { 49 | if (!query || command.orkey === 'tsd') { 50 | return getTimes().then(times => { 51 | if (times && times.length) { 52 | return dataFormat(times || [], command); 53 | } else { 54 | return util.getDefaultResult(command); 55 | } 56 | }); 57 | } else { 58 | return util.getDefaultResult(command); 59 | } 60 | } 61 | 62 | function onInput(query, command) { 63 | const orkey = command.orkey; 64 | 65 | if (orkey === 'ts') { 66 | return handleTimesInput(query, command); 67 | } else if (orkey === 'tsd') { 68 | return handleTimesInput(query, command); 69 | } 70 | } 71 | 72 | function handleTimesaEnter(item, command, query, shiftKey) { 73 | if (query) { 74 | return addTimes(query, command); 75 | } else { 76 | return updateCount(item.raw, shiftKey); 77 | } 78 | } 79 | 80 | function onEnter(item, command, query, { shiftKey }) { 81 | const orkey = command.orkey; 82 | 83 | if (orkey === 'ts') { 84 | return handleTimesaEnter(item, command, query, shiftKey); 85 | } else if (orkey === 'tsd') { 86 | return removeTimes(item.raw, command); 87 | } 88 | } 89 | 90 | function updateCount(item, shiftKey) { 91 | const newCount = shiftKey ? item.curCount - 1 : item.curCount + 1; 92 | 93 | item.curCount = newCount || 0; 94 | 95 | if (item.curCount < 0) { 96 | item.curCount = 0; 97 | } 98 | 99 | return updateTimes(item); 100 | } 101 | 102 | function createTimes(text, task, interval, count, overflowType = '+') { 103 | return { 104 | text, 105 | task, 106 | interval, 107 | count, 108 | curCount: 0, 109 | date: Number(new Date()), 110 | overflowType 111 | }; 112 | } 113 | 114 | function updateTimes(newItem, command) { 115 | return getTimes().then((resp = []) => { 116 | const times = resp; 117 | const oldItem = times.find(item => item.task === newItem.task); 118 | 119 | if (oldItem) { 120 | Object.assign(oldItem, newItem); 121 | } else { 122 | times.push(newItem); 123 | } 124 | 125 | return browser.storage.sync.set({ 126 | [STORAGE.TIMES]: times 127 | }).then(() => { 128 | if (command) { 129 | return `${command.key} `; 130 | } else { 131 | return ''; 132 | } 133 | }); 134 | }); 135 | } 136 | 137 | function removeTimes(ditem, command) { 138 | return getTimes().then((resp = []) => { 139 | const times = resp.filter(item => item.task !== ditem.task); 140 | 141 | return browser.storage.sync.set({ 142 | [STORAGE.TIMES]: times 143 | }).then(() => { 144 | if (command) { 145 | return `${command.key} `; 146 | } else { 147 | return ''; 148 | } 149 | }); 150 | }); 151 | } 152 | 153 | function addTimes(query, command) { 154 | const match = query.trim().match(queryReg); 155 | if (match) { 156 | const [text, task, interval, count, overflowType] = match; 157 | const newTask = createTimes(text, task, interval, count, overflowType); 158 | 159 | return util.isStorageSafe(STORAGE.TIMES).then(() => { 160 | return updateTimes(newTask, command); 161 | }).catch(() => { 162 | Toast.warning(chrome.i18n.getMessage('STORAGE_WARNING')); 163 | 164 | return Promise.reject(); 165 | }); 166 | } else { 167 | Toast.warning(formatWarningMsg); 168 | } 169 | } 170 | 171 | function getTimes() { 172 | return browser.storage.sync.get(STORAGE.TIMES).then(results => results[STORAGE.TIMES]); 173 | } 174 | 175 | function getTaskText(item) { 176 | const data = { 177 | task: item.task, 178 | interval: intervalMap[item.interval], 179 | curCount: item.curCount || 0 180 | }; 181 | 182 | return util.simTemplate(titleTemplate, data); 183 | } 184 | 185 | function getTaskSubtitle(item) { 186 | const data = { 187 | count: item.count 188 | }; 189 | 190 | if (item.overflowType === '-') { 191 | return util.simTemplate(subtitleLessTemplate, data); 192 | } else { 193 | return util.simTemplate(subtitleMoreTemplate, data); 194 | } 195 | } 196 | 197 | function checkIsWarn({ count, curCount = 0, overflowType = '+' }) { 198 | if (overflowType === '+') { 199 | return curCount < count; 200 | } else { 201 | return curCount > count; 202 | } 203 | } 204 | 205 | function dataFormat(rawList) { 206 | return rawList.map(function (item) { 207 | return { 208 | key: 'plugin', 209 | id: item.id, 210 | icon: icon, 211 | title: getTaskText(item), 212 | desc: getTaskSubtitle(item), 213 | isWarn: checkIsWarn(item), 214 | raw: item 215 | }; 216 | }); 217 | } 218 | 219 | function isAfter(date, taskType) { 220 | return dayjs().isAfter(date, taskType); 221 | } 222 | 223 | function refreshTimes() { 224 | const timeInterval = { 225 | d: 'day', 226 | w: 'week', 227 | m: 'month' 228 | }; 229 | 230 | getTimes() 231 | .then((times = []) => { 232 | let changed = false; 233 | 234 | times.forEach(item => { 235 | const taskType = timeInterval[item.interval]; 236 | 237 | if (isAfter(item.date, taskType)) { 238 | item.date = Number(new Date()); 239 | item.curCount = 0; 240 | changed = true; 241 | console.log('refresh: ', item); 242 | } 243 | }); 244 | 245 | if (changed) { 246 | return times; 247 | } else { 248 | return null; 249 | } 250 | }) 251 | .then(times => { 252 | if (times) { 253 | return browser.storage.sync.set({ 254 | [STORAGE.TIMES]: times 255 | }); 256 | } 257 | }); 258 | } 259 | 260 | refreshTimes(); 261 | 262 | return { 263 | author, 264 | version, 265 | name: 'Times', 266 | category: 'other', 267 | icon, 268 | title, 269 | commands, 270 | onInput, 271 | onEnter 272 | } 273 | } -------------------------------------------------------------------------------- /plugins/userscripts.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (steward) { 3 | const version = 3; 4 | const author = 'solobat'; 5 | const name = 'User Scripts'; 6 | const key = 'us'; 7 | const type = 'keyword'; 8 | const icon = 'https://greasyfork.org/assets/blacklogo96-e0c2c76180916332b7516ad47e1e206b42d131d36ff4afe98da3b1ba61fd5d6c.png'; 9 | const title = '根据 host 查找 userscripts'; 10 | const subtitle = '数据来自 https://greasyfork.org/'; 11 | const commands = [{ 12 | key, 13 | type, 14 | title, 15 | subtitle, 16 | icon 17 | }]; 18 | 19 | function getSiteData(host) { 20 | const lang = chrome.i18n.getUILanguage(); 21 | 22 | return steward.axios.get(`https://greasyfork.org/${lang}/scripts/by-site/${host}.json`); 23 | } 24 | 25 | function dataFormat(list) { 26 | return list.map(item => { 27 | return { 28 | key: 'url', 29 | universal: true, 30 | icon: steward.data.page ? steward.data.page.icon : icon, 31 | title: item.name, 32 | desc: item.description, 33 | url: item.url 34 | }; 35 | }); 36 | } 37 | 38 | function onInput(query, command) { 39 | const hostReg = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/; 40 | let host = query.trim(); 41 | 42 | if (!host && steward.data.page) { 43 | host = steward.data.page.host; 44 | } 45 | 46 | if (host.startsWith('www.')) { 47 | host = host.slice(4); 48 | } 49 | 50 | if (hostReg.test(host)) { 51 | return getSiteData(host).then(results => { 52 | if (results.data && results.data.length) { 53 | return dataFormat(results.data); 54 | } else { 55 | return steward.util.getEmptyResult(command); 56 | } 57 | }); 58 | } else { 59 | return steward.util.getDefaultResult(command); 60 | } 61 | } 62 | 63 | function onEnter(item, command, query, shiftKey, list) { 64 | steward.util.copyToClipboard(item.title, true); 65 | } 66 | 67 | return { 68 | author, 69 | version, 70 | name, 71 | category: 'other', 72 | icon, 73 | title, 74 | commands, 75 | onInput, 76 | onEnter 77 | }; 78 | } 79 | -------------------------------------------------------------------------------- /plugins/wiki.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (steward) { 3 | const version = 1; 4 | const author = 'solobat'; 5 | const name = 'Wiki'; 6 | const key = 'wk'; 7 | const type = 'keyword'; 8 | const icon = 'https://wx1.sinaimg.cn/large/6836364ely1ge0iyhz1zrj2074074jr6.jpg'; 9 | const title = 'wiki'; 10 | const subtitle = 'Confluence Wiki'; 11 | const commands = [{ 12 | key, 13 | type, 14 | title, 15 | subtitle, 16 | icon 17 | }]; 18 | 19 | // TODO 20 | const HOST = '' 21 | const SERVER_URL = `${HOST}/rest/quicknav/1/search` 22 | 23 | function dataFormat(list) { 24 | return list.reduce((memo, sublist) => { 25 | const arr = sublist.map(item => { 26 | return { 27 | key: 'url', 28 | id: item.id, 29 | icon: `${HOST}/images/icons/profilepics/default.png`, 30 | universal: true, 31 | title: item.name, 32 | desc: item.href, 33 | url: `${HOST}${item.href}` 34 | } 35 | }) 36 | return memo.concat(arr) 37 | }, []) 38 | } 39 | 40 | function getQuery(query) { 41 | return new Promise((resolve, reject) => { 42 | fetch(`${SERVER_URL}?query=${query}&_=${Number(new Date)}`) 43 | .then(resp => resp.json()) 44 | .then(results => { 45 | resolve(dataFormat(results.contentNameMatches)) 46 | }) 47 | .catch(() => { 48 | reject() 49 | }) 50 | }) 51 | } 52 | 53 | function onInput(query, command) { 54 | // return a promise 55 | return getQuery(query).then(results => { 56 | if (results && results.length) { 57 | return results; 58 | } else { 59 | return steward.util.getDefaultResult(command); 60 | } 61 | }); 62 | } 63 | 64 | function onEnter(item, command, query, keyStatus, list) { 65 | steward.util.copyToClipboard(item.title, true); 66 | } 67 | 68 | return { 69 | author, 70 | version, 71 | name, 72 | category: 'other', 73 | icon, 74 | title, 75 | commands, 76 | onInput, 77 | onEnter 78 | }; 79 | } 80 | -------------------------------------------------------------------------------- /plugins/windowtabs.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (steward) { 3 | const version = 3; 4 | const author = 'solobat'; 5 | const name = 'Tabs in window'; 6 | const type = 'keyword'; 7 | const icon = 'https://i.imgur.com/QcoukjA.png'; 8 | const title = 'Manage tabs in the window'; 9 | const subtitle = 'Detach a tab and also to attach all tabs from a window.'; 10 | const commands = [{ 11 | key: 'det', 12 | type, 13 | title, 14 | subtitle, 15 | icon 16 | }, { 17 | key: 'att', 18 | type, 19 | title, 20 | subtitle, 21 | icon 22 | }]; 23 | 24 | function detachSelectedTab(item) { 25 | chrome.tabs.getSelected(null, t => { 26 | if (t.id >= 0) { 27 | if (!item.id) { 28 | chrome.windows.create({ tabId: t.id, focused: true }) 29 | } else { 30 | chrome.tabs.move(t.id, { 31 | windowId: item.id, 32 | index: item.tabIndex + 1 33 | }, console.log) 34 | } 35 | } 36 | }) 37 | } 38 | 39 | function getOtherWindows() { 40 | return new Promise(resolve => { 41 | chrome.windows.getAll({ populate: true }, wins => { 42 | if (wins.length) { 43 | let curWin; 44 | 45 | curWin = wins.find(win => win.focused); 46 | 47 | function getOthers() { 48 | const otherWins = wins.filter(win => win.id !== curWin.id); 49 | 50 | resolve(otherWins); 51 | } 52 | 53 | if (!curWin) { 54 | // popup mode 55 | chrome.windows.getLastFocused({ populate: true }, result => { 56 | curWin = result; 57 | 58 | getOthers(); 59 | }) 60 | } else { 61 | getOthers(); 62 | } 63 | } else { 64 | resolve([]); 65 | } 66 | }); 67 | }); 68 | } 69 | 70 | function attachTabs() { 71 | chrome.windows.getAll({ populate: true }, wins => { 72 | if (wins.length) { 73 | let curTabs = []; 74 | let curWin; 75 | let otherTabs = []; 76 | 77 | wins.forEach(win => { 78 | if (win.focused) { 79 | curTabs = win.tabs; 80 | curWin = win; 81 | } else { 82 | otherTabs = otherTabs.concat(win.tabs); 83 | } 84 | }); 85 | 86 | function moveTabs() { 87 | let i = curTabs.length; 88 | 89 | otherTabs.forEach(({ id: tabId }) => { 90 | chrome.tabs.move(tabId, { windowId: curWin.id, index: i++ }, console.log) 91 | }) 92 | } 93 | 94 | if (curWin) { 95 | moveTabs(); 96 | } else { 97 | // popup mode 98 | chrome.windows.getLastFocused({ populate: true }, result => { 99 | curWin = result; 100 | curTabs = result.tabs; 101 | moveTabs(); 102 | }) 103 | } 104 | } else { 105 | steward.util.toast('Only one window'); 106 | } 107 | }); 108 | } 109 | 110 | function getOtherWindowsResult() { 111 | return getOtherWindows().then(wins => { 112 | return wins.map(win => { 113 | const tab = win.tabs.pop(); 114 | 115 | tab.index = win.tabs.length; 116 | 117 | return { 118 | id: win.id, 119 | icon: tab.favIconUrl || icon, 120 | title: tab.title, 121 | desc: 'detach to this window', 122 | tabId: tab.id, 123 | tabIndex: tab.index 124 | } 125 | }); 126 | }); 127 | } 128 | 129 | const defaultDetachResult = [ 130 | { 131 | icon, 132 | title: 'Detach in a new window' 133 | } 134 | ]; 135 | 136 | function onInput(query, command) { 137 | if (command.key === 'det') { 138 | return getOtherWindowsResult().then(items => { 139 | return defaultDetachResult.concat(items); 140 | }); 141 | } else { 142 | const result = steward.util.getDefaultResult(command); 143 | 144 | result[0].isDefault = false; 145 | 146 | return Promise.resolve(result); 147 | } 148 | } 149 | 150 | function onEnter(item, command, query, shiftKey, list) { 151 | if (command.key === 'det') { 152 | detachSelectedTab(item); 153 | } else if (command.key === 'att') { 154 | attachTabs(); 155 | } 156 | } 157 | 158 | return { 159 | author, 160 | version, 161 | name, 162 | category: 'browser', 163 | icon, 164 | title, 165 | commands, 166 | onInput, 167 | onEnter 168 | }; 169 | } 170 | -------------------------------------------------------------------------------- /plugins/zimuzu.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (steward) { 3 | const version = 4; 4 | const author = 'solobat'; 5 | const name = 'zimuzu movie'; 6 | const key = 'zm'; 7 | const type = 'keyword'; 8 | const icon = 'https://s1.ax1x.com/2020/06/22/NGB4Qf.png'; 9 | const title = '在 zimuzu.io 查找电影'; 10 | const subtitle = '按 Enter / Return 跳转到 zimuzu 网页'; 11 | const commands = [{ 12 | key, 13 | type, 14 | title, 15 | subtitle, 16 | icon, 17 | shiftKey: true 18 | }]; 19 | 20 | function searchKeyword(keyword) { 21 | return steward.axios.get('http://www.rrys2019.com/search/api', { 22 | params: { 23 | keyword 24 | } 25 | }); 26 | } 27 | 28 | function dataFormat(list) { 29 | return list.map(item => { 30 | const url = `http://www.rrys2019.com/resource/${item.itemid}`; 31 | 32 | return { 33 | key: 'url', 34 | universal: true, 35 | icon: item.poster || icon, 36 | title: item.title, 37 | desc: url, 38 | url 39 | }; 40 | }); 41 | } 42 | 43 | function onInput(query, command) { 44 | if (query) { 45 | return searchKeyword(query).then(results => { 46 | const resp = results.data; 47 | 48 | if (resp.status === 1 && results.data) { 49 | return dataFormat(resp.data); 50 | } else { 51 | return steward.util.getDefaultResult(command); 52 | } 53 | }).catch(() => { 54 | return steward.util.getDefaultResult(command); 55 | }); 56 | } else { 57 | return steward.util.getDefaultResult(command); 58 | } 59 | } 60 | 61 | function onEnter(item, command, query, shiftKey, list) { 62 | 63 | } 64 | 65 | return { 66 | author, 67 | version, 68 | name, 69 | category: 'other', 70 | icon, 71 | title, 72 | commands, 73 | onInput, 74 | onEnter 75 | }; 76 | } 77 | --------------------------------------------------------------------------------