imap-sync-client
v1.1.1
Published
imap sync client, always promise
Downloads
78
Readme
node-imap-sync-client
说明
网址: https://gitee.com/linuxmail/node-imap-sync-client
同步操作 imap 客户端, 见例子 examples
本客户端将来的版本也不考虑支持 fetch bodystructure / envelope, 请慎重选择
本imap客户端, 特点:
- 全部命令都是 promise 风格
- 主要用于和 IMAPD 服务器同步邮箱数据和邮件数据
- 不支持bodystructure,envelope等, 只完整下载信件, 信件解析由其他库负责
- 支持文件夹的创建/删除/移动(改名)
- 支持邮件的复制/移动/删除/标记/上传
- 支持获取文件夹下邮件UID列表
- 各种方法返回的邮箱文件夹名字都是 Buffer
接口 interface
对象初始化 选项
interface ImapSyncClientOptions extends socketSyncBuffer.options {
user: string // 用户
pass: string // 密码
tryStartTLS?: boolean // 如果服务器支持就启动 STARTTLS
startTLS?: boolean // 是否启动 STARTTLS
cmdIdInfo?: string // imap 命令 ID 的具体内容, 一般用于向服务器表明身份
}
回调函数类型, 记录通讯协议内容
interface ReadWriteRecordHandler {
(type: string, data: Buffer): void // type: read/write
}
读取一行, 解析为一组 token
export interface ReadOneLineResult {
tokens: Buffer[]
extraDataLength: number // 最后一个token 形如: {123}
}
命令 status 结果解析
interface MboxStatus {
messages: number
recent: number
uidnext: number
uidvalidity: number
unseen: number
}
命令 list/lsub 返回的结果按行解析
interface MboxAttrs {
noinferiors: boolean
noselect: boolean
junk: boolean
trash: boolean
sent: boolean
drafts: boolean
}
命令 select 返回的结果解析
interface MboxSelect {
exists: number
recent: number
uidvalidity: number
uidnext: number
highestmodseq: number
}
文件夹信息
interface MboxInfo {
pathname: Buffer, // 文件夹名(Buffer), 返回的文件夹名字可能不是 imap-utf-7 编码
mboxNameUtf8: string, // 文件夹名), 一定是 ""
attrs: mboxAttrs
status?: mboxStatus
subscribed?: boolean
}
邮件标记
interface MailFlags {
answered?: boolean // 是否已回复
seen?: boolean // 是否已读
draft?: boolean // 是否草稿
flagged?: boolean // 是否标记(星标)
deleted?: boolean // 是否删除
}
邮件标记 + 邮件 UID, 用于邮件列表
interface MailUidWithFlags extends mailFlags {
uid: number
}
uidplus 扩展, 移动/复制/上传的结果
interface UidplusResult {
uidvalidity: number,
uid: number
}
使用方法
见例子: examples/imap.js
创建对象
const imapSyncClient = require("imap-sync-sclient")
let ic = new imapSyncClient.imapSyncClient({
host: "127.0.0.1",
port: 143,
user: "[email protected]",
pass: "password",
tryStartTLS: true,
})
打开连接并初始化
打开imap连接,并认证等, 使用者可以自己实现类似的方法
// 返回 null 表示网络错误, 否则返回 boolean 值, true 表示成功
async open()
发起 STARTTLS 握手
发起命令 STARTTLS, 然后开始 ssl 握手
// 返回 null 表示网络错误, 否则返回 boolean 值, true 表示成功
async cmdStartTLS();
读取welcome
// 返回 null 表示网络错误, 否则返回 Buffer
// open() 方法内会调用这个方法
async readWelcome()
命令 capability
// 执行执行命令 capability 并返回结果, 同时保存到缓存
async forceGetCapability()
// 首先从缓存中取值
async getCapability()
登录
现在只支持 login
// 返回 null 表示网络失败, 否则返回 boolean, true表示认证成功
// open() 方法会调用这个方法
async login()
命令 ID
open() 方法会调用这个方法
// 返回 null 表示网络失败, 否则返回 boolean, true表示认证成功
// idInfo 为空则使用对象初始化参数 cmdIdInfo
async cmdId(idInfo?: string)
命令 LIST/LSUB
// 返回 null 表示网络失败, 否则返回 mboxInfo[]
async getMboxList()
async getSubscribedMboxList()
// 获取文件夹全部信息(LIST + LSUB + STATUS)
async getAllMboxInfos()
命令 create, 创建文件夹
// 返回 null 表示网络失败, 否则返回 boolean, true表示成功
async createMbox(pathname: string | Buffer)
// 创建文件夹, 并订阅
async createAndSubscribeMbox(pathname: string | Buffer)
命令 delete, 删除文件夹
// 返回 null 表示网络失败, 否则返回 boolean, true表示成功
async deleteMbox(pathname: string | Buffer)
命令 subscribe, 订阅文件夹
// 返回 null 表示网络失败, 否则返回 boolean, true表示成功
async subscribeMbox(pathname: string | Buffer)
命令 unSubscribe, 取消订阅文件夹
// 返回 null 表示网络失败, 否则返回 boolean, true表示成功
async unSubscribeMbox(pathname: string | Buffer)
命令 rename, 文件夹改名
// 返回 null 表示网络失败, 否则返回 boolean, true表示成功
async renameMbox(fromPathname: string | Buffer, toPathname: string | Buffer)
命令 select, 选择(打开)文件夹
// 返回 null 表示网络失败, 返回 false 表示不存在, 否则返回 mboxSelect
// 命令 select, 选择(打开) 文件夹
async forceSelectMbox(pathname: string | Buffer) {
// 如过select的文件夹不变,则直接返回成功
async selectMbox(pathname: string | Buffer) {
命令 UID MOVE, 移动邮件
// 返回 null 表示网络失败, 否则返回 boolean, true表示成功
// 一封
async moveOneMailByUid(uid: number | string, toPathname: string | Buffer, options?: {
callbackForUidplus?: { (r: { uidvalidity: number, uid: number }): void }
})
// 多封
async moveMailByUid(uids: string, toPathname: string | Buffer, options?: {})
命令 MOVE, 移动邮件
// 返回 null 表示网络失败, 否则返回 boolean, true表示成功
// 一封
async moveOneMailBySn(sn: number | string, toPathname: string | Buffer, options?: {
callbackForUidplus?: { (r: { uidvalidity: number, uid: number }): void }
})
// 多封
async moveMailBySn(sns: string, toPathname: string | Buffer, options?: {})
命令 UID COPY, 复制邮件
// 返回 null 表示网络失败, 否则返回 boolean, true表示成功
// 一封
async copyOneMailByUid(uid: number | string, toPathname: string | Buffer, options?: {
callbackForUidplus?: { (r: { uidvalidity: number, uid: number }): void }
})
// 多封
async copyMailByUid(uids: string, toPathname: string | Buffer, options?: {})
命令 COPY, 复制邮件
// 返回 null 表示网络失败, 否则返回 boolean, true表示成功
// 一封
async copyOneMailBySn(sn: number | string, toPathname: string | Buffer, options?: {
callbackForUidplus?: { (r: { uidvalidity: number, uid: number }): void }
})
// 多封
async copyMailBySn(sns: string, toPathname: string | Buffer, options?: {})
命令 UID STORE, 设置标记
// 返回 null 表示网络失败, 否则返回 boolean, true表示成功
async setMailFlagByUid(uidOrUids: number | string, flags: mailFlags, set_or_unset?: boolean)
async unsetMailFlagByUid(uidOrUids: number | string, flags: mailFlags)
命令 STORE, 设置标记
// 返回 null 表示网络失败, 否则返回 boolean, true表示成功
async setMailFlagBySn(snOrSns: number | string, flags: mailFlags, set_or_unset?: boolean)
async unsetMailFlagBySn(snOrSns: number | string, flags: mailFlags)
删除信件, UID
// 返回 null 表示网络失败, 否则返回 boolean, true表示成功
async deleteMailByUid(uidOrUids: number | string)
删除信件
// 返回 null 表示网络失败, 否则返回 boolean, true表示成功
async deleteMailBySn(snOrSns: number | string)
获取邮件列表 UID + 标记
// 返回 null 表示网络失败, 否则返回 mailUidWithFlags[]
async fetchUidListWithFlags()
通过搜索命令, 获取邮件 UID 列表
// 返回 null 表示网络失败, 否则返回 number[]
// 全部邮件
async searchAllUids()
// 全部未读邮件
async searchUnseenUids()
// 全部已回复邮件
async searchAnsweredUids()
// 全部设置了已删除标记的邮件
async searchDeletedUids()
// 全部草稿邮件
async searchDraftUids()
// 全部flagged(星标)邮件
async searchFlaggedUids()
命令 append, 上传信件
// 返回 null 表示网络失败, 否则返回 boolean, true表示成功
// callbackForMailPieceData, 多次调用, 返回上传的信件的部分数据,读够mailSize就不在执行
// options.callbackForUidplus, 如果支持 uidplus 协议, 则执行
async appendMail(mboxname: Buffer | string, mailSize: number,
callbackForMailPieceData: { (): Promise<Buffer | null> },
options?: {
flags?: mailFlags
date?: any /* string, unix-time, Date */
callbackForUidplus?: { (r: uidplusResult): void }
})
imap 返回结果 OK/NO/BAD
// 返回 boolean
resultIsOk()
resultIsNo()
resultIsBad()
编译字符串
escape(str: string | Buffer): string | Buffer
// 例如:
escape("a\nb\"c") => "a\\nb\"c"
// 或
escape("a\nb\"c") => {5}
a
b"c
其他
// 设置调试模式
setDebugMode(tf = true)
// 设置回调函数,记录通讯协议
setReadWriteRecordHandler(handler: readWriteRecordHandler)
// 返回协议的最后一行
getLastReadedBuffer(): Buffer
// 是否网络错误
isNetError()
// 是否逻辑错误
isLogicError()
// 是否密码错误
isPasswordError()
扩展(基础) API
通用 IMAP 命令 封装
大部分IMAP命令可以靠这个基础封装实现
// 返回 null 表示网络失败, 否则返回 boolean, true表示成功
async generalCmd(cmdArgv: (Buffer | string)[], options?: {
callbackForUntag?: { (data: Buffer[]): Promise<void> }
callbackForTag?: { (data: Buffer[]): Promise<void> }
[keys: string]: any
})
例如:
async _searchUidsByFlag(flag: string) {
let uids: number[] = []
let res = await this.generalCmd(["UID SEARCH ", flag], {
callbackForUntag: async (tokens: Buffer[]) => {
let i;
for (i = 2; i < tokens.length; i++) {
uids.push(parseInt(tokens[i].toString()))
}
},
})
if (!res) {
return null
}
return uids
}
读取行数据,并解析为 tokens
// 读一行返回,并解析为 tokens
async readOneLineTokens()
// 读取一个完整的返回, 并解析为 tokens
async readTokens()
// 解析返回结果是不是 OK/NO/BAD
parseResult(tokens: Buffer[]): boolean
读写原始socket数据
见 this.socket, 见模块 socket-sync-buffer
字符集转码
见过太多不规范的文件夹名字, 以 "研发部" 为例子
合法的(imap-utf-7): &eBRT0ZDo-
不规范的(imap-utf-7): &eBRT0D-
非法的(utf-7): 研发部
非法的(GBK): 研发部
本库作者认为, 库不可能自动正确处理这些文件夹名字的解码, 而只是返回Buffer.
不做进一步的转码工作, 以保证通过 Buffer 能正确的操作这些文件夹
而文件夹的名字要最终转为UTF-8用于显示,使用者需要自己承担乱码的风险, 建议通过库 jschardet 来自动识别字符集
下面是规范的字符集转码方法:
//
const imapSyncClient = require("imap-sync-sclient")
// 字符集转码: imap-utf-7 => utf-8
function imapSyncClient.imapUtf7ToUtf8(str: string | Buffer): string
// 字符集转码: utf-8 => imap-utf-7
function imapSyncClient.utf8ToImapUtf7(str: string | Buffer): string