作者 邓超

x

<?php
namespace Event;
/**
* 事件
* @author:dc
* @time 2024/9/30 16:33
* Class Event
* @package Event
*/
class Event {
/**
* 同步
* @var array
*/
protected static $alias = [
// 列表同步成功后
'mail_sync_list' => [
SyncMail::class,
MailBlack::class
],
];
/**
* @param $name
* @param mixed ...$params
* @throws \Exception
* @author:dc
* @time 2024/9/30 16:55
*/
public static function call($name, ...$params){
// 是否已配置
if(isset(self::$alias[$name])){
array_map(function ($v) use ($params){
new $v(...$params);
},
is_array(self::$alias[$name])? self::$alias[$name] : [is_array(self::$alias[$name])]
);
}else if (class_exists($name)){
new $name(...$params);
}else{
throw new \Exception('event '.$name.' not found');
}
}
}
... ...
<?php
namespace Event;
use Model\emailSql;
use Model\folderSql;
use Model\listsSql;
use Swlib\Saber;
use Swlib\SaberGM;
/**
* 黑名单
* @author:dc
* @time 2024/9/30 17:16
* Class MailBlack
* @package Event
*/
class MailBlack {
/**
* @var \Lib\Db|\Lib\DbPool
*/
private $db;
private $blacklist = [];
private $blackFolder = '';
private $data;
public function __construct($id,$data)
{
$data['id'] = $id;
$this->data = $data;
// 读取黑名单
$this->blacklist = redis()->get('blacklist:'.$data['email_id']);
if($this->blacklist){
$this->blackFolder = $this->db->cache(3600)->value(folderSql::originFolder($data['email_id'],'垃圾箱'));
}
}
}
\ No newline at end of file
... ...
... ... @@ -2,6 +2,7 @@
namespace Event;
use Lib\Imap\Parse\MessageItem;
use Model\emailSql;
use Model\folderSql;
use Model\listsSql;
... ... @@ -15,75 +16,80 @@ use Swlib\SaberGM;
* Class syncMail
* @package Event
*/
class syncMail {
class SyncMail {
/**
* @var \Lib\Db|\Lib\DbPool
*/
private $db;
public function __construct($id,$header,$data)
public function __construct(int $id, array $data)
{
$this->db = db();
if(php_sapi_name() == 'cli'){
$this->db = db();
// 是否是预热邮件 aicc专用
if(!empty($header['Aicc-Hot-Mail']) || !empty($header['aicc-hot-mail'])){
return $this->hot($id);
}
// 是否在指定文件夹内
$f = $this->db->value(folderSql::first($data['folder_id'],'folder'));
if(!$f){
return true;
}
$f = folderAlias($f);
if($f=='发件箱'){
if(empty($data['to_name'])){
$data['to_name'] = [];
// 是否在指定文件夹内
$f = $this->db->value(folderSql::first($data['folder_id'],'folder'));
if(!$f){
return true;
}
$f = folderAlias($f);
if($f=='发件箱'){
if(empty($data['to_name'])){
$data['to_name'] = [];
}
$data['to_name'] = is_array($data['to_name'])?$data['to_name']:json_decode($data['to_name']);
$data['to_name'] = is_array($data['to_name'])?$data['to_name']:json_decode($data['to_name']);
$w = ['email' => array_map('strtolower',array_column($data['to_name'],'email'))];
}else{
$w = ['email' => strtolower($data['from'])];
}
// 是否在 预热邮箱中
if($this->db->count('select count(*) from `hot_mail` where '.dbWhere($w))){
return $this->hot($id);
}
$w = ['email' => array_map('strtolower',array_column($data['to_name'],'email'))];
}else{
$w = ['email' => strtolower($data['from'])];
}
// 是否在 预热邮箱中
if($this->db->count('select count(*) from `hot_mail` where '.dbWhere($w))){
return $this->hot($id);
}
// 不是预热邮箱
if($f=='收件箱'){
// 不是预热邮箱
if($f=='收件箱'){
$this->auto_mail($id,$data);
$this->auto_mail($id,$data);
$this->black_mail($id,$data);
$this->black_mail($id,$data);
// 邮件过滤 这些邮箱都是系统邮箱
// 邮件过滤 这些邮箱都是系统邮箱
// if(!$this->checkEmail($data['from']) && !$this->checkSubject($data['subject'])){
//只提醒re
if(stripos(trim($data['subject']),'re:')===0){
// 通知黑格 2024-08-22 新上 这个是异步的不会阻塞当前进程
try {
SaberGM::post('https://fob.ai.cc/api/email_new_push',[
'sign' => md5(date('ymd').'fob.ai.cc.email'),
'id' => $id,
'subject' => $data['subject'],
'udate' => $data['udate'],
'from' => $data['from'],
'tos' => array_column(json_decode($data['to_name'],1),'email')
]);
}catch (\Throwable $e){
// 就算异常了也不在推送了
//只提醒re
if(stripos(trim($data['subject']),'re:')===0){
// 通知黑格 2024-08-22 新上 这个是异步的不会阻塞当前进程
try {
SaberGM::post('https://fob.ai.cc/api/email_new_push',[
'sign' => md5(date('ymd').'fob.ai.cc.email'),
'id' => $id,
'subject' => $data['subject'],
'udate' => $data['udate'],
'from' => $data['from'],
'tos' => array_column(json_decode($data['to_name'],1),'email')
]);
}catch (\Throwable $e){
// 就算异常了也不在推送了
}
}
}
}
}
}
private function hot($id){
... ...
... ... @@ -254,6 +254,9 @@ trait DbQuery {
* @time 2023/2/17 11:03
*/
public function value(string|array $sql){
return $this->getCacheData($sql,null,\PDO::FETCH_COLUMN);
$query = $this->query($sql);
if($query){
return $query->fetch(\PDO::FETCH_COLUMN);
... ...
... ... @@ -37,6 +37,8 @@ class Imap {
$this->config = $config;
$this->client = new ImapClient($this->config->getHost());
$this->debug($config->isDebug());
}
/**
... ...
... ... @@ -6,6 +6,7 @@ class ImapConfig {
protected string $host = '';
protected string $password = '';
protected string $email = '';
protected bool $debug = false;
/**
... ... @@ -68,4 +69,21 @@ class ImapConfig {
return $this->password;
}
/**
* @param bool $debug
*/
public function debug(bool $debug = true): static
{
$this->debug = $debug;
return $this;
}
/**
* @return bool
*/
public function isDebug(): bool
{
return $this->debug;
}
}
\ No newline at end of file
... ...
... ... @@ -347,6 +347,14 @@ class Body {
}
/**
* @return DataArray[]
*/
public function getItems(): array
{
return $this->items;
}
}
... ...
... ... @@ -44,6 +44,7 @@ class Folder{
$this->folder = $folder;
$this->flags = $flags;
$this->isSelect = $isSelect;
$this->parent = $parent;
}
/**
... ...
... ... @@ -58,7 +58,23 @@ class Msg extends Request{
* @time 2024/9/14 14:06
*/
public function forPage(int $p=1, int $limit=20):static {
$this->msgno(range(($p-1)*$limit+1,$p*$limit));
$start = ($p-1)*$limit+1;
$end = $p*$limit;
// 如果开始大于 总数 就不要查询了
if($start <= $this->folder->getTotal()){
// 如果结束大于 总数 以总数为结束
if($end > $this->folder->getTotal()){
// 防止 有些邮件服务商 查询没有的邮件会异常
$end = $this->folder->getTotal();
}
}else{
// 如果 开始 大于总数 就始终 查询总数后的limit
$start = $this->folder->getTotal()+1;
$end = $start+$limit;
}
$this->msgno(range($start,$end));
return $this;
}
... ...
... ... @@ -2,11 +2,15 @@
namespace Service;
use Event\Event;
use Lib\Imap\ImapConfig;
use Lib\Imap\ImapPool;
use Lib\Imap\Parse\Folder\Folder;
use Lib\Imap\Parse\MessageItem;
use Model\bodySql;
use Model\emailSql;
use Model\folderSql;
use Model\listsSql;
/**
* 同步邮件
... ... @@ -32,6 +36,9 @@ class SyncMail {
*/
protected $email;
protected $isStop = false;
/**
* SyncMail constructor.
* @param int|string|array $email
... ... @@ -54,12 +61,18 @@ class SyncMail {
->setHost($email['imap'])
->setEmail($email['email'])
->setPassword(base64_decode($email['password']))
->debug()
);
$this->login();
}
public function stop(){
$this->isStop = true;
}
protected function emailId(){
return $this->email['id'];
}
... ... @@ -78,71 +91,225 @@ class SyncMail {
}
/**
* 同步文件夹
* @param $folder
* @param int $pid
* @return array
* @throws \Exception
* @author:dc
* @time 2024/9/26 10:22
* @time 2024/9/26 10:46
*/
protected function folders(){
protected function folder($folder,$pid = 0){
$uuids = [];
foreach ($folder as $item){
/** @var Folder $item*/
$uuid = md5($this->emailId().$item->folder);
$uuids[$uuid] = $uuid;
$folder_name = '';
if($item->flags){
// 有些邮箱是把公共的文件夹标记在flag里面的,识别出来
foreach ($item->flags as $flag){
if(in_array($flag,['Send','Drafts','Junk','Trash'])){
$folder_name = folderAlias($flag);
}
}
}
if(!$folder_name){
$fn = explode('/',$item->getParseFolder());
$folder_name = folderAlias(end($fn));
}
// 是否存在
$id = $this->db->value(folderSql::has(['email_id'=>$this->emailId(),'uuid'=>$uuid]));
$data = [
'email_id' => $this->emailId(),
'folder' => $folder_name,
'origin_folder' => $item->folder,
'uuid' => $uuid,
'pid' => $pid
];
if ($id){
$this->db->update(folderSql::$table,$data,dbWhere(['id'=>$id]),false);
}else{
$id = $this->db->insert(folderSql::$table,$data,false);
if(!$id) abort('文件夹写入异常 '.json_encode($data,JSON_UNESCAPED_UNICODE));
}
// 是否有子级目录
if($id && $item->getChild()){
$uuids = array_merge($uuids,$this->folder($item->getChild(),$id));
}
}
return $uuids;
}
/**
* 同步
* @throws \Exception
* @author:dc
* @time 2024/9/26 10:46
*/
public function sync(){
$this->isStop = false;
/*********************************** 同步文件夹 ***************************************/
// 获取文件夹
$folders = $this->imap->getFolders();
$uuids = $this->folder($folders->getTopFolder());
if($uuids){
// 删除以前的
$this->db->delete(folderSql::$table,['uuid.notin'=>$uuids,'email_id'=>$this->emailId()]);
}
$tmp = function ($folder) use ($folders,&$uuids){
foreach ($folder as $item){
/** @var Folder $item*/
if($this->isStop) return;
$uuid = md5($this->emailId().$item->folder);
$uuids[$uuid] = $uuid;
$folder_name = '';
// 已发送
if(in_array('Send',$item->flags)){
$folder_name = folderAlias('Send');
}
// 草稿
elseif(in_array('Drafts',$item->flags)){
$folder_name = folderAlias('Drafts');
}
// 垃圾
elseif(in_array('Junk',$item->flags)){
$folder_name = folderAlias('Junk');
}
// 回收站
elseif(in_array('Trash',$item->flags)){
$folder_name = folderAlias('Trash');
/********************* 同步邮件 **********************/
// 循环文件夹
foreach ($folders->all() as $f){
if($this->isStop) return;
if($f->isSelect){ // 是否可以选择 只有可以选中的文件夹才有邮件
$folder = $this->imap->folder($f); // 选择文件夹后,有状态
// 这个暂时不要
// $this->db->update(folderSql::$table,[
// 'exsts' => $folder->getTotal(),
// 'unseen' => $folder->getUnseen(),
// 'last_sync_time' => time()
// ],dbWhere(['email_id'=>$this->emailId(),'origin_folder'=>$folder->getName()]),false);
// 是否有邮件 有邮件才继续
if ($folder->getTotal()){
$this->mail($folder);
}
if(!$folder_name){
$fn = explode('/',$item->getParseFolder());
$folder_name = folderAlias(end($fn));
}
}
}
/**同步邮件
*
* @param string|\Lib\Imap\Request\Folder $folder
* @param array $uids 固定的uid
* @param false $isBody 是否同时同步body
* @author:dc
* @time 2024/9/26 11:10
*/
public function mail(string|\Lib\Imap\Request\Folder $folder, array $uids = [],$isBody = false){
if(is_string($folder)){
$folder = $this->imap->folder($folder);
}
$folder_id = $this->db->value(folderSql::first([
'email_id'=>$this->emailId(),
'uuid' => md5($this->emailId().$folder->getName())
],'`id`'));
// 选择成功
if($folder->isOk()){
$msg = $folder->msg();
if($uids){
$this->saveMail($folder_id,$msg->uid($uids)->get()->all(),$isBody);
}else{
$p=1;
while (1){
if($this->isStop) return;
$lists = $msg->forPage($p)->get()->all();
// 没有数据就跳出
if(!$lists){
break;
}else{
$p++;
$this->saveMail($folder_id,$lists,$isBody);
}
}
}
}
// 是否存在
$id = $this->db->value(folderSql::has(['email_id'=>$this->emailId(),'uuid'=>$uuid]));
$data = [
'email_id' => $this->emailId(),
'folder' => folderAlias($folder_name),
'origin_folder' => $folder['folder'],
'uuid' => $uuid,
'pid' => $pid
}
/**
* 保存邮件列表
* @param int $folder_id
* @param MessageItem[] $lists
* @param bool $isBody
* @author:dc
* @time 2024/9/29 15:14
*/
protected function saveMail(int $folder_id, array $lists, bool $isBody=false){
foreach ($lists as $item){
$data = [
'uid' => $item->uid,
'subject' => mb_substr($item->header->getSubject(),0,1000),// 控制下,有的蛋疼,整tm多长
'cc' => $item->header->getCc(true),
'bcc' => $item->header->getBcc(true),
'from' => $item->header->getFrom()->email,
'from_name' => $item->header->getFrom()->name,
'to' => implode(',',array_column($item->header->getTo(true),'email')),
'to_name' => $item->header->getTo(true),
// 这个是 邮件的时间 就是header里面带的 一般情况就是发件时间
// 'date' => strtotime($item->header->getDate()),
'udate' => strtotime($item->date), // 有这个时间就够了,内部时间,就是收到邮件的时间
'size' => $item->size,
'recent' => $item->isRecent() ? 1 : 0,
'seen' => $item->isSeen() ? 1 : 0,
'draft' => $item->isDraft() ? 1 : 0,
'flagged' => $item->isFlagged() ? 1 : 0,
'answered' => $item->isAnswered() ? 1 : 0,
'folder_id' => $folder_id,
'email_id' => $this->emailId(),
'is_file' => $item->isAttachment() ? 1: 0 //是否附件
];
// 查询是否存在
$id = $this->db->value(listsSql::first(dbWhere([
'email_id'=> $data['email_id'],
'folder_id' => $data['folder_id'],
'uid' => $data['uid']
]),'`id`'));
if(!$id){
$id = $this->db->insert(listsSql::$table,$data);
// 新邮件标记
if($item->getFolderName() == 'INBOX')
redis()->incr('have_new_mail_'.$this->emailId(),120);
// 执行事件
$data['Aicc-Hot-Mail'] = $item->header->get('Aicc-Hot-Mail');
Event::call('mail_sync_list',$id, $data);
}else{
$this->db->update(listsSql::$table,$data,dbWhere(['id'=> $id]));
}
// 是否同步body内容
if($isBody){
$body = [
'lists_id' => $id,
'text_html' => $item->getBody()->getItems()
];
if ($id){
if($this->db->count(bodySql::has($id))){
$this->db->update(bodySql::$table,$body,'`lists_id` = '.$id,false);
}else{
$this->db->insert(bodySql::$table,$body,false);
}
}
};
$tmp($folders->getTopFolder());
if($uuids){
// 删除以前的
$this->db->delete(folderSql::$table,['uuid.notin'=>$uuids,'email_id'=>$this->emailId()]);
}
return $folders;
}
public function sync(){
$folders = $this->folders();
}
... ...