码桶

发现社区成员的开源项目

飞扬 / LB_Down 公开
main
LB_Down/email.php
email.php12.4 KB
<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;

require_once __DIR__ . '/phpmailer.php';
require_once __DIR__ . '/smtp.php';

class V3_Email
{

    public static function send($comment)
    {
        try {
            $options = Helper::options();
            if ($options->v3_email_switch !== 'on') {
                return;
            }
            if (!$options->v3_email_host || !$options->v3_email_port || !$options->v3_email_sendmail || !$options->v3_email_password) {
                return;
            }

            $mail = new PHPMailer();
        $mail->isSMTP();
        $mail->SMTPAuth = true;
        $mail->CharSet = 'UTF-8';
        if ($options->v3_email_ssl && $options->v3_email_ssl !== 'none') {
            $mail->SMTPSecure = $options->v3_email_ssl;
        }
        $mail->Host = $options->v3_email_host;
        $mail->Port = intval($options->v3_email_port);
        $mail->FromName = $options->v3_email_nickname ? $options->v3_email_nickname : $options->title;
        $mail->Username = $options->v3_email_sendmail;
        $mail->From = $options->v3_email_sendmail;
        $mail->Password = $options->v3_email_password;
        $mail->isHTML(true);

        $text = self::sanitize($comment->text);
        $img = $options->v3_email_pic ? $options->v3_email_pic : 'https://jihulab.com/ScarletDor/owo/-/raw/master/public/mail1.webp';
        $copyright = '此邮件由系统自动发送,请勿直接回复<br>©' . date("Y") . ' - '
            . '<a style="color:#999;text-decoration:none;" href="' . $options->siteUrl . '" target="_blank">' . $options->title . '</a>';
        $permalinkNoHash = $comment->permalink;
        $hashPos = strrpos($comment->permalink, '#');
        if ($hashPos !== false) {
            $permalinkNoHash = substr($comment->permalink, 0, $hashPos);
        }
        $linkHtml = '<a style="color:#ff5850;text-decoration:none;" href="' . $permalinkNoHash . '" target="_blank">《 ' . $comment->title . ' 》</a>';
        $time = date('Y年n月j日 H:i', $comment->created);

        $html_1 = self::template_blogger($img, $linkHtml, $copyright);

        $html_2 = self::template_visitor($img, $linkHtml, $copyright);

        $db = Typecho_Db::get();
        if ($comment->authorId != $comment->ownerId) {

            $authorMail = trim($options->v3_admin_email ?? '');
            if (!$authorMail) {
                $authorInfo = $db->fetchRow($db->select()->from('table.users')->where('uid = ?', 1));
                $authorMail = isset($authorInfo['mail']) ? $authorInfo['mail'] : '';
            }
            if ($authorMail) {
                $mail->Body = strtr($html_1, array(
                    '{avatar2}'   => lb_avatar_url($comment->mail, 200),
                    '{author}'    => htmlspecialchars($comment->author),
                    '{ip}'        => $comment->ip,
                    '{mail}'      => $comment->mail,
                    '{time}'      => $time,
                    '{content}'   => $text,
                ));
                $mail->addAddress($authorMail);
                $mail->Subject = '💬 ' . htmlspecialchars($comment->author) . ' 在 『' . $comment->title . '』 中发表了新评论';
                $sendResult = $mail->send();
                if (!$sendResult) {
                    error_log('[V3] 邮件发送失败(通知博主): ' . $mail->ErrorInfo);
                }
                $mail->clearAddresses();
            }
        }

        
        if ($comment->parent > 0) {
            $parentInfo = $db->fetchRow($db->select('mail', 'text', 'created')->from('table.comments')->where('coid = ?', $comment->parent));
            if ($parentInfo) {
                $parentMail = $parentInfo['mail'];
                $parentText = self::sanitize($parentInfo['text']);
                $parentCreated = $parentInfo['created'];
                if ($parentMail && $parentMail != $comment->mail) {
                    $mail->Body = strtr($html_2, array(
                        '{avatar1}'    => lb_avatar_url($parentMail, 200),
                        '{avatar2}'    => lb_avatar_url($comment->mail, 200),
                        '{author}'     => htmlspecialchars($comment->author),
                        '{ip}'         => $comment->ip,
                        '{mail}'       => $comment->mail,
                        '{time}'       => $time,
                        '{oldtime}'    => date('Y年n月j日 H:i', $parentCreated),
                        '{content}'    => $text,
                        '{oldcontent}' => $parentText,
                    ));
                    $mail->addAddress($parentMail);
                    $mail->Subject = '💬 ' . htmlspecialchars($comment->author) . ' 回复了您在 『' . $comment->title . '』 的评论';
                    $sendResult2 = $mail->send();
                    if (!$sendResult2) {
                        error_log('[V3] 邮件发送失败(通知访客): ' . $mail->ErrorInfo);
                    }
                    $mail->clearAddresses();
                }
            }
        }

        
        $mentionMails = self::getMentionMails($comment);
        foreach ($mentionMails as $mentionMail) {
            $mail->clearAddresses();
            $mail->Body = strtr($html_2, array(
                '{avatar1}'    => lb_avatar_url($mentionMail, 200),
                '{avatar2}'    => lb_avatar_url($comment->mail, 200),
                '{author}'     => htmlspecialchars($comment->author),
                '{ip}'         => $comment->ip,
                '{mail}'       => $comment->mail,
                '{time}'       => $time,
                '{oldtime}'    => date('Y年n月j日 H:i', $comment->created),
                '{content}'    => $text,
                '{oldcontent}' => '在评论中提到了您',
            ));
            $mail->addAddress($mentionMail);
            $mail->Subject = '📢 ' . htmlspecialchars($comment->author) . ' 在 『' . $comment->title . '』 的评论中 @了您';
            $sendResult3 = $mail->send();
            if (!$sendResult3) {
                error_log('[V3] 邮件发送失败(@提及): ' . $mail->ErrorInfo);
            }
        }
        } catch (\Throwable $e) {
            if (defined('__TYPECHO_DEBUG__') && __TYPECHO_DEBUG__) {
                error_log('[V3] 邮件发送异常: ' . $e->getMessage());
            }
        }
    }

    

    private static function getMentionMails($comment)
    {
        $text = $comment->text;
        if (!preg_match_all('/@([\x{4e00}-\x{9fa5}a-zA-Z0-9_]+)/u', $text, $matches)) {
            return array();
        }

        
        $brands = Helper::options()->v3_brands ?: '';
        $brandNames = array();
        foreach (explode("\n", $brands) as $line) {
            $parts = explode('|', trim($line), 4);
            if (!empty($parts[0])) {
                $brandNames[] = trim($parts[0]);
            }
        }

        $mentioned = array();
        $db = Typecho_Db::get();
        $currentMail = $comment->mail;
        $parentMail = null;
        if ($comment->parent > 0) {
            $pi = $db->fetchRow($db->select('mail')->from('table.comments')->where('coid = ?', $comment->parent));
            $parentMail = $pi ? $pi['mail'] : null;
        }

        foreach ($matches[1] as $name) {

            if (in_array($name, $brandNames)) continue;

            $row = $db->fetchRow($db->select('mail')->from('table.comments')
                ->where('cid = ?', $comment->cid)
                ->where('author = ?', $name)
                ->where('mail != ?', '')
                ->order('created', Typecho_Db::SORT_DESC)
                ->limit(1));
            if ($row && !empty($row['mail'])
                && $row['mail'] !== $currentMail
                && $row['mail'] !== $parentMail) {
                $mentioned[$row['mail']] = true;
            }
        }
        return array_keys($mentioned);
    }

    private static function sanitize($text)
    {
        if (self::hasXss($text)) {
            return '该回复疑似异常,已被系统拦截!';
        }
        return strip_tags($text, '<img>');
    }

    private static function hasXss($text)
    {
        $patterns = array('/on\w+\s*=/is', '/javascript:/is', '/eval/is', '/vbscript:/is');
        foreach ($patterns as $p) {
            if (preg_match($p, $text)) {
                return true;
            }
        }
        return false;
    }

    private static function template_blogger($img, $linkHtml, $copyright)
    {
        return '
        <div style="width:550px;margin:0 auto;border-radius:8px;overflow:hidden;word-break:break-all;background:linear-gradient(117deg,#e2ffda,#f6ffff);">
            <img src="' . $img . '" alt="您的邮件" style="width:100%;">
            <div style="padding:1rem;position:relative;">
                <p><b style="color:#ff5850;" title="IP:{ip} Mail:{mail}">{author} </b>在 ' . $linkHtml . ' 上给您留言啦:</p>
                <div style="display:flex;margin-bottom:5px;">
                    <div style="width:auto;flex:none;">
                        <img src="{avatar2}" style="width:40px;height:40px;border-radius:5px;">
                    </div>
                    <div style="position:relative;margin-left:10px;">
                        <span style="width:0;height:0;border-top:8px solid transparent;border-bottom:8px solid transparent;border-right:8px solid;border-right-color:#f4f4f4;left:-7px;right:auto;top:12px;position:absolute;"></span>
                        <div style="background:linear-gradient(117deg,#dff6ff,#daffdf);padding:10px;border-radius:9px;margin-bottom:3px;">{content}</div>
                    </div>
                </div>
                <p style="color:#999;font-size:12px;">{time}</p>
                <div style="text-align:center;color:#999;font-size:12px;line-height:1.5rem;">' . $copyright . '</div>
                <div style="font-size:4rem;position:absolute;right:0;bottom:0;transform:rotate(-15deg);opacity:0.5;">💌</div>
            </div>
        </div>';
    }

    private static function template_visitor($img, $linkHtml, $copyright)
    {
        return '
        <div style="width:550px;margin:0 auto;border-radius:8px;overflow:hidden;word-break:break-all;background:linear-gradient(117deg,#e2ffda,#f6ffff);">
            <img src="' . $img . '" alt="您的邮件" style="width:100%;">
            <div style="padding:1rem;position:relative;">
                <p style="display:flex;justify-content:flex-end;">您在 ' . $linkHtml . ' 上的评论:</p>
                <div style="display:flex;justify-content:flex-end;margin-bottom:5px;">
                    <div style="width:auto;flex:none;order:2;">
                        <img src="{avatar1}" style="width:40px;height:40px;border-radius:5px;">
                    </div>
                    <div style="position:relative;margin-right:10px;">
                        <span style="width:0;height:0;border-top:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid;border-left-color:#f4f4f4;right:-7px;left:auto;top:12px;position:absolute;"></span>
                        <div style="background:linear-gradient(117deg,#dff6ff,#daffdf);padding:10px;border-radius:9px;margin-bottom:3px;">{oldcontent}</div>
                    </div>
                </div>
                <p style="color:#999;font-size:12px;display:flex;justify-content:flex-end;">{oldtime}</p>
                <p><b style="color:#ff5850;" title="IP:{ip} Mail:{mail}">{author} </b>给您回复到:</p>
                <div style="display:flex;margin-bottom:5px;">
                    <div style="width:auto;flex:none;">
                        <img src="{avatar2}" style="width:40px;height:40px;border-radius:5px;">
                    </div>
                    <div style="position:relative;margin-left:10px;">
                        <span style="width:0;height:0;border-top:8px solid transparent;border-bottom:8px solid transparent;border-right:8px solid;border-right-color:#f4f4f4;left:-7px;right:auto;top:12px;position:absolute;"></span>
                        <div style="background:linear-gradient(117deg,#f3efff,#e7f1ff);padding:10px;border-radius:9px;margin-bottom:3px;">{content}</div>
                    </div>
                </div>
                <p style="color:#999;font-size:12px;">{time}</p>
                <div style="text-align:center;color:#999;font-size:12px;line-height:1.5rem;">' . $copyright . '</div>
                <div style="font-size:4rem;position:absolute;right:0;bottom:0;transform:rotate(-15deg);opacity:0.5;">💌</div>
            </div>
        </div>';
    }
}