Chrome扩展开发之四——核心功能的实现思路
目录:
如果你对GmailAssist感兴趣,可以在chrome商店中搜索“Gmail助手”,或点击这里直接访问商店来安装试用;
如果你对GmailAssist的源码感兴趣,可以在我的GitHub上查看它的源码。
原计划专门用一篇博文简单介绍一下 gmail api 的,但考虑了一下觉得官方的文档已经很清楚了,也没什么不好理解的地方,我就不再专门写一篇文章来说它了。介绍 GmailAssist 的功能实现的过程中,如果有关于 gmail api 的需要再说明的地方,我会补充。
一、授权及界面显示
用户点击地址栏图标后,调用前一篇提到的用于chrome扩展进行 OAuth2 授权的库中的函数 authorize 来完成授权(详情可以查看其源码,很直观)。并向当前页面的 content script 发消息,使其通过修改页面的DOM元素,来显示 GmailAssist 的 UI。
需要在后台脚本中监听点击图标的事件,因而代码如下:
google.authorize(function () {
chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
chrome.tabs.sendMessage(tabs[0].id, {token: google.getAccessToken()}, function (response) {
//可以写点处理response的逻辑。另外,上面google.getAccessToken() 是库中的函数。
});
});
});
})
二、获取附件列表
这个很有说头。
先看几个相关的 gmail API:
首先几乎 gmail API 中所有的方法都需要提供授权信息,即通过OAuth2获得的token。几乎所有的方法也都要求一个参数指定所要操作的邮箱(userId),而这个参数可以通过赋值”me”来表明当前用户完成授权的邮箱。gmail API 的调用,都可以通过 HTTP 请求来完成。
1. Users.messages: list
用户点击“获取附件列表”按钮,就调用 gmail api 中的 Users.messages: list 方法来获取符合条件的(该方法可以带参数,获取特定的邮件集合)全部邮件。但该方法返回的结果中每个 message 默认只有 messageId 和 threadId 两个字段(后来了解到,应该可以通过指定参数,使其返回更多的字段),如这样:
},
{
"id": "152a348995acc143", "threadId": "152a348995acc143"
},
...
{
"id": "14956ab84abc30c8", "threadId": "14956ab84abc30c8"
}
],
"nextPageToken": "16041633375627414246", "resultSizeEstimate": 103
}
在GmailAssist 中,我们要的只是带有附件的邮件,因而可以在list方法的参数中指定 q=has:attachment 来获取所有含附件的邮件信息。
这个参数是咋回事呢?可以在请求的URL中指定q字段。例如:
能不能指定别的搜索条件?让q等于别的内容?可以。这个参数的值是支持gmail内置的搜索过滤条件语法的,详情可以参考这里。
从上面的 HTTP 请求结果示例中不难看出,返回信息中有个字段:nextPageToken。顾名思义,这是获取list结果的下一页的令牌。list方法返回的结果在比较多时会分页,每页默认上限100项邮件信息,因而需要返回一页后用这个token去拿下一页。最后一页没有这个字段,因此可以通过
if (list.nextPageToken) {
fetchNextList(list.nextPageToken);
}
来递归地获取到完整的邮件列表。
2. Users.messages: get
通过 list 方法拿到了带附件的所有邮件的 id,接下来为了拿到里面的附件,就得用 get 方法,挨个获取具体的邮件,并从中提取出附件的信息。get 方法的具体信息,可以看 google 的文档,不多提了。
其中,附件的下载,没有直接的 api 可用,也不需要。根据自己多次手动在邮箱里操作的试验,找到了附件下载地址的规律,在程序里把它拼出来,在用户要下载相应附件时,把下载地址从content script 发到后台即可。对具体的下载地址长什么样子感兴趣的话,可以看源码。
3. Users.drafts: list 、 Users.drafts: get 、Users.drafts: update
(补充一点:如果你熟悉 gmail,或者已经玩了玩它的这些 API 的话,不难发现,gmail 中除草稿之外的信件们都是 message 类型的,而草稿是draft类型包装了一个message。)
要完成把选中的附件插到最新草稿中,没有直接的API可用,那就只能让程序麻烦一点了。我现在采用的思路如下:
首先获取最新草稿,即先调用drafts. list 方法返回草稿列表,由于结果是按照时间顺序排列的,所以我们再拿第一个草稿的 id 来调用 get 方法,获得这封草稿的全文。
把要插入的附件所在的邮件的全文用 messages.get 获取到,从中截取出选中的附件,将这些(如果有多个的话)附件插入一个队列中,然后再依次出队,拼到之前拿到的原草稿末尾。(这中间需要一点操作,主要是为了保证格式正确。细节可以参考RFC822协议,或者自己get几个 RAW 格式的message,按base64urlsafe格式来转码看看就清楚格式了)
拼好之后,用 drafts.update 方法直接把整个拼好的新草稿按base64urlsafe格式转码的结果,作为RAW字段传回服务器即可完成草稿的更新,也即完成了附件的插入。
这三个方法具体操作跟上面的 messages.list 和 messages.get 是类似的。需要注意的是,根据上述思路,通过 get 来获取草稿时,需要在参数中指明返回 RAW 类型的草稿内容。获得的RAW字段是base64编码后的,在本地要操作需要先解码,操作完之后再编码,再发回服务器。这个编码解码可以用js的函数atob()、btoa():
//上面这句是先完成把'-'和'_'替换成'+'和'/'(即把base64urlsafe变成base64),再进行base64解码
//
之所以有这样的替换,是因为'+'和'/'出现在URI中是有含义的
encodedMsg
= btoa(newdraft).replace(/\//g, '_').replace(/\+/g, '-'); //newdraft是一个字符串,即待编码的内容
//上面这句和第一句差不多,先base64编码,然后完成替换,变成base64urlsafe
当然,这种思路虽然可以实现想要的功能,但并不好。我认为在这种思路的基础上还可以有一种优化:
在获取附件时,不通过 messages.get 方法获取邮件全文,而是获取每个附件的AttachmentId字段,用相应的字段结合 messageId 一起,通过 messages.attachments.get 方法获取附件内容。这样获取到的是MIME的一些注释和base64编码的附件内容。
进一步还可以考虑在本地 cache 一波,就是把用过的附件内容(包括相应MIME 字段)用 chrome.storage 接口来保存在本地。这里需要注意,如果这样,就需要在manifest中声明一个unlimited storage权限,否则会受到5MB的存储上限限制。
或者再换一种思路,直接上传附件。但这种思路要求先把附件文件下载,然后用 drafts.update 方法来完成附件上传。
总之几种思路都是一个核心想法:把附件先下载下来,然后再上传到草稿中,只不过咱的程序是把这套工作自动完成从而解放了用户。为啥非得这样整呢?下载再上传,多麻烦啊?没辙。因为邮件本身的格式就这么限制着了,附件都是作为MIME part 写在邮件文中的,而并不是像我最初想象的那样——在邮件中留一个指向附件的URI,附件和邮件正文分开。
三、gmail API 的使用限制
很关键!
点链接进去看官方文档,具体细节都说得很清楚。我这里大致说几点:
1. 限制分两种:总数限制 和 速率限制。
前者是针对 你的整个程序 而言的,即不同的gmail用户共享着你的程序拥有的配额。你的程序要使用gmail API是需要在google注册的(这个过程没啥复杂的,照着官方文档一点点弄就行),然后你的程序就相当于有一个自己的账号,这个所谓的“总数限制”就是针对你这个程序的“账号”而言的。具体是:免费用户(就是你了,开发者)一天可以发 10亿个单位 的请求。注意,是你的程序每天有这么多配额。具体到GmailAssist而言吧,不同的gmail用户使用它的时候,是都在消耗我的开发者账号的这个 10亿单位/天 的配额总数的。每天下来你的配额都会刷新。那么怎么查看自己的程序这一天消耗了多少配额呢?
进入Google Developer Console,选择你要查看的项目,点下一步。然后就可以在右边看到配额啊,用量啊之类的信息了。
后者是针对 具体的gmail用户 而言的,免费项目支持的最大请求速率限制为 250单位/秒/用户。但这个限制并不是绝对的,google允许短暂的burst,即你的程序可以暂时突破这一限制,但要是你持续突破它,你就会收到 HTTP 429 或者 403 或者 502 等错误,表明服务器受不了了,你太猛了。(这时候咋办?采用指数退避算法尝试重发请求。具体的我会在下一篇博文中说。)
2. 描述限制或描述使用情况的单位是啥?
我在上面都说的是多少多少单位。即官方文档中所谓的 quota unit,它是衡量和描述你的API使用量的最小单位,每次调用API中的不同方法,会消耗不同数量的 quota unit,从5到100不等,具体可以参考官方文档。
3. 除 gmail API 本身之外的限制
上面说的都是gmail API本身的限制,但用户在用你的程序时,还受到 gmail 本身使用的速率限制等,不过你的程序开发中不需要考虑这一点,你也几乎没法针对这个限制做什么。
4. Batch request 不是一个突破限制的trick
gmail API 鼓励开发者使用batch request来提高程序的性能。顾名思义,即把多个请求合并为一个请求。但这种方式下,这个合成的请求里的每个单独的请求所消耗的配额,仍然是单独计算的。
Chrome扩展开发之四——核心功能的实现思路的更多相关文章
- Chrome扩展开发之二——Chrome扩展中脚本的运行机制和通信方式
目录: 0.Chrome扩展开发(Gmail附件管理助手)系列之〇——概述 1.Chrome扩展开发之一——Chrome扩展的文件结构 2.Chrome扩展开发之二——Chrome扩展中脚本的运行机制 ...
- Chrome扩展开发(Gmail附件管理助手)系列之〇——概述
目录: 0.Chrome扩展开发(Gmail附件管理助手)系列之〇——概述 1.Chrome扩展开发之一——Chrome扩展的文件结构 2.Chrome扩展开发之二——Chrome扩展中脚本的运行机制 ...
- Chrome扩展开发之一——Chrome扩展的文件结构
目录: 0.Chrome扩展开发(Gmail附件管理助手)系列之〇——概述 1.Chrome扩展开发之一——Chrome扩展的文件结构 2.Chrome扩展开发之二——Chrome扩展中脚本的运行机制 ...
- Chrome扩展开发之三——Chrome扩展中的数据本地存储和下载
目录: 0.Chrome扩展开发(Gmail附件管理助手)系列之〇——概述 1.Chrome扩展开发之一——Chrome扩展的文件结构 2.Chrome扩展开发之二——Chrome扩展中脚本的运行机制 ...
- 【转发】NPAPI学习(Firefox和Chrome扩展开发 )
NPAPI学习(Firefox和Chrome扩展开发 ) 2011-11-08 14:41:02 by [6yang], 1172 visits, 收藏 | 返回 Firefox和Chrome扩展开发 ...
- Chrome扩展开发基础教程(附HelloWorld)
1 概述 Chrome扩展开发的基础教程,代码基于原生JS+H5,教程内容基于谷歌扩展开发官方文档. 2 环境 Chrome 88.0.4324.96 Chromium 87.0.4280.141 B ...
- 基于 webpack 的 chrome 扩展开发探索
起 最近利用闲暇时间在进行一款 chrome 扩展 V2EX-HELPER 的开发(如果巧遇 V 友欢迎试用),今天把它彻底改成了用 webpack 打包依赖的模式,不由得感概 webpack 的强大 ...
- 手把手教你Chrome扩展开发:本地存储篇
手把手教你开发chrome扩展一:开发Chrome Extenstion其实很简单 手把手教你开发Chrome扩展二:为html添加行为 手把手教你开发Chrome扩展三:关于本地存储数据 HTML5 ...
- 【java框架】MyBatis-Plus(1)--MyBatis-Plus快速上手开发及核心功能体验
1.MyBatis-Plus入门开发及配置 1.1.MyBatis-Plus简介 MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变, ...
随机推荐
- debian和ubuntu的sh dash bash
Ubuntu和debian 的 shell 默认安装的是 dash,而不是 bash.运行以下命令查看 sh 的详细信息,确认 shell 对应的程序是哪个:$ls -al /bin/sh dash ...
- Android Design Support Library——Snackbar
Snackbar是一个轻量级控件,它可以很方便的提供消息的提示和动作反馈,类似于Toast.Snackbar包括一段文字信息与一个可选的操作按钮,超时自动隐藏,也可以通过滑动来删除.效果如下所示: S ...
- OOD沉思录 --- 类和对象的关系 --- 包含关系1
4.5 如果类包含另一个类的对象,那么包含类应当向被包含的对象发送消息(调用方法). 也就是说,所有的包含关系都应当是使用关系. 如果不是这样,那么包含的类有什么用处呢?当然,面向过程的开发人员会想 ...
- Windows 2003 Server C盘空间被IIS日志文件消耗殆尽案例
今天突然收到手头一台数据库服务器的磁盘空间告警邮件,C盘空间只剩下5.41GB大小(当系统磁盘剩余空间小于总大小的10%时,发出告警邮件),如下图所示: 由于还有一些微弱印象:前阵子这台服务器的C盘剩 ...
- LightSpeed的批量更新和批量删除
1.Update对于批量操作 无论是Update还是Remove 都是使用LightSpeed的Query对象来完成. 注:Student是要进行Update的表(实体),StuName是表Stud ...
- wordpress安装记录
wordpress 已经完全部署到Linux后,进行开始安装的时候,数据库信息都填入好了(前提是:链接信息输入都正确) 然后点击会报错,说是链接数据库失败(数据库是建在阿里云服务器上的),但是具体不知 ...
- cocos2d-x之文件读写
bool HelloWorld::init() { if ( !Layer::init() ) { return false; } auto fu=FileUtils::getInstance(); ...
- cordova Process finished with exit code -1
安装完cordova之后,创建一个测试项目后,运行报Process finished with exit code -1,经过查找原因,是因为gradle没有安装,在http://www.androi ...
- [转帖]迅为4412开发板最小linux系统的存储空间修改
本文转自迅为论坛:http://www.topeetboard.com 最小linux系统的存储空间修改以修改成 1G 存储空间为例来修改,如果需要改成其他大小的存储空间,参照此方法修改即可. 首先连 ...
- 自定义input[type="file"]的样式
input[type="file"]的样式在各个浏览器中的表现不尽相同: 1. chrome: 2. firefox: 3. opera: 4. ie: 5. edge: 另外,当 ...