作者 邓超

x

  1 +<?php
  2 +
  3 +namespace Event;
  4 +
  5 +/**
  6 + * 事件
  7 + * @author:dc
  8 + * @time 2024/9/30 16:33
  9 + * Class Event
  10 + * @package Event
  11 + */
  12 +class Event {
  13 +
  14 + /**
  15 + * 同步
  16 + * @var array
  17 + */
  18 + protected static $alias = [
  19 + // 列表同步成功后
  20 + 'mail_sync_list' => [
  21 + SyncMail::class,
  22 + MailBlack::class
  23 + ],
  24 +
  25 + ];
  26 +
  27 +
  28 + /**
  29 + * @param $name
  30 + * @param mixed ...$params
  31 + * @throws \Exception
  32 + * @author:dc
  33 + * @time 2024/9/30 16:55
  34 + */
  35 + public static function call($name, ...$params){
  36 + // 是否已配置
  37 + if(isset(self::$alias[$name])){
  38 + array_map(function ($v) use ($params){
  39 + new $v(...$params);
  40 + },
  41 + is_array(self::$alias[$name])? self::$alias[$name] : [is_array(self::$alias[$name])]
  42 + );
  43 + }else if (class_exists($name)){
  44 + new $name(...$params);
  45 + }else{
  46 + throw new \Exception('event '.$name.' not found');
  47 + }
  48 +
  49 + }
  50 +
  51 +
  52 +}
  1 +<?php
  2 +
  3 +namespace Event;
  4 +
  5 +use Model\emailSql;
  6 +use Model\folderSql;
  7 +use Model\listsSql;
  8 +use Swlib\Saber;
  9 +use Swlib\SaberGM;
  10 +
  11 +/**
  12 + * 黑名单
  13 + * @author:dc
  14 + * @time 2024/9/30 17:16
  15 + * Class MailBlack
  16 + * @package Event
  17 + */
  18 +class MailBlack {
  19 +
  20 + /**
  21 + * @var \Lib\Db|\Lib\DbPool
  22 + */
  23 + private $db;
  24 +
  25 + private $blacklist = [];
  26 +
  27 + private $blackFolder = '';
  28 +
  29 + private $data;
  30 +
  31 +
  32 + public function __construct($id,$data)
  33 + {
  34 + $data['id'] = $id;
  35 +
  36 + $this->data = $data;
  37 +
  38 +// 读取黑名单
  39 + $this->blacklist = redis()->get('blacklist:'.$data['email_id']);
  40 + if($this->blacklist){
  41 + $this->blackFolder = $this->db->cache(3600)->value(folderSql::originFolder($data['email_id'],'垃圾箱'));
  42 + }
  43 +
  44 +
  45 +
  46 +
  47 +
  48 +
  49 + }
  50 +
  51 +
  52 +}
@@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
2 2
3 namespace Event; 3 namespace Event;
4 4
  5 +use Lib\Imap\Parse\MessageItem;
5 use Model\emailSql; 6 use Model\emailSql;
6 use Model\folderSql; 7 use Model\folderSql;
7 use Model\listsSql; 8 use Model\listsSql;
@@ -15,75 +16,80 @@ use Swlib\SaberGM; @@ -15,75 +16,80 @@ use Swlib\SaberGM;
15 * Class syncMail 16 * Class syncMail
16 * @package Event 17 * @package Event
17 */ 18 */
18 -class syncMail { 19 +class SyncMail {
19 /** 20 /**
20 * @var \Lib\Db|\Lib\DbPool 21 * @var \Lib\Db|\Lib\DbPool
21 */ 22 */
22 private $db; 23 private $db;
23 24
24 25
25 - public function __construct($id,$header,$data) 26 + public function __construct(int $id, array $data)
26 { 27 {
27 - $this->db = db(); 28 + if(php_sapi_name() == 'cli'){
  29 + $this->db = db();
28 30
29 // 是否是预热邮件 aicc专用 31 // 是否是预热邮件 aicc专用
30 if(!empty($header['Aicc-Hot-Mail']) || !empty($header['aicc-hot-mail'])){ 32 if(!empty($header['Aicc-Hot-Mail']) || !empty($header['aicc-hot-mail'])){
31 return $this->hot($id); 33 return $this->hot($id);
32 } 34 }
33 35
34 - // 是否在指定文件夹内  
35 - $f = $this->db->value(folderSql::first($data['folder_id'],'folder'));  
36 - if(!$f){  
37 - return true;  
38 - }  
39 - $f = folderAlias($f);  
40 - if($f=='发件箱'){  
41 - if(empty($data['to_name'])){  
42 - $data['to_name'] = []; 36 + // 是否在指定文件夹内
  37 + $f = $this->db->value(folderSql::first($data['folder_id'],'folder'));
  38 + if(!$f){
  39 + return true;
43 } 40 }
  41 + $f = folderAlias($f);
  42 + if($f=='发件箱'){
  43 + if(empty($data['to_name'])){
  44 + $data['to_name'] = [];
  45 + }
44 46
45 - $data['to_name'] = is_array($data['to_name'])?$data['to_name']:json_decode($data['to_name']); 47 + $data['to_name'] = is_array($data['to_name'])?$data['to_name']:json_decode($data['to_name']);
46 48
47 - $w = ['email' => array_map('strtolower',array_column($data['to_name'],'email'))];  
48 - }else{  
49 - $w = ['email' => strtolower($data['from'])];  
50 - }  
51 - // 是否在 预热邮箱中  
52 - if($this->db->count('select count(*) from `hot_mail` where '.dbWhere($w))){  
53 - return $this->hot($id);  
54 - } 49 + $w = ['email' => array_map('strtolower',array_column($data['to_name'],'email'))];
  50 + }else{
  51 + $w = ['email' => strtolower($data['from'])];
  52 + }
  53 + // 是否在 预热邮箱中
  54 + if($this->db->count('select count(*) from `hot_mail` where '.dbWhere($w))){
  55 + return $this->hot($id);
  56 + }
55 57
56 58
57 - // 不是预热邮箱  
58 - if($f=='收件箱'){ 59 + // 不是预热邮箱
  60 + if($f=='收件箱'){
59 61
60 - $this->auto_mail($id,$data); 62 + $this->auto_mail($id,$data);
61 63
62 - $this->black_mail($id,$data); 64 + $this->black_mail($id,$data);
63 65
64 - // 邮件过滤 这些邮箱都是系统邮箱 66 + // 邮件过滤 这些邮箱都是系统邮箱
65 // if(!$this->checkEmail($data['from']) && !$this->checkSubject($data['subject'])){ 67 // if(!$this->checkEmail($data['from']) && !$this->checkSubject($data['subject'])){
66 - //只提醒re  
67 - if(stripos(trim($data['subject']),'re:')===0){  
68 - // 通知黑格 2024-08-22 新上 这个是异步的不会阻塞当前进程  
69 - try {  
70 - SaberGM::post('https://fob.ai.cc/api/email_new_push',[  
71 - 'sign' => md5(date('ymd').'fob.ai.cc.email'),  
72 - 'id' => $id,  
73 - 'subject' => $data['subject'],  
74 - 'udate' => $data['udate'],  
75 - 'from' => $data['from'],  
76 - 'tos' => array_column(json_decode($data['to_name'],1),'email')  
77 - ]);  
78 - }catch (\Throwable $e){  
79 - // 就算异常了也不在推送了 68 + //只提醒re
  69 + if(stripos(trim($data['subject']),'re:')===0){
  70 + // 通知黑格 2024-08-22 新上 这个是异步的不会阻塞当前进程
  71 + try {
  72 + SaberGM::post('https://fob.ai.cc/api/email_new_push',[
  73 + 'sign' => md5(date('ymd').'fob.ai.cc.email'),
  74 + 'id' => $id,
  75 + 'subject' => $data['subject'],
  76 + 'udate' => $data['udate'],
  77 + 'from' => $data['from'],
  78 + 'tos' => array_column(json_decode($data['to_name'],1),'email')
  79 + ]);
  80 + }catch (\Throwable $e){
  81 + // 就算异常了也不在推送了
  82 + }
  83 +
80 } 84 }
81 85
82 - }  
83 86
  87 + }
84 88
85 } 89 }
86 90
  91 +
  92 +
87 } 93 }
88 94
89 private function hot($id){ 95 private function hot($id){
@@ -254,6 +254,9 @@ trait DbQuery { @@ -254,6 +254,9 @@ trait DbQuery {
254 * @time 2023/2/17 11:03 254 * @time 2023/2/17 11:03
255 */ 255 */
256 public function value(string|array $sql){ 256 public function value(string|array $sql){
  257 +
  258 + return $this->getCacheData($sql,null,\PDO::FETCH_COLUMN);
  259 +
257 $query = $this->query($sql); 260 $query = $this->query($sql);
258 if($query){ 261 if($query){
259 return $query->fetch(\PDO::FETCH_COLUMN); 262 return $query->fetch(\PDO::FETCH_COLUMN);
@@ -37,6 +37,8 @@ class Imap { @@ -37,6 +37,8 @@ class Imap {
37 $this->config = $config; 37 $this->config = $config;
38 38
39 $this->client = new ImapClient($this->config->getHost()); 39 $this->client = new ImapClient($this->config->getHost());
  40 +
  41 + $this->debug($config->isDebug());
40 } 42 }
41 43
42 /** 44 /**
@@ -6,6 +6,7 @@ class ImapConfig { @@ -6,6 +6,7 @@ class ImapConfig {
6 protected string $host = ''; 6 protected string $host = '';
7 protected string $password = ''; 7 protected string $password = '';
8 protected string $email = ''; 8 protected string $email = '';
  9 + protected bool $debug = false;
9 10
10 11
11 /** 12 /**
@@ -68,4 +69,21 @@ class ImapConfig { @@ -68,4 +69,21 @@ class ImapConfig {
68 return $this->password; 69 return $this->password;
69 } 70 }
70 71
  72 +
  73 + /**
  74 + * @param bool $debug
  75 + */
  76 + public function debug(bool $debug = true): static
  77 + {
  78 + $this->debug = $debug;
  79 + return $this;
  80 + }
  81 +
  82 + /**
  83 + * @return bool
  84 + */
  85 + public function isDebug(): bool
  86 + {
  87 + return $this->debug;
  88 + }
71 } 89 }
@@ -347,6 +347,14 @@ class Body { @@ -347,6 +347,14 @@ class Body {
347 } 347 }
348 348
349 349
  350 + /**
  351 + * @return DataArray[]
  352 + */
  353 + public function getItems(): array
  354 + {
  355 + return $this->items;
  356 + }
  357 +
350 } 358 }
351 359
352 360
@@ -44,6 +44,7 @@ class Folder{ @@ -44,6 +44,7 @@ class Folder{
44 $this->folder = $folder; 44 $this->folder = $folder;
45 $this->flags = $flags; 45 $this->flags = $flags;
46 $this->isSelect = $isSelect; 46 $this->isSelect = $isSelect;
  47 + $this->parent = $parent;
47 } 48 }
48 49
49 /** 50 /**
@@ -58,7 +58,23 @@ class Msg extends Request{ @@ -58,7 +58,23 @@ class Msg extends Request{
58 * @time 2024/9/14 14:06 58 * @time 2024/9/14 14:06
59 */ 59 */
60 public function forPage(int $p=1, int $limit=20):static { 60 public function forPage(int $p=1, int $limit=20):static {
61 - $this->msgno(range(($p-1)*$limit+1,$p*$limit)); 61 + $start = ($p-1)*$limit+1;
  62 + $end = $p*$limit;
  63 +
  64 + // 如果开始大于 总数 就不要查询了
  65 + if($start <= $this->folder->getTotal()){
  66 + // 如果结束大于 总数 以总数为结束
  67 + if($end > $this->folder->getTotal()){
  68 + // 防止 有些邮件服务商 查询没有的邮件会异常
  69 + $end = $this->folder->getTotal();
  70 + }
  71 + }else{
  72 + // 如果 开始 大于总数 就始终 查询总数后的limit
  73 + $start = $this->folder->getTotal()+1;
  74 + $end = $start+$limit;
  75 + }
  76 +
  77 + $this->msgno(range($start,$end));
62 return $this; 78 return $this;
63 } 79 }
64 80
@@ -2,11 +2,15 @@ @@ -2,11 +2,15 @@
2 2
3 namespace Service; 3 namespace Service;
4 4
  5 +use Event\Event;
5 use Lib\Imap\ImapConfig; 6 use Lib\Imap\ImapConfig;
6 use Lib\Imap\ImapPool; 7 use Lib\Imap\ImapPool;
7 use Lib\Imap\Parse\Folder\Folder; 8 use Lib\Imap\Parse\Folder\Folder;
  9 +use Lib\Imap\Parse\MessageItem;
  10 +use Model\bodySql;
8 use Model\emailSql; 11 use Model\emailSql;
9 use Model\folderSql; 12 use Model\folderSql;
  13 +use Model\listsSql;
10 14
11 /** 15 /**
12 * 同步邮件 16 * 同步邮件
@@ -32,6 +36,9 @@ class SyncMail { @@ -32,6 +36,9 @@ class SyncMail {
32 */ 36 */
33 protected $email; 37 protected $email;
34 38
  39 +
  40 + protected $isStop = false;
  41 +
35 /** 42 /**
36 * SyncMail constructor. 43 * SyncMail constructor.
37 * @param int|string|array $email 44 * @param int|string|array $email
@@ -54,12 +61,18 @@ class SyncMail { @@ -54,12 +61,18 @@ class SyncMail {
54 ->setHost($email['imap']) 61 ->setHost($email['imap'])
55 ->setEmail($email['email']) 62 ->setEmail($email['email'])
56 ->setPassword(base64_decode($email['password'])) 63 ->setPassword(base64_decode($email['password']))
  64 + ->debug()
57 ); 65 );
58 66
59 $this->login(); 67 $this->login();
60 68
61 } 69 }
62 70
  71 + public function stop(){
  72 + $this->isStop = true;
  73 + }
  74 +
  75 +
63 protected function emailId(){ 76 protected function emailId(){
64 return $this->email['id']; 77 return $this->email['id'];
65 } 78 }
@@ -78,71 +91,225 @@ class SyncMail { @@ -78,71 +91,225 @@ class SyncMail {
78 } 91 }
79 92
80 /** 93 /**
81 - * 同步文件夹 94 + * @param $folder
  95 + * @param int $pid
  96 + * @return array
  97 + * @throws \Exception
82 * @author:dc 98 * @author:dc
83 - * @time 2024/9/26 10:22 99 + * @time 2024/9/26 10:46
84 */ 100 */
85 - protected function folders(){ 101 + protected function folder($folder,$pid = 0){
86 $uuids = []; 102 $uuids = [];
  103 +
  104 + foreach ($folder as $item){
  105 + /** @var Folder $item*/
  106 +
  107 + $uuid = md5($this->emailId().$item->folder);
  108 + $uuids[$uuid] = $uuid;
  109 +
  110 + $folder_name = '';
  111 + if($item->flags){
  112 + // 有些邮箱是把公共的文件夹标记在flag里面的,识别出来
  113 + foreach ($item->flags as $flag){
  114 + if(in_array($flag,['Send','Drafts','Junk','Trash'])){
  115 + $folder_name = folderAlias($flag);
  116 + }
  117 + }
  118 + }
  119 + if(!$folder_name){
  120 + $fn = explode('/',$item->getParseFolder());
  121 + $folder_name = folderAlias(end($fn));
  122 + }
  123 +
  124 + // 是否存在
  125 + $id = $this->db->value(folderSql::has(['email_id'=>$this->emailId(),'uuid'=>$uuid]));
  126 + $data = [
  127 + 'email_id' => $this->emailId(),
  128 + 'folder' => $folder_name,
  129 + 'origin_folder' => $item->folder,
  130 + 'uuid' => $uuid,
  131 + 'pid' => $pid
  132 + ];
  133 + if ($id){
  134 + $this->db->update(folderSql::$table,$data,dbWhere(['id'=>$id]),false);
  135 + }else{
  136 + $id = $this->db->insert(folderSql::$table,$data,false);
  137 + if(!$id) abort('文件夹写入异常 '.json_encode($data,JSON_UNESCAPED_UNICODE));
  138 + }
  139 + // 是否有子级目录
  140 + if($id && $item->getChild()){
  141 + $uuids = array_merge($uuids,$this->folder($item->getChild(),$id));
  142 + }
  143 + }
  144 +
  145 + return $uuids;
  146 + }
  147 +
  148 + /**
  149 + * 同步
  150 + * @throws \Exception
  151 + * @author:dc
  152 + * @time 2024/9/26 10:46
  153 + */
  154 + public function sync(){
  155 + $this->isStop = false;
  156 + /*********************************** 同步文件夹 ***************************************/
87 // 获取文件夹 157 // 获取文件夹
88 $folders = $this->imap->getFolders(); 158 $folders = $this->imap->getFolders();
  159 + $uuids = $this->folder($folders->getTopFolder());
  160 + if($uuids){
  161 + // 删除以前的
  162 + $this->db->delete(folderSql::$table,['uuid.notin'=>$uuids,'email_id'=>$this->emailId()]);
  163 + }
89 164
90 - $tmp = function ($folder) use ($folders,&$uuids){  
91 - foreach ($folder as $item){  
92 - /** @var Folder $item*/ 165 + if($this->isStop) return;
93 166
94 - $uuid = md5($this->emailId().$item->folder);  
95 - $uuids[$uuid] = $uuid;  
96 167
97 - $folder_name = '';  
98 - // 已发送  
99 - if(in_array('Send',$item->flags)){  
100 - $folder_name = folderAlias('Send');  
101 - }  
102 - // 草稿  
103 - elseif(in_array('Drafts',$item->flags)){  
104 - $folder_name = folderAlias('Drafts');  
105 - }  
106 - // 垃圾  
107 - elseif(in_array('Junk',$item->flags)){  
108 - $folder_name = folderAlias('Junk');  
109 - }  
110 - // 回收站  
111 - elseif(in_array('Trash',$item->flags)){  
112 - $folder_name = folderAlias('Trash'); 168 + /********************* 同步邮件 **********************/
  169 +
  170 + // 循环文件夹
  171 + foreach ($folders->all() as $f){
  172 + if($this->isStop) return;
  173 +
  174 + if($f->isSelect){ // 是否可以选择 只有可以选中的文件夹才有邮件
  175 + $folder = $this->imap->folder($f); // 选择文件夹后,有状态
  176 +
  177 + // 这个暂时不要
  178 +// $this->db->update(folderSql::$table,[
  179 +// 'exsts' => $folder->getTotal(),
  180 +// 'unseen' => $folder->getUnseen(),
  181 +// 'last_sync_time' => time()
  182 +// ],dbWhere(['email_id'=>$this->emailId(),'origin_folder'=>$folder->getName()]),false);
  183 +
  184 + // 是否有邮件 有邮件才继续
  185 + if ($folder->getTotal()){
  186 + $this->mail($folder);
113 } 187 }
114 - if(!$folder_name){  
115 - $fn = explode('/',$item->getParseFolder());  
116 - $folder_name = folderAlias(end($fn)); 188 + }
  189 + }
  190 +
  191 +
  192 + }
  193 +
  194 + /**同步邮件
  195 + *
  196 + * @param string|\Lib\Imap\Request\Folder $folder
  197 + * @param array $uids 固定的uid
  198 + * @param false $isBody 是否同时同步body
  199 + * @author:dc
  200 + * @time 2024/9/26 11:10
  201 + */
  202 + public function mail(string|\Lib\Imap\Request\Folder $folder, array $uids = [],$isBody = false){
  203 + if(is_string($folder)){
  204 + $folder = $this->imap->folder($folder);
  205 + }
  206 +
  207 + $folder_id = $this->db->value(folderSql::first([
  208 + 'email_id'=>$this->emailId(),
  209 + 'uuid' => md5($this->emailId().$folder->getName())
  210 + ],'`id`'));
  211 +
  212 + // 选择成功
  213 + if($folder->isOk()){
  214 + $msg = $folder->msg();
  215 + if($uids){
  216 + $this->saveMail($folder_id,$msg->uid($uids)->get()->all(),$isBody);
  217 + }else{
  218 + $p=1;
  219 + while (1){
  220 + if($this->isStop) return;
  221 +
  222 + $lists = $msg->forPage($p)->get()->all();
  223 + // 没有数据就跳出
  224 + if(!$lists){
  225 + break;
  226 + }else{
  227 + $p++;
  228 + $this->saveMail($folder_id,$lists,$isBody);
  229 + }
117 } 230 }
  231 + }
  232 +
  233 + }
118 234
119 - // 是否存在  
120 - $id = $this->db->value(folderSql::has(['email_id'=>$this->emailId(),'uuid'=>$uuid]));  
121 - $data = [  
122 - 'email_id' => $this->emailId(),  
123 - 'folder' => folderAlias($folder_name),  
124 - 'origin_folder' => $folder['folder'],  
125 - 'uuid' => $uuid,  
126 - 'pid' => $pid 235 + }
  236 +
  237 + /**
  238 + * 保存邮件列表
  239 + * @param int $folder_id
  240 + * @param MessageItem[] $lists
  241 + * @param bool $isBody
  242 + * @author:dc
  243 + * @time 2024/9/29 15:14
  244 + */
  245 + protected function saveMail(int $folder_id, array $lists, bool $isBody=false){
  246 + foreach ($lists as $item){
  247 +
  248 + $data = [
  249 + 'uid' => $item->uid,
  250 + 'subject' => mb_substr($item->header->getSubject(),0,1000),// 控制下,有的蛋疼,整tm多长
  251 + 'cc' => $item->header->getCc(true),
  252 + 'bcc' => $item->header->getBcc(true),
  253 + 'from' => $item->header->getFrom()->email,
  254 + 'from_name' => $item->header->getFrom()->name,
  255 + 'to' => implode(',',array_column($item->header->getTo(true),'email')),
  256 + 'to_name' => $item->header->getTo(true),
  257 + // 这个是 邮件的时间 就是header里面带的 一般情况就是发件时间
  258 +// 'date' => strtotime($item->header->getDate()),
  259 + 'udate' => strtotime($item->date), // 有这个时间就够了,内部时间,就是收到邮件的时间
  260 + 'size' => $item->size,
  261 + 'recent' => $item->isRecent() ? 1 : 0,
  262 + 'seen' => $item->isSeen() ? 1 : 0,
  263 + 'draft' => $item->isDraft() ? 1 : 0,
  264 + 'flagged' => $item->isFlagged() ? 1 : 0,
  265 + 'answered' => $item->isAnswered() ? 1 : 0,
  266 + 'folder_id' => $folder_id,
  267 + 'email_id' => $this->emailId(),
  268 + 'is_file' => $item->isAttachment() ? 1: 0 //是否附件
  269 + ];
  270 +
  271 + // 查询是否存在
  272 + $id = $this->db->value(listsSql::first(dbWhere([
  273 + 'email_id'=> $data['email_id'],
  274 + 'folder_id' => $data['folder_id'],
  275 + 'uid' => $data['uid']
  276 + ]),'`id`'));
  277 +
  278 +
  279 + if(!$id){
  280 + $id = $this->db->insert(listsSql::$table,$data);
  281 + // 新邮件标记
  282 + if($item->getFolderName() == 'INBOX')
  283 + redis()->incr('have_new_mail_'.$this->emailId(),120);
  284 + // 执行事件
  285 + $data['Aicc-Hot-Mail'] = $item->header->get('Aicc-Hot-Mail');
  286 + Event::call('mail_sync_list',$id, $data);
  287 +
  288 + }else{
  289 + $this->db->update(listsSql::$table,$data,dbWhere(['id'=> $id]));
  290 + }
  291 +
  292 +
  293 + // 是否同步body内容
  294 + if($isBody){
  295 +
  296 + $body = [
  297 + 'lists_id' => $id,
  298 + 'text_html' => $item->getBody()->getItems()
127 ]; 299 ];
128 - if ($id){  
129 300
  301 + if($this->db->count(bodySql::has($id))){
  302 + $this->db->update(bodySql::$table,$body,'`lists_id` = '.$id,false);
  303 + }else{
  304 + $this->db->insert(bodySql::$table,$body,false);
130 } 305 }
  306 +
131 } 307 }
132 - };  
133 308
134 - $tmp($folders->getTopFolder());  
135 309
136 - if($uuids){  
137 - // 删除以前的  
138 - $this->db->delete(folderSql::$table,['uuid.notin'=>$uuids,'email_id'=>$this->emailId()]);  
139 } 310 }
140 311
141 - return $folders;  
142 - }  
143 312
144 - public function sync(){  
145 - $folders = $this->folders();  
146 } 313 }
147 314
148 315