瀏覽代碼

重构消息模块;重构核心续期代码;重构自定义依赖库

luolongfei 4 年之前
父節點
當前提交
04cb23c81c

+ 12 - 8
.env.example

@@ -32,11 +32,12 @@ MAIL_PASSWORD=''
 # 用于接收通知的邮箱 Email address used to receive notifications
 TO=''
 
-# 是否启用邮件推送功能 true:启用 false:不启用 Whether to enable email push features true: enabled false: not enabled
-MAIL_ENABLE=true
+# 是否启用邮件推送功能 1:启用 0:不启用 Whether to enable email push features 1: enabled 0: not enabled
+MAIL_ENABLE=1
 
 # 自定义邮箱配置 Custom email config
 # 如果你想使用除“QQ邮箱、163邮箱、Gmail、Outlook邮箱”外的第三方邮箱或者自建邮箱服务作为机器人邮箱,可以自定义邮箱配置,否则请不要配置这些项
+# If you want to use third-party mailboxes or self-built mailbox services other than "QQ mailbox, 163 mailbox, Gmail, Outlook mailbox" as robot mailbox, you can customize mailbox configuration, otherwise, please don't configure these items.
 
 # SMTP 服务器 SMTP server
 MAIL_HOST=''
@@ -60,15 +61,18 @@ TELEGRAM_CHAT_ID=''
 # 你的Telegram bot的token Token for your Telegram bot
 TELEGRAM_BOT_TOKEN=''
 
-# 是否启用 Telegram Bot 功能 true:启用 false:不启用 Whether to enable Telegram Bot features true: enabled false: not enabled
-TELEGRAM_BOT_ENABLE=false
+# 是否启用 Telegram Bot 功能 1:启用 0:不启用 Whether to enable Telegram Bot features 1: enabled 0: not enabled
+TELEGRAM_BOT_ENABLE=0
+
+# Telegram 代理 e.g. http://127.0.0.1:1081
+TELEGRAM_PROXY=''
 ######################  end Telegram bot  #########################
 
 # 通知频率 0:仅当有续期操作的时候 1:每次执行 Notification frequency 0: Only when there is a renewal operation 1: Each execution
 NOTICE_FREQ=1
 
-# 是否验证服务器证书 Whether to verify server certificate
-VERIFY_SSL=false
+# 是否验证服务器证书 1:验证 0:不验证 Whether to verify the server certificate 1: verify 0: no verification
+VERIFY_SSL=0
 
-# 是否开启 Debug 模式 Whether to enable debug mode
-DEBUG=false
+# 是否开启 Debug 模式 1:开启 0:关闭 Whether to turn on Debug mode 1: On 0: Off
+DEBUG=0

+ 197 - 135
app/Console/FreeNom.php

@@ -14,14 +14,15 @@ use Luolongfei\App\Exceptions\LlfException;
 use GuzzleHttp\Client;
 use GuzzleHttp\Cookie\CookieJar;
 use Luolongfei\Libs\Log;
-use Luolongfei\Libs\Mail;
-use Luolongfei\Libs\TelegramBot;
+use Luolongfei\Libs\Message;
+use Luolongfei\Libs\MessageServices\Mail;
+use Luolongfei\Libs\MessageServices\TelegramBot;
 
 class FreeNom
 {
-    const VERSION = 'v0.3';
+    const VERSION = 'v0.4';
 
-    const TIMEOUT = 34.52;
+    const TIMEOUT = 33;
 
     // FreeNom登录地址
     const LOGIN_URL = 'https://my.freenom.com/dologin.php';
@@ -57,12 +58,12 @@ class FreeNom
     protected $jar = true;
 
     /**
-     * @var string freenom账户
+     * @var string FreeNom 账户
      */
     protected $username;
 
     /**
-     * @var string freenom密码
+     * @var string FreeNom 密码
      */
     protected $password;
 
@@ -98,147 +99,213 @@ class FreeNom
 
     /**
      * 登录
+     *
+     * @param string $username
+     * @param string $password
+     *
+     * @return bool
+     * @throws LlfException
      */
-    protected function login()
+    protected function login(string $username, string $password)
     {
-        $this->client->post(self::LOGIN_URL, [
-            'headers' => [
-                'Content-Type' => 'application/x-www-form-urlencoded',
-                'Referer' => 'https://my.freenom.com/clientarea.php'
-            ],
-            'form_params' => [
-                'username' => $this->username,
-                'password' => $this->password
-            ],
-            'cookies' => $this->jar
-        ]);
+        try {
+            $this->client->post(self::LOGIN_URL, [
+                'headers' => [
+                    'Content-Type' => 'application/x-www-form-urlencoded',
+                    'Referer' => 'https://my.freenom.com/clientarea.php'
+                ],
+                'form_params' => [
+                    'username' => $username,
+                    'password' => $password
+                ],
+                'cookies' => $this->jar
+            ]);
+        } catch (\Exception $e) {
+            throw new LlfException(34520002, $e->getMessage());
+        }
+
+        if (empty($this->jar->getCookieByName('WHMCSZH5eHTGhfvzP')->getValue())) {
+            throw new LlfException(34520002, lang('error_msg.100001'));
+        }
+
+        return true;
     }
 
     /**
-     * 续期
+     * 匹配获取所有域名
      *
-     * @throws \Exception
+     * @param string $domainStatusPage
+     *
+     * @return array
      * @throws LlfException
      */
-    public function renewDomains()
+    protected function getAllDomains(string $domainStatusPage)
     {
-        // 所有请求共用一个CookieJar实例
-        $this->jar = new CookieJar();
-
-        $this->login();
-        $authCookie = $this->jar->getCookieByName('WHMCSZH5eHTGhfvzP')->getValue();
-        if (empty($authCookie)) {
-            throw new LlfException(34520002);
+        if (!preg_match_all(self::DOMAIN_INFO_REGEX, $domainStatusPage, $allDomains, PREG_SET_ORDER)) {
+            throw new LlfException(34520003);
         }
 
-        // 检查域名状态
-        $response = $this->client->get(self::DOMAIN_STATUS_URL, [
-            'headers' => [
-                'Referer' => 'https://my.freenom.com/clientarea.php'
-            ],
-            'cookies' => $this->jar
-        ]);
-        $body = (string)$response->getBody();
+        return $allDomains;
+    }
 
-        if (!preg_match(self::LOGIN_STATUS_REGEX, $body)) {
-            throw new LlfException(34520009);
+    /**
+     * 获取匹配 token
+     *
+     * 据观察,每次登录后此 token 不会改变,故可以只获取一次,多次使用
+     *
+     * @param string $domainStatusPage
+     *
+     * @return string
+     * @throws LlfException
+     */
+    protected function getToken(string $domainStatusPage)
+    {
+        if (!preg_match(self::TOKEN_REGEX, $domainStatusPage, $matches)) {
+            throw new LlfException(34520004);
         }
 
-        // 域名数据
-        if (!preg_match_all(self::DOMAIN_INFO_REGEX, $body, $domains, PREG_SET_ORDER)) {
-            throw new LlfException(34520003);
+        return $matches['token'];
+    }
+
+    /**
+     * 获取域名状态页面
+     *
+     * @return string
+     * @throws LlfException
+     */
+    protected function getDomainStatusPage()
+    {
+        try {
+            $resp = $this->client->get(self::DOMAIN_STATUS_URL, [
+                'headers' => [
+                    'Referer' => 'https://my.freenom.com/clientarea.php'
+                ],
+                'cookies' => $this->jar
+            ]);
+
+            $page = (string)$resp->getBody();
+        } catch (\Exception $e) {
+            throw new LlfException(34520013, $e->getMessage());
         }
 
-        // 页面token
-        if (!preg_match(self::TOKEN_REGEX, $body, $matches)) {
-            throw new LlfException(34520004);
+        if (!preg_match(self::LOGIN_STATUS_REGEX, $page)) {
+            throw new LlfException(34520009);
         }
-        $token = $matches['token'];
-
-        // 续期
-        $result = '';
-        $renewed = $renewedTG = ''; // 续期成功的域名
-        $notRenewed = $notRenewedTG = ''; // 记录续期出错的域名,用于邮件通知内容
-        $domainInfo = $domainInfoTG = ''; // 域名状态信息,用于邮件通知内容
-        foreach ($domains as $d) {
+
+        return $page;
+    }
+
+    /**
+     * 续期所有域名
+     *
+     * @param array $allDomains
+     * @param string $token
+     *
+     * @return bool
+     */
+    public function renewAllDomains(array $allDomains, string $token)
+    {
+        $renewalSuccessArr = [];
+        $renewalFailuresArr = [];
+        $domainStatusArr = [];
+
+        foreach ($allDomains as $d) {
             $domain = $d['domain'];
-            $days = intval($d['days']);
+            $days = (int)$d['days'];
             $id = $d['id'];
 
-            // 免费域名只允许在到期前14天内续期
+            // 免费域名只允许在到期前 14 天内续期
             if ($days <= 14) {
-                try {
-                    $response = $this->client->post(self::RENEW_DOMAIN_URL, [
-                        'headers' => [
-                            'Referer' => sprintf('https://my.freenom.com/domains.php?a=renewdomain&domain=%s', $id),
-                            'Content-Type' => 'application/x-www-form-urlencoded'
-                        ],
-                        'form_params' => [
-                            'token' => $token,
-                            'renewalid' => $id,
-                            sprintf('renewalperiod[%s]', $id) => '12M', // 续期一年
-                            'paymentmethod' => 'credit', // 支付方式:信用卡
-                        ],
-                        'cookies' => $this->jar
-                    ]);
-                } catch (\Exception $e) {
-                    system_log(sprintf('%s:续期请求出错:%s', $this->username, $e->getMessage()));
-                    continue;
-                }
+                $renewalResult = $this->renew($id, $token);
 
-                $body = (string)$response->getBody();
                 sleep(1);
 
-                if (stripos($body, 'Order Confirmation') === false) { // 续期失败
-                    $result .= sprintf("%s续期失败\n", $domain);
-                    $notRenewed .= sprintf('<a href="http://%s" rel="noopener" target="_blank">%s</a>', $domain, $domain);
-                    $notRenewedTG .= sprintf('[%s](http://%s)  ', $domain, $domain);
+                if ($renewalResult) {
+                    $renewalSuccessArr[] = $domain;
+
+                    continue; // 续期成功的域名无需记录过期天数
                 } else {
-                    $result .= sprintf("%s续期成功\n", $domain);
-                    $renewed .= sprintf('<a href="http://%s" rel="noopener" target="_blank">%s</a>', $domain, $domain);
-                    $renewedTG .= sprintf('[%s](http://%s)  ', $domain, $domain);
-                    continue;
+                    $renewalFailuresArr[] = $domain;
                 }
             }
 
-            $domainInfo .= sprintf('<a href="http://%s" rel="noopener" target="_blank">%s</a>还有<span style="font-weight: bold; font-size: 16px;">%d</span>天到期,', $domain, $domain, $days);
-            $domainInfoTG .= sprintf('[%s](http://%s)还有*%d*天到期,', $domain, $domain, $days);
+            // 记录域名过期天数
+            $domainStatusArr[$domain] = $days;
         }
-        $domainInfoTG .= "更多信息可以参考[Freenom官网](https://my.freenom.com/domains.php?a=renewals)哦~\n\n(如果你不想每次执行都收到推送,请将 .env 中 NOTICE_FREQ 的值设为0,使程序只在有续期操作时才推送)";
-
-        if ($notRenewed || $renewed) {
-            Mail::send(
-                '主人,我刚刚帮你续期域名啦~',
-                [
-                    $this->username,
-                    $renewed ? sprintf('续期成功:%s<br>', $renewed) : '',
-                    $notRenewed ? sprintf('续期出错:%s<br>', $notRenewed) : '',
-                    $domainInfo ?: '哦豁,没看到其它域名。'
-                ]
-            );
-            TelegramBot::send(sprintf(
-                "主人,我刚刚帮你续期域名啦~\n\n%s%s\n另外,%s",
-                $renewedTG ? sprintf("续期成功:%s\n", $renewedTG) : '',
-                $notRenewedTG ? sprintf("续期失败:%s\n", $notRenewedTG) : '',
-                $domainInfoTG
+
+        // 存在续期操作
+        if ($renewalSuccessArr || $renewalFailuresArr) {
+            $data = [
+                'username' => $this->username,
+                'renewalSuccessArr' => $renewalSuccessArr,
+                'renewalFailuresArr' => $renewalFailuresArr,
+                'domainStatusArr' => $domainStatusArr,
+            ];
+            Message::send('', '主人,我刚刚帮你续期域名啦~', 2, $data);
+
+            system_log(sprintf(
+                '恭喜,成功续期 <green>%d</green> 个域名,失败 <green>%d</green> 个域名,详细的续期结果已送信成功,请注意查收',
+                count($renewalSuccessArr),
+                count($renewalFailuresArr)
             ));
-            system_log(sprintf("%s:续期结果如下:\n%s", $this->username, $result));
+
+            Log::info(sprintf("账户:%s\n续期结果如下:\n", $this->username), $data);
+
+            return true;
+        }
+
+        // 不存在续期操作
+        if (config('notice_freq') === 1) {
+            $data = [
+                'username' => $this->username,
+                'domainStatusArr' => $domainStatusArr,
+            ];
+            Message::send('', '报告,今天没有域名需要续期', 3, $data);
+
+            system_log('域名状态信息已送信成功,请注意查收');
         } else {
-            if (config('notice_freq') == 1) {
-                Mail::send(
-                    '报告,今天没有域名需要续期',
-                    [
-                        $this->username,
-                        $domainInfo
-                    ],
-                    '',
-                    'notice'
-                );
-                TelegramBot::send("报告,今天没有域名需要续期,所有域名情况如下:\n\n" . $domainInfoTG);
-            } else {
-                system_log('当前通知频率为「仅当有续期操作时」,故本次不会推送通知');
-            }
-            system_log(sprintf('%s:<green>执行成功,今次没有需要续期的域名</green>', $this->username));
+            system_log('当前通知频率为「仅当有续期操作时」,故本次不会推送通知');
+        }
+
+        system_log(sprintf('%s:<green>执行成功,今次没有需要续期的域名</green>', $this->username));
+
+        return true;
+    }
+
+    /**
+     * 续期单个域名
+     *
+     * @param int $id
+     * @param string $token
+     *
+     * @return bool
+     */
+    protected function renew(int $id, string $token)
+    {
+        try {
+            $resp = $this->client->post(self::RENEW_DOMAIN_URL, [
+                'headers' => [
+                    'Referer' => sprintf('https://my.freenom.com/domains.php?a=renewdomain&domain=%s', $id),
+                    'Content-Type' => 'application/x-www-form-urlencoded'
+                ],
+                'form_params' => [
+                    'token' => $token,
+                    'renewalid' => $id,
+                    sprintf('renewalperiod[%s]', $id) => '12M', // 续期一年
+                    'paymentmethod' => 'credit', // 支付方式:信用卡
+                ],
+                'cookies' => $this->jar
+            ]);
+
+            $resp = (string)$resp->getBody();
+
+            return stripos($resp, 'Order Confirmation') !== false;
+        } catch (\Exception $e) {
+            $errorMsg = sprintf('续期请求出错:%s,域名 ID:%s(账户:%s)', $e->getMessage(), $id, $this->username);
+            system_log($errorMsg);
+            Message::send($errorMsg);
+
+            return false;
         }
     }
 
@@ -279,7 +346,7 @@ class FreeNom
     }
 
     /**
-     * 获取freenom账户信息
+     * 获取 FreeNom 账户信息
      *
      * @return array
      * @throws LlfException
@@ -319,29 +386,17 @@ class FreeNom
     /**
      * 发送异常报告
      *
-     * @param \Exception $e
-     *
-     * @throws \Exception
+     * @param $e \Exception|LlfException
      */
     private function sendExceptionReport($e)
     {
-        Mail::send(
-            '主人,' . $e->getMessage(),
-            [
-                $this->username,
-                sprintf('具体是在%s文件的第%d行,抛出了一个异常。异常的内容是%s,快去看看吧。', $e->getFile(), $e->getLine(), $e->getMessage()),
-            ],
-            '',
-            'LlfException'
-        );
-
-        TelegramBot::send(sprintf(
-            '主人,出错了。具体是在%s文件的第%d行,抛出了一个异常。异常的内容是%s,快去看看吧。(账户:%s)',
+        Message::send(sprintf(
+            '具体是在%s文件的第%d行,抛出了一个异常。异常的内容是%s,快去看看吧。(账户:%s)',
             $e->getFile(),
             $e->getLine(),
             $e->getMessage(),
             $this->username
-        ), '', false);
+        ), '主人,出错了,' . $e->getMessage());
     }
 
     /**
@@ -356,7 +411,14 @@ class FreeNom
                 $this->username = $account['username'];
                 $this->password = $account['password'];
 
-                $this->renewDomains();
+                $this->jar = new CookieJar(); // 所有请求共用一个 CookieJar 实例
+                $this->login($this->username, $this->password);
+
+                $domainStatusPage = $this->getDomainStatusPage();
+                $allDomains = $this->getAllDomains($domainStatusPage);
+                $token = $this->getToken($domainStatusPage);
+
+                $this->renewAllDomains($allDomains, $token);
             } catch (LlfException $e) {
                 system_log(sprintf('出错:<red>%s</red>', $e->getMessage()));
                 $this->sendExceptionReport($e);
@@ -366,4 +428,4 @@ class FreeNom
             }
         }
     }
-}
+}

+ 18 - 13
app/helpers.php

@@ -26,7 +26,7 @@ if (!function_exists('config')) {
      */
     function config($key = '', $default = null)
     {
-        return Config::instance()->get($key, $default);
+        return Config::getInstance()->get($key, $default);
     }
 }
 
@@ -40,7 +40,7 @@ if (!function_exists('lang')) {
      */
     function lang($key = '')
     {
-        return Lang::instance()->get($key);
+        return Lang::getInstance()->get($key);
     }
 }
 
@@ -89,7 +89,7 @@ if (!function_exists('system_log')) {
             }
 
             // 尝试为消息着色
-            $c = PhpColor::instance()->getColorInstance();
+            $c = PhpColor::getInstance()->getColorInstance();
             echo $c($msg)->colorize();
 
             // 干掉着色标签
@@ -201,7 +201,7 @@ if (!function_exists('env')) {
      */
     function env($key = '', $default = null)
     {
-        return Env::instance()->get($key, $default);
+        return Env::getInstance()->get($key, $default);
     }
 }
 
@@ -216,7 +216,7 @@ if (!function_exists('get_argv')) {
      */
     function get_argv(string $name, string $default = '')
     {
-        return Argv::instance()->get($name, $default);
+        return Argv::getInstance()->get($name, $default);
     }
 }
 
@@ -224,21 +224,26 @@ if (!function_exists('system_check')) {
     /**
      * 检查环境是否满足要求
      *
+     * @param bool $isSCF 是否腾讯云函数
+     *
      * @throws LlfException
      */
-    function system_check()
+    function system_check($isSCF = false)
     {
-        if (!function_exists('putenv')) {
-            throw new LlfException(34520005);
-        }
-
         if (version_compare(PHP_VERSION, '7.0.0') < 0) {
             throw new LlfException(34520006);
         }
 
-        $envFile = ROOT_PATH . '/.env';
-        if (!file_exists($envFile)) {
-            throw new LlfException(copy(ROOT_PATH . '/.env.example', $envFile) ? 34520007 : 34520008);
+        // 如果是在腾讯云函数部署,则不需要检查这几项
+        if (!$isSCF) {
+            if (!function_exists('putenv')) {
+                throw new LlfException(34520005);
+            }
+
+            $envFile = ROOT_PATH . '/.env';
+            if (!file_exists($envFile)) {
+                throw new LlfException(copy(ROOT_PATH . '/.env.example', $envFile) ? 34520007 : 34520008);
+            }
         }
 
         if (!extension_loaded('curl')) {

+ 35 - 34
composer.json

@@ -1,37 +1,38 @@
 {
-  "name": "luolongfei/freenom",
-  "description": "freenom域名自动续期。",
-  "type": "project",
-  "require": {
-    "php": ">=7.1.0",
-    "ext-curl": "*",
-    "ext-openssl": "*",
-    "guzzlehttp/guzzle": "^6.3",
-    "monolog/monolog": "^1.24",
-    "bramus/monolog-colored-line-formatter": "~2.0",
-    "phpmailer/phpmailer": "^6.0",
-    "vlucas/phpdotenv": "^3.3",
-    "predis/predis": "^1.1",
-    "ext-json": "*",
-    "ext-bcmath": "*",
-    "ext-pdo": "*",
-    "kevinlebrun/colors.php": "^1.0"
-  },
-  "license": "MIT",
-  "authors": [
-    {
-      "name": "luolongfei",
-      "email": "[email protected]",
-      "homepage": "https://github.com/luolongfei/freenom"
-    }
-  ],
-  "autoload": {
-    "psr-4": {
-      "Luolongfei\\Libs\\": "libs",
-      "Luolongfei\\App\\": "app"
+    "name": "luolongfei/freenom",
+    "description": "freenom域名自动续期。",
+    "type": "project",
+    "require": {
+        "php": ">=7.1.0",
+        "ext-curl": "*",
+        "ext-openssl": "*",
+        "guzzlehttp/guzzle": "^6.3",
+        "monolog/monolog": "^1.24",
+        "bramus/monolog-colored-line-formatter": "~2.0",
+        "phpmailer/phpmailer": "^6.0",
+        "vlucas/phpdotenv": "^3.3",
+        "predis/predis": "^1.1",
+        "ext-json": "*",
+        "ext-bcmath": "*",
+        "ext-pdo": "*",
+        "kevinlebrun/colors.php": "^1.0",
+        "ext-mbstring": "*"
     },
-    "files": [
-      "app/helpers.php"
-    ]
-  }
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "luolongfei",
+            "email": "[email protected]",
+            "homepage": "https://github.com/luolongfei/freenom"
+        }
+    ],
+    "autoload": {
+        "psr-4": {
+            "Luolongfei\\Libs\\": "libs",
+            "Luolongfei\\App\\": "app"
+        },
+        "files": [
+            "app/helpers.php"
+        ]
+    }
 }

+ 846 - 844
composer.lock

@@ -1,844 +1,846 @@
-{
-    "_readme": [
-        "This file locks the dependencies of your project to a known state",
-        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
-        "This file is @generated automatically"
-    ],
-    "content-hash": "1490a7a3df10963c3b2891841cfe0591",
-    "packages": [
-        {
-            "name": "bramus/ansi-php",
-            "version": "3.0.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/bramus/ansi-php.git",
-                "reference": "fb0be33f36053af7454d462e3ddc0a2ac0b2f311"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/bramus/ansi-php/zipball/fb0be33f36053af7454d462e3ddc0a2ac0b2f311",
-                "reference": "fb0be33f36053af7454d462e3ddc0a2ac0b2f311",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.4.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "~4.0"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Bramus\\Ansi\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Bramus Van Damme",
-                    "email": "[email protected]",
-                    "homepage": "https://www.bram.us/"
-                }
-            ],
-            "description": "ANSI Control Functions and ANSI Control Sequences (Colors, Erasing, etc.) for PHP CLI Apps",
-            "time": "2019-12-03T09:04:38+00:00"
-        },
-        {
-            "name": "bramus/monolog-colored-line-formatter",
-            "version": "2.0.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/bramus/monolog-colored-line-formatter.git",
-                "reference": "6bff15eee00afe2690642535b0f1541f10852c2b"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/bramus/monolog-colored-line-formatter/zipball/6bff15eee00afe2690642535b0f1541f10852c2b",
-                "reference": "6bff15eee00afe2690642535b0f1541f10852c2b",
-                "shasum": ""
-            },
-            "require": {
-                "bramus/ansi-php": "~3.0",
-                "php": ">=5.4.0"
-            },
-            "require-dev": {
-                "monolog/monolog": "~1.0",
-                "phpunit/phpunit": "~4.0"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Bramus\\Monolog\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Bramus Van Damme",
-                    "email": "[email protected]",
-                    "homepage": "https://www.bram.us/"
-                }
-            ],
-            "description": "Colored Line Formatter for Monolog",
-            "time": "2015-01-07T22:12:35+00:00"
-        },
-        {
-            "name": "guzzlehttp/guzzle",
-            "version": "6.5.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/guzzle/guzzle.git",
-                "reference": "43ece0e75098b7ecd8d13918293029e555a50f82"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/43ece0e75098b7ecd8d13918293029e555a50f82",
-                "reference": "43ece0e75098b7ecd8d13918293029e555a50f82",
-                "shasum": ""
-            },
-            "require": {
-                "ext-json": "*",
-                "guzzlehttp/promises": "^1.0",
-                "guzzlehttp/psr7": "^1.6.1",
-                "php": ">=5.5"
-            },
-            "require-dev": {
-                "ext-curl": "*",
-                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
-                "psr/log": "^1.1"
-            },
-            "suggest": {
-                "ext-intl": "Required for Internationalized Domain Name (IDN) support",
-                "psr/log": "Required for using the Log middleware"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "6.5-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "GuzzleHttp\\": "src/"
-                },
-                "files": [
-                    "src/functions_include.php"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Michael Dowling",
-                    "email": "[email protected]",
-                    "homepage": "https://github.com/mtdowling"
-                }
-            ],
-            "description": "Guzzle is a PHP HTTP client library",
-            "homepage": "http://guzzlephp.org/",
-            "keywords": [
-                "client",
-                "curl",
-                "framework",
-                "http",
-                "http client",
-                "rest",
-                "web service"
-            ],
-            "time": "2019-12-23T11:57:10+00:00"
-        },
-        {
-            "name": "guzzlehttp/promises",
-            "version": "v1.3.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/guzzle/promises.git",
-                "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
-                "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.5.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^4.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.4-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "GuzzleHttp\\Promise\\": "src/"
-                },
-                "files": [
-                    "src/functions_include.php"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Michael Dowling",
-                    "email": "[email protected]",
-                    "homepage": "https://github.com/mtdowling"
-                }
-            ],
-            "description": "Guzzle promises library",
-            "keywords": [
-                "promise"
-            ],
-            "time": "2016-12-20T10:07:11+00:00"
-        },
-        {
-            "name": "guzzlehttp/psr7",
-            "version": "1.6.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/guzzle/psr7.git",
-                "reference": "239400de7a173fe9901b9ac7c06497751f00727a"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a",
-                "reference": "239400de7a173fe9901b9ac7c06497751f00727a",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.4.0",
-                "psr/http-message": "~1.0",
-                "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
-            },
-            "provide": {
-                "psr/http-message-implementation": "1.0"
-            },
-            "require-dev": {
-                "ext-zlib": "*",
-                "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
-            },
-            "suggest": {
-                "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.6-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "GuzzleHttp\\Psr7\\": "src/"
-                },
-                "files": [
-                    "src/functions_include.php"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Michael Dowling",
-                    "email": "[email protected]",
-                    "homepage": "https://github.com/mtdowling"
-                },
-                {
-                    "name": "Tobias Schultze",
-                    "homepage": "https://github.com/Tobion"
-                }
-            ],
-            "description": "PSR-7 message implementation that also provides common utility methods",
-            "keywords": [
-                "http",
-                "message",
-                "psr-7",
-                "request",
-                "response",
-                "stream",
-                "uri",
-                "url"
-            ],
-            "time": "2019-07-01T23:21:34+00:00"
-        },
-        {
-            "name": "kevinlebrun/colors.php",
-            "version": "1.0.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/kevinlebrun/colors.php.git",
-                "reference": "cdda5eee41314b87cd5a8bb91b1ffc7c0210e673"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/kevinlebrun/colors.php/zipball/cdda5eee41314b87cd5a8bb91b1ffc7c0210e673",
-                "reference": "cdda5eee41314b87cd5a8bb91b1ffc7c0210e673",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "3.7.*",
-                "satooshi/php-coveralls": "1.0.*",
-                "squizlabs/php_codesniffer": "1.*"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-0": {
-                    "Colors": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Kevin Le Brun",
-                    "email": "[email protected]",
-                    "homepage": "http://kevinlebrun.fr",
-                    "role": "developer"
-                }
-            ],
-            "description": "Colors for PHP CLI scripts",
-            "homepage": "https://github.com/kevinlebrun/colors.php",
-            "keywords": [
-                "cli",
-                "color",
-                "colors",
-                "console",
-                "shell"
-            ],
-            "time": "2018-05-30T08:34:23+00:00"
-        },
-        {
-            "name": "monolog/monolog",
-            "version": "1.25.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/Seldaek/monolog.git",
-                "reference": "fa82921994db851a8becaf3787a9e73c5976b6f1"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fa82921994db851a8becaf3787a9e73c5976b6f1",
-                "reference": "fa82921994db851a8becaf3787a9e73c5976b6f1",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.0",
-                "psr/log": "~1.0"
-            },
-            "provide": {
-                "psr/log-implementation": "1.0.0"
-            },
-            "require-dev": {
-                "aws/aws-sdk-php": "^2.4.9 || ^3.0",
-                "doctrine/couchdb": "~1.0@dev",
-                "graylog2/gelf-php": "~1.0",
-                "jakub-onderka/php-parallel-lint": "0.9",
-                "php-amqplib/php-amqplib": "~2.4",
-                "php-console/php-console": "^3.1.3",
-                "phpunit/phpunit": "~4.5",
-                "phpunit/phpunit-mock-objects": "2.3.0",
-                "ruflin/elastica": ">=0.90 <3.0",
-                "sentry/sentry": "^0.13",
-                "swiftmailer/swiftmailer": "^5.3|^6.0"
-            },
-            "suggest": {
-                "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
-                "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
-                "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
-                "ext-mongo": "Allow sending log messages to a MongoDB server",
-                "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
-                "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
-                "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
-                "php-console/php-console": "Allow sending log messages to Google Chrome",
-                "rollbar/rollbar": "Allow sending log messages to Rollbar",
-                "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
-                "sentry/sentry": "Allow sending log messages to a Sentry server"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "2.0.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Monolog\\": "src/Monolog"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Jordi Boggiano",
-                    "email": "[email protected]",
-                    "homepage": "http://seld.be"
-                }
-            ],
-            "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
-            "homepage": "http://github.com/Seldaek/monolog",
-            "keywords": [
-                "log",
-                "logging",
-                "psr-3"
-            ],
-            "time": "2019-12-20T14:15:16+00:00"
-        },
-        {
-            "name": "phpmailer/phpmailer",
-            "version": "v6.1.6",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/PHPMailer/PHPMailer.git",
-                "reference": "c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3",
-                "reference": "c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3",
-                "shasum": ""
-            },
-            "require": {
-                "ext-ctype": "*",
-                "ext-filter": "*",
-                "php": ">=5.5.0"
-            },
-            "require-dev": {
-                "doctrine/annotations": "^1.2",
-                "friendsofphp/php-cs-fixer": "^2.2",
-                "phpunit/phpunit": "^4.8 || ^5.7"
-            },
-            "suggest": {
-                "ext-mbstring": "Needed to send email in multibyte encoding charset",
-                "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
-                "league/oauth2-google": "Needed for Google XOAUTH2 authentication",
-                "psr/log": "For optional PSR-3 debug logging",
-                "stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication",
-                "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "PHPMailer\\PHPMailer\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "LGPL-2.1-only"
-            ],
-            "authors": [
-                {
-                    "name": "Marcus Bointon",
-                    "email": "[email protected]"
-                },
-                {
-                    "name": "Jim Jagielski",
-                    "email": "[email protected]"
-                },
-                {
-                    "name": "Andy Prevost",
-                    "email": "[email protected]"
-                },
-                {
-                    "name": "Brent R. Matzelle"
-                }
-            ],
-            "description": "PHPMailer is a full-featured email creation and transfer class for PHP",
-            "time": "2020-05-27T12:24:03+00:00"
-        },
-        {
-            "name": "phpoption/phpoption",
-            "version": "1.7.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/schmittjoh/php-option.git",
-                "reference": "77f7c4d2e65413aff5b5a8cc8b3caf7a28d81959"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/77f7c4d2e65413aff5b5a8cc8b3caf7a28d81959",
-                "reference": "77f7c4d2e65413aff5b5a8cc8b3caf7a28d81959",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^5.5.9 || ^7.0"
-            },
-            "require-dev": {
-                "bamarni/composer-bin-plugin": "^1.3",
-                "phpunit/phpunit": "^4.8.35 || ^5.0 || ^6.0 || ^7.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.7-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "PhpOption\\": "src/PhpOption/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "Apache-2.0"
-            ],
-            "authors": [
-                {
-                    "name": "Johannes M. Schmitt",
-                    "email": "[email protected]"
-                },
-                {
-                    "name": "Graham Campbell",
-                    "email": "[email protected]"
-                }
-            ],
-            "description": "Option Type for PHP",
-            "keywords": [
-                "language",
-                "option",
-                "php",
-                "type"
-            ],
-            "time": "2019-12-15T19:35:24+00:00"
-        },
-        {
-            "name": "predis/predis",
-            "version": "v1.1.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/nrk/predis.git",
-                "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/nrk/predis/zipball/f0210e38881631afeafb56ab43405a92cafd9fd1",
-                "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.9"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "~4.8"
-            },
-            "suggest": {
-                "ext-curl": "Allows access to Webdis when paired with phpiredis",
-                "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Predis\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Daniele Alessandri",
-                    "email": "[email protected]",
-                    "homepage": "http://clorophilla.net"
-                }
-            ],
-            "description": "Flexible and feature-complete Redis client for PHP and HHVM",
-            "homepage": "http://github.com/nrk/predis",
-            "keywords": [
-                "nosql",
-                "predis",
-                "redis"
-            ],
-            "time": "2016-06-16T16:22:20+00:00"
-        },
-        {
-            "name": "psr/http-message",
-            "version": "1.0.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/php-fig/http-message.git",
-                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
-                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.0.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Psr\\Http\\Message\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "PHP-FIG",
-                    "homepage": "http://www.php-fig.org/"
-                }
-            ],
-            "description": "Common interface for HTTP messages",
-            "homepage": "https://github.com/php-fig/http-message",
-            "keywords": [
-                "http",
-                "http-message",
-                "psr",
-                "psr-7",
-                "request",
-                "response"
-            ],
-            "time": "2016-08-06T14:39:51+00:00"
-        },
-        {
-            "name": "psr/log",
-            "version": "1.1.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/php-fig/log.git",
-                "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801",
-                "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.1.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Psr\\Log\\": "Psr/Log/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "PHP-FIG",
-                    "homepage": "http://www.php-fig.org/"
-                }
-            ],
-            "description": "Common interface for logging libraries",
-            "homepage": "https://github.com/php-fig/log",
-            "keywords": [
-                "log",
-                "psr",
-                "psr-3"
-            ],
-            "time": "2019-11-01T11:05:21+00:00"
-        },
-        {
-            "name": "ralouphie/getallheaders",
-            "version": "3.0.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/ralouphie/getallheaders.git",
-                "reference": "120b605dfeb996808c31b6477290a714d356e822"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
-                "reference": "120b605dfeb996808c31b6477290a714d356e822",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.6"
-            },
-            "require-dev": {
-                "php-coveralls/php-coveralls": "^2.1",
-                "phpunit/phpunit": "^5 || ^6.5"
-            },
-            "type": "library",
-            "autoload": {
-                "files": [
-                    "src/getallheaders.php"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Ralph Khattar",
-                    "email": "[email protected]"
-                }
-            ],
-            "description": "A polyfill for getallheaders.",
-            "time": "2019-03-08T08:55:37+00:00"
-        },
-        {
-            "name": "symfony/polyfill-ctype",
-            "version": "v1.13.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-ctype.git",
-                "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
-                "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.3"
-            },
-            "suggest": {
-                "ext-ctype": "For best performance"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.13-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Symfony\\Polyfill\\Ctype\\": ""
-                },
-                "files": [
-                    "bootstrap.php"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Gert de Pagter",
-                    "email": "[email protected]"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill for ctype functions",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "ctype",
-                "polyfill",
-                "portable"
-            ],
-            "time": "2019-11-27T13:56:44+00:00"
-        },
-        {
-            "name": "vlucas/phpdotenv",
-            "version": "v3.6.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/vlucas/phpdotenv.git",
-                "reference": "1bdf24f065975594f6a117f0f1f6cabf1333b156"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/1bdf24f065975594f6a117f0f1f6cabf1333b156",
-                "reference": "1bdf24f065975594f6a117f0f1f6cabf1333b156",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^5.4 || ^7.0",
-                "phpoption/phpoption": "^1.5",
-                "symfony/polyfill-ctype": "^1.9"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^4.8.35 || ^5.0 || ^6.0 || ^7.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "3.6-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Dotenv\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Graham Campbell",
-                    "email": "[email protected]",
-                    "homepage": "https://gjcampbell.co.uk/"
-                },
-                {
-                    "name": "Vance Lucas",
-                    "email": "[email protected]",
-                    "homepage": "https://vancelucas.com/"
-                }
-            ],
-            "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
-            "keywords": [
-                "dotenv",
-                "env",
-                "environment"
-            ],
-            "time": "2019-09-10T21:37:39+00:00"
-        }
-    ],
-    "packages-dev": [],
-    "aliases": [],
-    "minimum-stability": "stable",
-    "stability-flags": [],
-    "prefer-stable": false,
-    "prefer-lowest": false,
-    "platform": {
-        "php": ">=7.1.0",
-        "ext-curl": "*",
-        "ext-openssl": "*",
-        "ext-json": "*",
-        "ext-bcmath": "*",
-        "ext-pdo": "*"
-    },
-    "platform-dev": []
-}
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "b504850b7e1b4a31e982816488f90fff",
+    "packages": [
+        {
+            "name": "bramus/ansi-php",
+            "version": "3.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/bramus/ansi-php.git",
+                "reference": "fb0be33f36053af7454d462e3ddc0a2ac0b2f311"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/bramus/ansi-php/zipball/fb0be33f36053af7454d462e3ddc0a2ac0b2f311",
+                "reference": "fb0be33f36053af7454d462e3ddc0a2ac0b2f311",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Bramus\\Ansi\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Bramus Van Damme",
+                    "email": "[email protected]",
+                    "homepage": "https://www.bram.us/"
+                }
+            ],
+            "description": "ANSI Control Functions and ANSI Control Sequences (Colors, Erasing, etc.) for PHP CLI Apps",
+            "time": "2019-12-03T09:04:38+00:00"
+        },
+        {
+            "name": "bramus/monolog-colored-line-formatter",
+            "version": "2.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/bramus/monolog-colored-line-formatter.git",
+                "reference": "6bff15eee00afe2690642535b0f1541f10852c2b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/bramus/monolog-colored-line-formatter/zipball/6bff15eee00afe2690642535b0f1541f10852c2b",
+                "reference": "6bff15eee00afe2690642535b0f1541f10852c2b",
+                "shasum": ""
+            },
+            "require": {
+                "bramus/ansi-php": "~3.0",
+                "php": ">=5.4.0"
+            },
+            "require-dev": {
+                "monolog/monolog": "~1.0",
+                "phpunit/phpunit": "~4.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Bramus\\Monolog\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Bramus Van Damme",
+                    "email": "[email protected]",
+                    "homepage": "https://www.bram.us/"
+                }
+            ],
+            "description": "Colored Line Formatter for Monolog",
+            "time": "2015-01-07T22:12:35+00:00"
+        },
+        {
+            "name": "guzzlehttp/guzzle",
+            "version": "6.5.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/guzzle.git",
+                "reference": "43ece0e75098b7ecd8d13918293029e555a50f82"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/43ece0e75098b7ecd8d13918293029e555a50f82",
+                "reference": "43ece0e75098b7ecd8d13918293029e555a50f82",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "guzzlehttp/promises": "^1.0",
+                "guzzlehttp/psr7": "^1.6.1",
+                "php": ">=5.5"
+            },
+            "require-dev": {
+                "ext-curl": "*",
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
+                "psr/log": "^1.1"
+            },
+            "suggest": {
+                "ext-intl": "Required for Internationalized Domain Name (IDN) support",
+                "psr/log": "Required for using the Log middleware"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "6.5-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "[email protected]",
+                    "homepage": "https://github.com/mtdowling"
+                }
+            ],
+            "description": "Guzzle is a PHP HTTP client library",
+            "homepage": "http://guzzlephp.org/",
+            "keywords": [
+                "client",
+                "curl",
+                "framework",
+                "http",
+                "http client",
+                "rest",
+                "web service"
+            ],
+            "time": "2019-12-23T11:57:10+00:00"
+        },
+        {
+            "name": "guzzlehttp/promises",
+            "version": "v1.3.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/promises.git",
+                "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+                "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Promise\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "[email protected]",
+                    "homepage": "https://github.com/mtdowling"
+                }
+            ],
+            "description": "Guzzle promises library",
+            "keywords": [
+                "promise"
+            ],
+            "time": "2016-12-20T10:07:11+00:00"
+        },
+        {
+            "name": "guzzlehttp/psr7",
+            "version": "1.6.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/psr7.git",
+                "reference": "239400de7a173fe9901b9ac7c06497751f00727a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a",
+                "reference": "239400de7a173fe9901b9ac7c06497751f00727a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0",
+                "psr/http-message": "~1.0",
+                "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
+            },
+            "provide": {
+                "psr/http-message-implementation": "1.0"
+            },
+            "require-dev": {
+                "ext-zlib": "*",
+                "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
+            },
+            "suggest": {
+                "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.6-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Psr7\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "[email protected]",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "PSR-7 message implementation that also provides common utility methods",
+            "keywords": [
+                "http",
+                "message",
+                "psr-7",
+                "request",
+                "response",
+                "stream",
+                "uri",
+                "url"
+            ],
+            "time": "2019-07-01T23:21:34+00:00"
+        },
+        {
+            "name": "kevinlebrun/colors.php",
+            "version": "1.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/kevinlebrun/colors.php.git",
+                "reference": "cdda5eee41314b87cd5a8bb91b1ffc7c0210e673"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/kevinlebrun/colors.php/zipball/cdda5eee41314b87cd5a8bb91b1ffc7c0210e673",
+                "reference": "cdda5eee41314b87cd5a8bb91b1ffc7c0210e673",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "3.7.*",
+                "satooshi/php-coveralls": "1.0.*",
+                "squizlabs/php_codesniffer": "1.*"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "Colors": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Kevin Le Brun",
+                    "email": "[email protected]",
+                    "homepage": "http://kevinlebrun.fr",
+                    "role": "developer"
+                }
+            ],
+            "description": "Colors for PHP CLI scripts",
+            "homepage": "https://github.com/kevinlebrun/colors.php",
+            "keywords": [
+                "cli",
+                "color",
+                "colors",
+                "console",
+                "shell"
+            ],
+            "time": "2018-05-30T08:34:23+00:00"
+        },
+        {
+            "name": "monolog/monolog",
+            "version": "1.25.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Seldaek/monolog.git",
+                "reference": "fa82921994db851a8becaf3787a9e73c5976b6f1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fa82921994db851a8becaf3787a9e73c5976b6f1",
+                "reference": "fa82921994db851a8becaf3787a9e73c5976b6f1",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0",
+                "psr/log": "~1.0"
+            },
+            "provide": {
+                "psr/log-implementation": "1.0.0"
+            },
+            "require-dev": {
+                "aws/aws-sdk-php": "^2.4.9 || ^3.0",
+                "doctrine/couchdb": "~1.0@dev",
+                "graylog2/gelf-php": "~1.0",
+                "jakub-onderka/php-parallel-lint": "0.9",
+                "php-amqplib/php-amqplib": "~2.4",
+                "php-console/php-console": "^3.1.3",
+                "phpunit/phpunit": "~4.5",
+                "phpunit/phpunit-mock-objects": "2.3.0",
+                "ruflin/elastica": ">=0.90 <3.0",
+                "sentry/sentry": "^0.13",
+                "swiftmailer/swiftmailer": "^5.3|^6.0"
+            },
+            "suggest": {
+                "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
+                "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
+                "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
+                "ext-mongo": "Allow sending log messages to a MongoDB server",
+                "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
+                "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
+                "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
+                "php-console/php-console": "Allow sending log messages to Google Chrome",
+                "rollbar/rollbar": "Allow sending log messages to Rollbar",
+                "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
+                "sentry/sentry": "Allow sending log messages to a Sentry server"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Monolog\\": "src/Monolog"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jordi Boggiano",
+                    "email": "[email protected]",
+                    "homepage": "http://seld.be"
+                }
+            ],
+            "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
+            "homepage": "http://github.com/Seldaek/monolog",
+            "keywords": [
+                "log",
+                "logging",
+                "psr-3"
+            ],
+            "time": "2019-12-20T14:15:16+00:00"
+        },
+        {
+            "name": "phpmailer/phpmailer",
+            "version": "v6.1.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHPMailer/PHPMailer.git",
+                "reference": "c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3",
+                "reference": "c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3",
+                "shasum": ""
+            },
+            "require": {
+                "ext-ctype": "*",
+                "ext-filter": "*",
+                "php": ">=5.5.0"
+            },
+            "require-dev": {
+                "doctrine/annotations": "^1.2",
+                "friendsofphp/php-cs-fixer": "^2.2",
+                "phpunit/phpunit": "^4.8 || ^5.7"
+            },
+            "suggest": {
+                "ext-mbstring": "Needed to send email in multibyte encoding charset",
+                "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
+                "league/oauth2-google": "Needed for Google XOAUTH2 authentication",
+                "psr/log": "For optional PSR-3 debug logging",
+                "stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication",
+                "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "PHPMailer\\PHPMailer\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-2.1-only"
+            ],
+            "authors": [
+                {
+                    "name": "Marcus Bointon",
+                    "email": "[email protected]"
+                },
+                {
+                    "name": "Jim Jagielski",
+                    "email": "[email protected]"
+                },
+                {
+                    "name": "Andy Prevost",
+                    "email": "[email protected]"
+                },
+                {
+                    "name": "Brent R. Matzelle"
+                }
+            ],
+            "description": "PHPMailer is a full-featured email creation and transfer class for PHP",
+            "time": "2020-05-27T12:24:03+00:00"
+        },
+        {
+            "name": "phpoption/phpoption",
+            "version": "1.7.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/schmittjoh/php-option.git",
+                "reference": "77f7c4d2e65413aff5b5a8cc8b3caf7a28d81959"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/77f7c4d2e65413aff5b5a8cc8b3caf7a28d81959",
+                "reference": "77f7c4d2e65413aff5b5a8cc8b3caf7a28d81959",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.5.9 || ^7.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.3",
+                "phpunit/phpunit": "^4.8.35 || ^5.0 || ^6.0 || ^7.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.7-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "PhpOption\\": "src/PhpOption/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "Johannes M. Schmitt",
+                    "email": "[email protected]"
+                },
+                {
+                    "name": "Graham Campbell",
+                    "email": "[email protected]"
+                }
+            ],
+            "description": "Option Type for PHP",
+            "keywords": [
+                "language",
+                "option",
+                "php",
+                "type"
+            ],
+            "time": "2019-12-15T19:35:24+00:00"
+        },
+        {
+            "name": "predis/predis",
+            "version": "v1.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nrk/predis.git",
+                "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nrk/predis/zipball/f0210e38881631afeafb56ab43405a92cafd9fd1",
+                "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.9"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.8"
+            },
+            "suggest": {
+                "ext-curl": "Allows access to Webdis when paired with phpiredis",
+                "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Predis\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Daniele Alessandri",
+                    "email": "[email protected]",
+                    "homepage": "http://clorophilla.net"
+                }
+            ],
+            "description": "Flexible and feature-complete Redis client for PHP and HHVM",
+            "homepage": "http://github.com/nrk/predis",
+            "keywords": [
+                "nosql",
+                "predis",
+                "redis"
+            ],
+            "time": "2016-06-16T16:22:20+00:00"
+        },
+        {
+            "name": "psr/http-message",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-message.git",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP messages",
+            "homepage": "https://github.com/php-fig/http-message",
+            "keywords": [
+                "http",
+                "http-message",
+                "psr",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "time": "2016-08-06T14:39:51+00:00"
+        },
+        {
+            "name": "psr/log",
+            "version": "1.1.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/log.git",
+                "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801",
+                "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Log\\": "Psr/Log/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for logging libraries",
+            "homepage": "https://github.com/php-fig/log",
+            "keywords": [
+                "log",
+                "psr",
+                "psr-3"
+            ],
+            "time": "2019-11-01T11:05:21+00:00"
+        },
+        {
+            "name": "ralouphie/getallheaders",
+            "version": "3.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ralouphie/getallheaders.git",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.6"
+            },
+            "require-dev": {
+                "php-coveralls/php-coveralls": "^2.1",
+                "phpunit/phpunit": "^5 || ^6.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/getallheaders.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ralph Khattar",
+                    "email": "[email protected]"
+                }
+            ],
+            "description": "A polyfill for getallheaders.",
+            "time": "2019-03-08T08:55:37+00:00"
+        },
+        {
+            "name": "symfony/polyfill-ctype",
+            "version": "v1.13.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-ctype.git",
+                "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
+                "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "suggest": {
+                "ext-ctype": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.13-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Ctype\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Gert de Pagter",
+                    "email": "[email protected]"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for ctype functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "ctype",
+                "polyfill",
+                "portable"
+            ],
+            "time": "2019-11-27T13:56:44+00:00"
+        },
+        {
+            "name": "vlucas/phpdotenv",
+            "version": "v3.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/vlucas/phpdotenv.git",
+                "reference": "1bdf24f065975594f6a117f0f1f6cabf1333b156"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/1bdf24f065975594f6a117f0f1f6cabf1333b156",
+                "reference": "1bdf24f065975594f6a117f0f1f6cabf1333b156",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.4 || ^7.0",
+                "phpoption/phpoption": "^1.5",
+                "symfony/polyfill-ctype": "^1.9"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8.35 || ^5.0 || ^6.0 || ^7.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.6-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Dotenv\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "[email protected]",
+                    "homepage": "https://gjcampbell.co.uk/"
+                },
+                {
+                    "name": "Vance Lucas",
+                    "email": "[email protected]",
+                    "homepage": "https://vancelucas.com/"
+                }
+            ],
+            "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
+            "keywords": [
+                "dotenv",
+                "env",
+                "environment"
+            ],
+            "time": "2019-09-10T21:37:39+00:00"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": {
+        "php": "7.1.0",
+        "ext-curl": "*",
+        "ext-openssl": "*",
+        "ext-json": "*",
+        "ext-bcmath": "*",
+        "ext-pdo": "*",
+        "ext-mbstring": "*"
+    },
+    "platform-dev": [],
+    "plugin-api-version": "2.1.0"
+}

+ 39 - 29
config.php

@@ -8,39 +8,49 @@
  */
 
 return [
-    /**
-     * 邮箱配置
-     */
-    'mail' => [
+    'message' => [
         /**
-         * 目前机器人邮箱账户支持谷歌邮箱、QQ邮箱以及163邮箱,程序会自动判断填入的邮箱类型并使用合适的配置。注意,QQ邮箱与163邮箱均使用
-         * 账户加授权码的方式登录,谷歌邮箱使用账户加密码的方式登录,请知悉。
+         * 邮箱配置
          */
-        'to' => env('TO'), // 用于接收通知的邮箱
-        'recipient_name' => '主人', // 收件人名字
-        'username' => env('MAIL_USERNAME'), // 机器人邮箱账户
-        'password' => env('MAIL_PASSWORD'), // 机器人邮箱密码或授权码
-        'enable' => env('MAIL_ENABLE'), // 是否启用,默认启用
-
-        // 'reply_to' => '[email protected]', // 接收回复的邮箱
-        // 'reply_to_name' => '作者', // 接收回复的人名
-
-        'host' => env('MAIL_HOST'), // 邮件 SMTP 服务器
-        'port' => env('MAIL_PORT'), // 邮件 SMTP 端口
-        'encryption' => env('MAIL_ENCRYPTION'), // 邮件加密方式
-    ],
+        'mail' => [
+            /**
+             * 目前机器人邮箱账户支持谷歌邮箱、QQ邮箱、163邮箱以及Outlook邮箱,程序会自动判断填入的邮箱类型并使用合适的配置。也可以自定义邮箱配置。
+             * 注意,QQ邮箱与163邮箱均使用账户加授权码的方式登录,谷歌邮箱使用账户加密码的方式登录,请知悉。
+             */
+            'to' => env('TO'), // 用于接收通知的邮箱
+            'recipient_name' => '主人', // 收件人名字
+            'username' => env('MAIL_USERNAME'), // 机器人邮箱账户
+            'password' => env('MAIL_PASSWORD'), // 机器人邮箱密码或授权码
+            'enable' => (int)env('MAIL_ENABLE'), // 是否启用,默认启用
+
+            // 'reply_to' => '[email protected]', // 接收回复的邮箱
+            // 'reply_to_name' => '作者', // 接收回复的人名
+
+            'host' => env('MAIL_HOST'), // 邮件 SMTP 服务器
+            'port' => env('MAIL_PORT'), // 邮件 SMTP 端口
+            'encryption' => env('MAIL_ENCRYPTION'), // 邮件加密方式
+
+            'class' => \Luolongfei\Libs\MessageServices\Mail::class,
+            'name' => '邮件',
+        ],
+
+        /**
+         * Telegram Bot
+         */
+        'telegram' => [
+            'chat_id' => env('TELEGRAM_CHAT_ID'), // 你的chat_id,通过发送“/start”给@userinfobot可以获取自己的id
+            'token' => env('TELEGRAM_BOT_TOKEN'), // Telegram Bot 的 token
+            'enable' => (int)env('TELEGRAM_BOT_ENABLE'), // 是否启用,默认不启用
+
+            'class' => \Luolongfei\Libs\MessageServices\TelegramBot::class,
+            'name' => 'Telegram Bot',
 
-    /**
-     * Telegram Bot
-     */
-    'telegram' => [
-        'chat_id' => env('TELEGRAM_CHAT_ID'), // 你的chat_id,通过发送“/start”给@userinfobot可以获取自己的id
-        'token' => env('TELEGRAM_BOT_TOKEN'), // Telegram Bot 的 token
-        'enable' => env('TELEGRAM_BOT_ENABLE') // 是否启用,默认不启用
+            'proxy' => env('TELEGRAM_PROXY'),
+        ],
     ],
 
     'locale' => 'zh', // 指定语言包,位于resources/lang/目录下
-    'notice_freq' => env('NOTICE_FREQ'), // 通知频率 0:仅当有续期操作的时候 1:每次执行
-    'verify_ssl' => env('VERIFY_SSL'), // 请求时验证 SSL 证书行为,默认不验证,防止服务器证书过期或证书颁布者信息不全导致无法发出请求
-    'debug' => env('DEBUG'),
+    'notice_freq' => (int)env('NOTICE_FREQ'), // 通知频率 0:仅当有续期操作的时候 1:每次执行
+    'verify_ssl' => (bool)env('VERIFY_SSL'), // 请求时验证 SSL 证书行为,默认不验证,防止服务器证书过期或证书颁布者信息不全导致无法发出请求
+    'debug' => (bool)env('DEBUG'),
 ];

+ 12 - 30
libs/Argv.php

@@ -9,20 +9,17 @@
 
 namespace Luolongfei\Libs;
 
-class Argv
+class Argv extends Base
 {
     /**
-     * @var Argv
+     * @var array 所有命令行参数
      */
-    protected static $instance;
+    public $allArgs = [];
 
-    /**
-     * @var array 所有命令行传参
-     */
-    public $allArgvs = [];
-
-    public function __construct()
+    protected function init()
     {
+        $this->parseAllArgs();
+
         if ($this->get('help') || $this->get('h')) {
             $desc = <<<FLL
 Description
@@ -36,20 +33,9 @@ $ php run -c=FreeNom -m=handle
 
 FLL;
             echo $desc;
-            exit(0);
-        }
-    }
 
-    /**
-     * @return Argv
-     */
-    public static function instance()
-    {
-        if (!self::$instance instanceof self) {
-            self::$instance = new self();
+            exit(0);
         }
-
-        return self::$instance;
     }
 
     /**
@@ -62,28 +48,24 @@ FLL;
      */
     public function get(string $name, string $default = '')
     {
-        if (!$this->allArgvs) {
-            $this->setAllArgvs();
-        }
-
-        return $this->allArgvs[$name] ?? $default;
+        return $this->allArgs[$name] ?? $default;
     }
 
     /**
-     * 设置命令行所有参数
+     * 解析所有命令行参数
      *
      * @return array
      */
-    public function setAllArgvs()
+    public function parseAllArgs()
     {
         global $argv;
 
         foreach ($argv as $a) { // Windows默认命令行无法正确传入使用引号括住的带空格参数,换个命令行终端就好,Linux不受影响
             if (preg_match('/^-{1,2}(?P<name>\w+)(?:=([\'"]|)(?P<val>[^\n\t\v\f\r\'"]+)\2)?$/i', $a, $m)) {
-                $this->allArgvs[$m['name']] = $m['val'] ?? true;
+                $this->allArgs[$m['name']] = $m['val'] ?? true;
             }
         }
 
-        return $this->allArgvs;
+        return $this->allArgs;
     }
 }

+ 72 - 0
libs/Base.php

@@ -0,0 +1,72 @@
+<?php
+/**
+ * @author mybsdc <[email protected]>
+ * @date 2021/10/22
+ * @time 17:13
+ */
+
+namespace Luolongfei\Libs;
+
+class Base
+{
+    /**
+     * @var array
+     */
+    private static $instances = [];
+
+    /**
+     * 添加单例
+     *
+     * @param string $className
+     * @param bool $overwrite
+     *
+     * @throws \Exception
+     */
+    public static function addInstance(string $className, bool $overwrite = false)
+    {
+        if (isset(self::$instances[$className]) && !$overwrite) {
+            throw new \InvalidArgumentException(sprintf('类 %s 的实例已存在', $className));
+        }
+
+        if (!class_exists($className)) {
+            throw new \Exception(sprintf('类 %s 不存在', $className));
+        }
+
+        $instance = new $className();
+
+        self::$instances[$className] = $instance;
+    }
+
+    /**
+     * 获取对象实例
+     *
+     * @param ...$params
+     *
+     * @return mixed
+     * @throws \Exception
+     */
+    public static function getInstance(...$params)
+    {
+        $className = isset($params[1]) && $params[1] === 'IS_MESSAGE_SERVICE' ? $params[0] : static::class;
+
+        if (!isset(self::$instances[$className])) {
+            self::addInstance($className);
+
+            // 由于自 php8 开始,is_callable 函数中如果使用类名,将不再适用于非静态方法,非静态方法必须使用对象实例,故只能将 init 从基类的
+            // 普通构造函数迁移至此处,既可以实现单次调用非静态初始化方法,又不影响继承
+            if (is_callable([self::$instances[$className], 'init'])) {
+                self::$instances[$className]->init(...$params);
+            }
+        }
+
+        return self::$instances[$className];
+    }
+
+    private function __construct()
+    {
+    }
+
+    private function __clone()
+    {
+    }
+}

+ 2 - 16
libs/Config.php

@@ -9,19 +9,14 @@
 
 namespace Luolongfei\Libs;
 
-class Config
+class Config extends Base
 {
-    /**
-     * @var Config
-     */
-    protected static $instance;
-
     /**
      * @var array 配置
      */
     protected $allConfig;
 
-    public function __construct()
+    protected function init()
     {
         $this->allConfig = require ROOT_PATH . '/config.php';
     }
@@ -62,13 +57,4 @@ class Config
 
         return $allConfig;
     }
-
-    public static function instance()
-    {
-        if (!self::$instance instanceof self) {
-            self::$instance = new self();
-        }
-
-        return self::$instance;
-    }
 }

+ 26 - 0
libs/Connector/MessageGateway.php

@@ -0,0 +1,26 @@
+<?php
+/**
+ * @author mybsdc <[email protected]>
+ * @date 2021/10/20
+ * @time 13:34
+ */
+
+namespace Luolongfei\Libs\Connector;
+
+abstract class MessageGateway implements MessageServiceInterface
+{
+    /**
+     * 根据模板生成送信内容
+     *
+     * @param array $data 数据
+     * @param string $template 模板内容
+     *
+     * @return string
+     */
+    public function genMessageContent(array $data, string $template)
+    {
+        array_unshift($data, $template);
+
+        return call_user_func_array('sprintf', $data);
+    }
+}

+ 29 - 0
libs/Connector/MessageServiceInterface.php

@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @author mybsdc <[email protected]>
+ * @date 2021/10/20
+ * @time 11:46
+ */
+
+namespace Luolongfei\Libs\Connector;
+
+/**
+ * 所有消息类需要实现的接口
+ */
+interface MessageServiceInterface
+{
+    /**
+     * 送信
+     *
+     * @param string $content
+     * @param string $subject
+     * @param integer $type 消息类型 1:普通消息 2:域名续期结果 3:无需续期,域名状态信件
+     * @param array $data
+     * @param string|null $recipient
+     * @param ...$params
+     *
+     * @return bool
+     */
+    public function send(string $content, string $subject = '', int $type = 1, array $data = [], ?string $recipient = null, ...$params);
+}

+ 4 - 18
libs/Env.php

@@ -11,35 +11,21 @@ namespace Luolongfei\Libs;
 
 use Dotenv\Dotenv;
 
-class Env
+class Env extends Base
 {
-    /**
-     * @var Env
-     */
-    protected static $instance;
-
     /**
      * @var array 环境变量值
      */
     protected $allValues;
 
-    public function __construct($fileName)
-    {
-        $this->allValues = Dotenv::create(ROOT_PATH, $fileName)->load();
-    }
-
-    public static function instance($fileName = '.env')
+    public function init($fileName = '.env')
     {
-        if (!self::$instance instanceof self) {
-            self::$instance = new self($fileName);
-        }
-
-        return self::$instance;
+        $this->allValues = file_exists($fileName) ? Dotenv::create(ROOT_PATH, $fileName)->load() : [];
     }
 
     public function get($key = '', $default = null)
     {
-        if (!strlen($key)) { // 不传key则返回所有环境变量
+        if (!strlen($key)) { // 不传 key 则返回所有环境变量
             return $this->allValues;
         }
 

+ 2 - 19
libs/Lang.php

@@ -9,19 +9,14 @@
 
 namespace Luolongfei\Libs;
 
-class Lang
+class Lang extends Base
 {
-    /**
-     * @var Lang
-     */
-    protected static $instance;
-
     /**
      * @var array
      */
     public $lang;
 
-    public function __construct()
+    public function init()
     {
         $this->lang = require sprintf('%s/lang/%s.php', RESOURCES_PATH, config('locale'));
     }
@@ -59,16 +54,4 @@ class Lang
 
         return $lang;
     }
-
-    /**
-     * @return Lang
-     */
-    public static function instance()
-    {
-        if (!self::$instance instanceof self) {
-            self::$instance = new self();
-        }
-
-        return self::$instance;
-    }
 }

+ 1 - 1
libs/Log.php

@@ -13,7 +13,7 @@ use Monolog\Logger;
 use Monolog\Handler\StreamHandler;
 use Bramus\Monolog\Formatter\ColoredLineFormatter;
 
-class Log
+class Log extends Base
 {
     /**
      * @var Logger

+ 0 - 149
libs/Mail.php

@@ -1,149 +0,0 @@
-<?php
-/**
- * 邮件
- *
- * @author mybsdc <[email protected]>
- * @date 2019/5/12
- * @time 16:38
- */
-
-namespace Luolongfei\Libs;
-
-use Luolongfei\App\Exceptions\LlfException;
-use PHPMailer\PHPMailer\PHPMailer;
-use PHPMailer\PHPMailer\Exception as MailException;
-
-class Mail
-{
-    /**
-     * @var PHPMailer
-     */
-    protected static $mail;
-
-    /**
-     * @return PHPMailer
-     * @throws MailException
-     * @throws \Exception
-     */
-    public static function mail()
-    {
-        if (!self::$mail instanceof PHPMailer) {
-            self::$mail = new PHPMailer(true);
-
-            // 邮件服务配置
-            $username = config('mail.username');
-            $password = config('mail.password');
-            if (stripos($username, '@gmail.com') !== false) {
-                $host = 'smtp.gmail.com';
-                $secure = 'tls';
-                $port = 587;
-            } else if (stripos($username, '@qq.com') !== false) {
-                $host = 'smtp.qq.com';
-                $secure = 'tls';
-                $port = 587;
-            } else if (stripos($username, '@163.com') !== false) {
-                $host = 'smtp.163.com';
-                $secure = 'ssl';
-                $port = 465;
-            } else if (stripos($username, '@vip.163.com') !== false) {
-                $host = 'smtp.vip.163.com';
-                $secure = 'ssl';
-                $port = 465;
-            } else if (stripos($username, '@outlook.com') !== false) {
-                $host = 'smtp.office365.com';
-                $secure = 'starttls';
-                $port = 587;
-            } else {
-                $host = config('mail.host');
-                $secure = config('mail.encryption');
-                $port = (int)config('mail.port');
-                if (!($host && $secure && $port)) {
-                    throw new MailException('目前支持Gmail、QQ邮箱、163邮箱以及Outlook邮箱自动识别配置,其它类型的邮箱或者自建邮箱,'
-                        . '请追加配置 .env 文件中“自定义邮箱配置”的所有相关项,否则无法使用邮件服务。');
-                }
-            }
-
-            self::$mail->SMTPDebug = config('debug') ? 2 : 0; // Debug 0:关闭 1:客户端信息 2:客户端和服务端信息
-            self::$mail->isSMTP(); // 告诉PHPMailer使用SMTP
-            self::$mail->Host = $host; // SMTP服务器
-            self::$mail->SMTPAuth = true; // 启用SMTP身份验证
-            self::$mail->Username = $username; // 账号
-            self::$mail->Password = $password; // 密码或授权码
-            self::$mail->SMTPSecure = $secure; // 将加密系统设置为使用 - ssl(不建议使用)或tls
-            self::$mail->Port = $port; // 设置SMTP端口号 - tsl使用587端口,ssl使用465端口
-            self::$mail->CharSet = 'UTF-8'; // 防止中文邮件乱码
-            self::$mail->setLanguage('zh_cn', VENDOR_PATH . '/phpmailer/phpmailer/language/'); // 设置语言
-            self::$mail->setFrom($username, 'im robot'); // 发件人
-        }
-
-        return self::$mail;
-    }
-
-    /**
-     * 发送邮件
-     *
-     * @param string $subject 标题
-     * @param string | array $content 正文
-     * @param string $to 收件人,选传
-     * @param string $template 模板,选传
-     *
-     * @return bool
-     * @throws \Exception
-     */
-    public static function send($subject, $content, $to = '', $template = '')
-    {
-        if (config('mail.enable') === false) {
-            system_log('由于没有启用邮件功能,故本次不通过邮件送信。');
-
-            return false;
-        }
-
-        $to = $to ?: config('mail.to');
-        if (!$to) {
-            throw new LlfException(env('ON_GITHUB_ACTIONS') ? 34520011 : 34520012);
-        }
-
-        self::mail()->addAddress($to, config('mail.recipient_name', '主人')); // 添加收件人,参数2选填
-        self::mail()->addReplyTo(config('mail.reply_to', '[email protected]'), config('mail.reply_to_name', '作者')); // 备用回复地址,收到的回复的邮件将被发到此地址
-
-        /**
-         * 抄送和密送都是添加收件人,抄送方式下,被抄送者知道除被密送者外的所有的收件人,密送方式下,
-         * 被密送者知道所有的被抄送者,但不知道其它的被密送者。
-         * 抄送好比@,密送好比私信。
-         */
-//        self::mail()->addCC('[email protected]'); // 抄送
-//        self::mail()->addBCC('[email protected]'); // 密送
-
-        // 添加附件,参数2选填
-//        self::mail()->addAttachment('README.md', '说明.txt');
-
-        // 内容
-        self::mail()->Subject = $subject; // 标题
-
-        /**
-         * 正文
-         * 使用html文件内容作为正文,其中的图片将被base64编码,另确保html样式为内联形式,且某些样式可能需要!important方能正常显示,
-         * msgHTML方法的第二个参数指定html内容中图片的路径,在转换时会拼接html中图片的相对路径得到完整的路径,最右侧无需“/”,PHPMailer
-         * 源码里有加。css中的背景图片不会被转换,这是PHPMailer已知问题,建议外链。
-         * 此处也可替换为:
-         * self::mail()->isHTML(true); // 设为html格式
-         * self::mail()->Body = '正文'; // 支持html
-         * self::mail()->AltBody = 'This is an HTML-only message. To view it, activate HTML in your email application.'; // 纯文本消息正文。不支持html预览的邮件客户端将显示此预览消息,其它情况将显示正常的body
-         */
-        $template = file_get_contents(RESOURCES_PATH . '/mail/' . ($template ?: 'default') . '.html');
-        if (is_array($content)) {
-            array_unshift($content, $template);
-            $message = call_user_func_array('sprintf', $content);
-        } else if (is_string($content)) {
-            $message = $content;
-        } else {
-            throw new MailException('邮件内容格式错误,仅支持传入数组或字符串。');
-        }
-
-        self::mail()->msgHTML($message, APP_PATH . '/mail');
-
-        if (!self::mail()->send()) throw new MailException(self::mail()->ErrorInfo);
-
-        return true;
-    }
-}

+ 43 - 0
libs/Message.php

@@ -0,0 +1,43 @@
+<?php
+/**
+ * @author mybsdc <[email protected]>
+ * @date 2021/10/20
+ * @time 13:46
+ */
+
+namespace Luolongfei\Libs;
+
+use Luolongfei\Libs\Connector\MessageServiceInterface;
+
+/**
+ * Class Message
+ *
+ * @method bool send(string $content, string $subject = '', int $type = 1, array $data = [], ?string $recipient = null, ...$params) 送信
+ */
+abstract class Message extends Base
+{
+    /**
+     * @param $method
+     * @param $params
+     *
+     * @throws \Exception
+     */
+    public static function __callStatic($method, $params)
+    {
+        foreach (config('message') as $conf) {
+            if ($conf['enable'] !== 1) {
+                system_log(sprintf('由于没有启用「%s」功能,故本次不通过「%s」送信。', $conf['name'], $conf['name']));
+
+                continue;
+            }
+
+            $serviceInstance = self::getInstance($conf['class'], 'IS_MESSAGE_SERVICE');
+
+            if (!$serviceInstance instanceof MessageServiceInterface) {
+                throw new \Exception(sprintf('消息服务类 %s 必须继承并实现 MessageServiceInterface 接口', $conf['class']));
+            }
+
+            $serviceInstance->$method(...$params);
+        }
+    }
+}

+ 236 - 0
libs/MessageServices/Mail.php

@@ -0,0 +1,236 @@
+<?php
+/**
+ * 邮件
+ *
+ * @author mybsdc <[email protected]>
+ * @date 2019/5/12
+ * @time 16:38
+ */
+
+namespace Luolongfei\Libs\MessageServices;
+
+use Luolongfei\App\Exceptions\LlfException;
+use Luolongfei\Libs\Log;
+use PHPMailer\PHPMailer\PHPMailer;
+use PHPMailer\PHPMailer\Exception as MailException;
+use Luolongfei\Libs\Connector\MessageGateway;
+
+class Mail extends MessageGateway
+{
+    /**
+     * @var PHPMailer
+     */
+    private $phpMailerInstance;
+
+    /**
+     * @throws MailException
+     */
+    public function __construct()
+    {
+        $this->phpMailerInstance = new PHPMailer(true);
+
+        $this->init();
+    }
+
+    /**
+     * 初始化邮箱配置
+     *
+     * @throws MailException
+     */
+    protected function init()
+    {
+        $username = config('message.mail.username');
+        $password = config('message.mail.password');
+
+        list($host, $secure, $port) = $this->getBasicMailConf($username);
+
+        $this->phpMailerInstance->SMTPDebug = config('debug') ? 2 : 0; // Debug 0:关闭 1:客户端信息 2:客户端和服务端信息
+        $this->phpMailerInstance->isSMTP(); // 告诉 PHPMailer 使用 SMTP
+        $this->phpMailerInstance->Host = $host; // SMTP 服务器
+        $this->phpMailerInstance->SMTPAuth = true; // 启用 SMTP 身份验证
+        $this->phpMailerInstance->Username = $username; // 账号
+        $this->phpMailerInstance->Password = $password; // 密码或授权码
+        $this->phpMailerInstance->SMTPSecure = $secure; // 将加密系统设置为使用 - ssl(不建议使用)或 tls
+        $this->phpMailerInstance->Port = $port; // 设置 SMTP 端口号 - tsl 使用 587 端口,ssl 使用 465 端口
+        $this->phpMailerInstance->CharSet = 'UTF-8'; // 防止中文邮件乱码
+        $this->phpMailerInstance->setLanguage('zh_cn', VENDOR_PATH . '/phpmailer/phpmailer/language/'); // 设置语言
+        $this->phpMailerInstance->setFrom($username, 'Im robot'); // 发件人
+    }
+
+    /**
+     * 获取邮箱基本配置
+     *
+     * @param string $username
+     *
+     * @return array
+     * @throws MailException
+     */
+    public function getBasicMailConf(string $username)
+    {
+        if (stripos($username, '@gmail.com') !== false) {
+            $host = 'smtp.gmail.com';
+            $secure = 'tls';
+            $port = 587;
+        } else if (stripos($username, '@qq.com') !== false) {
+            $host = 'smtp.qq.com';
+            $secure = 'tls';
+            $port = 587;
+        } else if (stripos($username, '@163.com') !== false) {
+            $host = 'smtp.163.com';
+            $secure = 'ssl';
+            $port = 465;
+        } else if (stripos($username, '@vip.163.com') !== false) {
+            $host = 'smtp.vip.163.com';
+            $secure = 'ssl';
+            $port = 465;
+        } else if (stripos($username, '@outlook.com') !== false) {
+            $host = 'smtp.office365.com';
+            $secure = 'starttls';
+            $port = 587;
+        } else {
+            $host = config('message.mail.host');
+            $secure = config('message.mail.encryption');
+            $port = (int)config('message.mail.port');
+            if (!($host && $secure && $port)) {
+                throw new MailException('目前支持Gmail、QQ邮箱、163邮箱以及Outlook邮箱自动识别配置,其它类型的邮箱或者自建邮箱,'
+                    . '请在 .env 文件中追加“自定义邮箱配置”的所有相关项,否则无法使用邮件服务。');
+            }
+        }
+
+        return [$host, $secure, $port];
+    }
+
+    /**
+     * 生成域名 html
+     *
+     * @param array $domains
+     *
+     * @return string
+     */
+    public function genDomainsHtml(array $domains)
+    {
+        $domainsHtml = '';
+
+        foreach ($domains as $domain) {
+            $domainsHtml .= sprintf('<a href="http://%s" rel="noopener" target="_blank">%s</a>', $domain, $domain);
+        }
+
+        return $domainsHtml;
+    }
+
+    /**
+     * 生成域名状态 html
+     *
+     * @param array $domainStatus
+     *
+     * @return string
+     */
+    public function genDomainStatusHtml(array $domainStatus)
+    {
+        $domainStatusHtml = '';
+
+        foreach ($domainStatus as $domain => $daysLeft) {
+            $domainStatusHtml .= sprintf('<a href="http://%s" rel="noopener" target="_blank">%s</a>还有 <span style="font-weight: bold; font-size: 16px;">%d</span> 天到期,', $domain, $domain, $daysLeft);
+        }
+
+        $domainStatusHtml = rtrim($domainStatusHtml, ',') . "。";
+
+        return $domainStatusHtml;
+    }
+
+    /**
+     * 送信
+     *
+     * @param string $content
+     * @param string $subject
+     * @param integer $type
+     * @param array $data
+     * @param string|null $recipient
+     * @param mixed ...$params
+     *
+     * @return bool
+     * @throws LlfException
+     * @throws MailException
+     */
+    public function send(string $content, string $subject = '', int $type = 1, array $data = [], ?string $recipient = null, ...$params)
+    {
+        $recipient = $recipient ?: config('message.mail.to');
+
+        if (!$recipient) {
+            throw new LlfException(34520012);
+        }
+
+        if ($content === '' && empty($data)) {
+            throw new \Exception(lang('error_msg.100002'));
+        }
+
+        if ($content !== '' && $data) {
+            throw new \Exception(lang('error_msg.100004'));
+        }
+
+        $this->phpMailerInstance->addAddress($recipient, config('message.mail.recipient_name', '主人')); // 添加收件人,参数2选填
+        $this->phpMailerInstance->addReplyTo(config('message.mail.reply_to', '[email protected]'), config('message.mail.reply_to_name', '作者')); // 备用回复地址,收到的回复的邮件将被发到此地址
+
+        /**
+         * 抄送和密送都是添加收件人,抄送方式下,被抄送者知道除被密送者外的所有的收件人,密送方式下,
+         * 被密送者知道所有的被抄送者,但不知道其它的被密送者。
+         * 抄送好比@,密送好比私信。
+         */
+//        $this->phpMailerInstance->addCC('[email protected]'); // 抄送
+//        $this->phpMailerInstance->addBCC('[email protected]'); // 密送
+
+        // 添加附件,参数2选填
+//        $this->phpMailerInstance->addAttachment('README.md', '说明.txt');
+
+        // 标题
+        $subject = $subject === '' ? mb_substr($content, 0, 12) . '...' : $subject;
+        $this->phpMailerInstance->Subject = $subject;
+
+        /**
+         * 正文
+         * 使用 html 文件内容作为正文,其中的图片将被 base64 编码,另确保 html 样式为内联形式,且某些样式可能需要 !important 方能正常显示,
+         * msgHTML 方法的第二个参数指定 html 内容中图片的路径,在转换时会拼接 html 中图片的相对路径得到完整的路径,最右侧无需“/”,PHPMailer
+         * 源码里有加。 css 中的背景图片不会被转换,这是 PHPMailer 已知问题,建议外链
+         * 此处也可替换为:
+         * $this->phpMailerInstance->isHTML(true); // 设为html格式
+         * $this->phpMailerInstance->Body = '正文'; // 支持html
+         * $this->phpMailerInstance->AltBody = 'This is an HTML-only message. To view it, activate HTML in your email application.'; // 纯文本消息正文。不支持html预览的邮件客户端将显示此预览消息,其它情况将显示正常的body
+         */
+        if ($type === 1) {
+            $template = file_get_contents(RESOURCES_PATH . '/mail/notice.html');
+            $message = sprintf($template, $content);
+        } else if ($type === 2) {
+            $template = file_get_contents(RESOURCES_PATH . '/mail/successful_renewal.html');
+            $realData = [
+                $data['username'],
+                $data['renewalSuccessArr'] ? sprintf('续期成功:%s<br>', $this->genDomainsHtml($data['renewalSuccessArr'])) : '',
+                $data['renewalFailuresArr'] ? sprintf('续期出错:%s<br>', $this->genDomainsHtml($data['renewalFailuresArr'])) : '',
+                $this->genDomainStatusHtml($data['domainStatusArr'])
+            ];
+            $message = $this->genMessageContent($realData, $template);
+        } else if ($type === 3) {
+            $template = file_get_contents(RESOURCES_PATH . '/mail/no_renewal_required.html');
+            $realData = [
+                $data['username'],
+                $this->genDomainStatusHtml($data['domainStatusArr'])
+            ];
+            $message = $this->genMessageContent($realData, $template);
+        } else {
+            throw new \Exception(lang('error_msg.100003'));
+        }
+
+        $this->phpMailerInstance->msgHTML($message, APP_PATH . '/mail');
+
+        try {
+            if (!$this->phpMailerInstance->send()) {
+                throw new MailException($this->phpMailerInstance->ErrorInfo);
+            }
+
+            return true;
+        } catch (\Exception $e) {
+            system_log('邮件发送失败:' . $e->getMessage());
+
+            return false;
+        }
+    }
+}

+ 288 - 0
libs/MessageServices/TelegramBot.php

@@ -0,0 +1,288 @@
+<?php
+/**
+ * Telegram Bot
+ *
+ * @author mybsdc <[email protected]>
+ * @date 2020/2/3
+ * @time 15:23
+ */
+
+namespace Luolongfei\Libs\MessageServices;
+
+use GuzzleHttp\Client;
+use Luolongfei\Libs\Log;
+use Luolongfei\Libs\Connector\MessageGateway;
+
+class TelegramBot extends MessageGateway
+{
+    const TIMEOUT = 33;
+
+    /**
+     * @var string chat_id
+     */
+    protected $chatID;
+
+    /**
+     * @var string 机器人令牌
+     */
+    protected $token;
+
+    /**
+     * @var Client
+     */
+    protected $client;
+
+    public function __construct()
+    {
+        $this->chatID = config('message.telegram.chat_id');
+        $this->token = config('message.telegram.token');
+
+        $this->client = new Client([
+            'headers' => [
+                'Content-Type' => 'application/x-www-form-urlencoded'
+            ],
+            'cookies' => false,
+            'timeout' => self::TIMEOUT,
+            'verify' => config('verify_ssl'),
+            'debug' => config('debug'),
+            'proxy' => config('message.telegram.proxy') ?: null,
+        ]);
+    }
+
+    /**
+     * 生成域名状态 MarkDown 完整文本
+     *
+     * @param string $username
+     * @param array $domainStatus
+     *
+     * @return string
+     */
+    public function genDomainStatusFullMarkDownText(string $username, array $domainStatus)
+    {
+        $markDownText = sprintf("我刚刚帮小主看了一下,账户 [%s](#) 今天并没有需要续期的域名。所有域名情况如下:\n\n", $username);
+
+        $markDownText .= $this->genDomainStatusMarkDownText($domainStatus);
+
+        $markDownText .= $this->getMarkDownFooter();
+
+        return $markDownText;
+    }
+
+    /**
+     * 获取 MarkDown 页脚
+     *
+     * @return string
+     */
+    public function getMarkDownFooter()
+    {
+        $footer = '';
+
+        $footer .= "\n更多信息可以参考 [Freenom官网](https://my.freenom.com/domains.php?a=renewals) 哦~";
+        $footer .= "\n\n(如果你不想每次执行都收到推送,请将 .env 中 NOTICE_FREQ 的值设为 0,使程序只在有续期操作时才推送)";
+
+        return $footer;
+    }
+
+    /**
+     * 生成域名状态 MarkDown 文本
+     *
+     * @param array $domainStatus
+     *
+     * @return string
+     */
+    public function genDomainStatusMarkDownText(array $domainStatus)
+    {
+        $domainStatusMarkDownText = '';
+
+        foreach ($domainStatus as $domain => $daysLeft) {
+            $domainStatusMarkDownText .= sprintf('[%s](http://%s) 还有 *%d* 天到期,', $domain, $domain, $daysLeft);
+        }
+
+        $domainStatusMarkDownText = rtrim($domainStatusMarkDownText, ',') . "。\n";
+
+        return $domainStatusMarkDownText;
+    }
+
+    public function genDomainRenewalResultsMarkDownText(string $username, array $renewalSuccessArr, array $renewalFailuresArr, array $domainStatus)
+    {
+        $text = sprintf("账户 [%s](#) 这次续期的结果如下\n\n", $username);
+
+        if ($renewalSuccessArr) {
+            $text .= '续期成功:';
+            $text .= $this->genDomainsMarkDownText($renewalSuccessArr);
+        }
+
+        if ($renewalFailuresArr) {
+            $text .= '续期出错:';
+            $text .= $this->genDomainsMarkDownText($renewalFailuresArr);
+        }
+
+        $text .= "\n今次无需续期的域名及其剩余天数如下所示:\n\n";
+        $text .= $this->genDomainStatusMarkDownText($domainStatus);
+
+        $text .= $this->getMarkDownFooter();
+
+        return $text;
+    }
+
+    /**
+     * 生成域名 MarkDown 文本
+     *
+     * @param array $domains
+     *
+     * @return string
+     */
+    public function genDomainsMarkDownText(array $domains)
+    {
+        $domainsMarkDownText = '';
+
+        foreach ($domains as $domain) {
+            $domainsMarkDownText .= sprintf("[%s](http://%s) ", $domain, $domain);
+        }
+
+        $domainsMarkDownText = trim($domainsMarkDownText, ' ') . "\n";
+
+        return $domainsMarkDownText;
+    }
+
+    /**
+     * 获取 MarkDown 表格映射的原始数组
+     *
+     * @param string $markDownTable
+     *
+     * @return array
+     */
+    public function getMarkDownRawArr(string $markDownTable)
+    {
+        $rawArr = [];
+        $markDownTableArr = preg_split("/(?:\n|\r\n)+/", $markDownTable);
+
+        foreach ($markDownTableArr as $row) {
+            $row = (string)preg_replace('/^\s+|\s+$|\s+|(?<=\|)\s+|\s+(?=\|)/', '', $row);
+
+            if ($row === '') {
+                continue;
+            }
+
+            $rowArr = explode('|', trim($row, '|'));
+            $rawArr[] = $rowArr;
+        }
+
+        return $rawArr;
+    }
+
+    /**
+     * 送信
+     *
+     * @param string $content 支持 markdown 语法,但记得对非标记部分进行转义
+     * @param string $subject
+     * @param integer $type
+     * @param array $data
+     * @param string|null $recipient 可单独指定 chat_id 参数
+     * @param mixed ...$params
+     *
+     * @desc
+     * 注意对 markdown 标记占用的字符进行转义,否则无法正确发送,根据官方说明,以下字符如果不想被 Telegram Bot 识别为 markdown 标记,
+     * 应转义后传入,官方说明如下:
+     * In all other places characters '_‘, ’*‘, ’[‘, ’]‘, ’(‘, ’)‘, ’~‘, ’`‘, ’>‘, ’#‘, ’+‘, ’-‘, ’=‘, ’|‘,
+     * ’{‘, ’}‘, ’.‘, ’!‘ must be escaped with the preceding character ’\'.
+     * 如果你不转义,且恰好又不是正确的markdown语法,那 Telegram Bot 就只有报错了您勒
+     *
+     * 官方markdown语法示例:
+     * *bold \*text*
+     * _italic \*text_
+     * __underline__
+     * ~strikethrough~
+     * *bold _italic bold ~italic bold strikethrough~ __underline italic bold___ bold*
+     * [inline URL](http://www.example.com/)
+     * [inline mention of a user](tg://user?id=123456789)
+     * `inline fixed-width code`
+     * ```
+     * pre-formatted fixed-width code block
+     * ```
+     * ```python
+     * pre-formatted fixed-width code block written in the Python programming language
+     * ```
+     * 需要注意的是,普通 markdown 语法中加粗字体使用的是“**正文**”的形式,但是 Telegram Bot 中是“*加粗我*”的形式
+     * 更多相关信息请参考官网:https://core.telegram.org/bots/api#sendmessage
+     * 另外我干掉了“_”、“~”、“-”、“.”和“>”关键字,分别对应斜体、删除线、无序列表、有序列表和引用符号,因为这几个比较容易在正常文本里出现,而
+     * 我又不想每次都手动转义传入,故做了自动转义处理
+     *
+     * 由于 telegram bot 的 markdown 语法不支持表格(https://core.telegram.org/bots/api#markdownv2-style),故表格部分由我自行解析
+     * 为字符形式的表格,坐等 telegram bot 支持表格
+     *
+     * @return bool
+     */
+    public function send(string $content, string $subject = '', int $type = 1, array $data = [], ?string $recipient = null, ...$params)
+    {
+        if ($content === '' && empty($data)) {
+            throw new \Exception(lang('error_msg.100002'));
+        }
+
+        if ($content !== '' && $data) {
+            throw new \Exception(lang('error_msg.100004'));
+        }
+
+        if ($type === 1) {
+            // Do nothing
+        } else if ($type === 2) {
+            $content = $this->genDomainRenewalResultsMarkDownText($data['username'], $data['renewalSuccessArr'], $data['renewalFailuresArr'], $data['domainStatusArr']);
+        } else if ($type === 3) {
+            $content = $this->genDomainStatusFullMarkDownText($data['username'], $data['domainStatusArr']);
+        } else {
+            throw new \Exception(lang('error_msg.100003'));
+        }
+
+        $isMarkdown = true;
+
+        // 使用可变参数控制 telegram 送信类型,一般不会用到
+        if ($params && isset($params[1]) && $params[0] === 'TG') {
+            $isMarkdown = $params[1];
+        }
+
+        if ($subject !== '') {
+            $content = $subject . "\n\n" . $content;
+        }
+
+        if ($isMarkdown) {
+            // 这几个比较容易在正常文本里出现,而我不想每次都手动转义传入,所以直接干掉了
+            $content = preg_replace('/([.>~_-])/i', '\\\\$1', $content);
+
+            // 转义非链接格式的 [] 以及 ()
+            $content = preg_replace_callback_array(
+                [
+                    '/(?<!\\\\)\[(?P<brackets>.*?)(?!\]\()(?<!\\\\)\]/' => function ($match) {
+                        return '\\[' . $match['brackets'] . '\\]';
+                    },
+                    '/(?<!\\\\)(?<!\])\((?P<parentheses>.*?)(?<!\\\\)\)/' => function ($match) {
+                        return '\\(' . $match['parentheses'] . '\\)';
+                    }
+                ],
+                $content
+            );
+        }
+
+        try {
+            $resp = $this->client->post(
+                sprintf('https://api.telegram.org/bot%s/sendMessage', $this->token),
+                [
+                    'form_params' => [
+                        'chat_id' => $recipient ? $recipient : $this->chatID,
+                        'text' => $content,
+                        'parse_mode' => $isMarkdown ? 'MarkdownV2' : 'HTML',
+                        'disable_web_page_preview' => true,
+                        'disable_notification' => false
+                    ],
+                ]
+            );
+
+            $resp = json_decode((string)$resp->getBody(), true);
+
+            return $resp['ok'] ?? false;
+        } catch (\Exception $e) {
+            system_log('Telegram 消息发送失败:<red>' . $e->getMessage() . '</red>');
+
+            return false;
+        }
+    }
+}

+ 2 - 19
libs/PhpColor.php

@@ -13,19 +13,14 @@ namespace Luolongfei\Libs;
 
 use Colors\Color;
 
-class PhpColor
+class PhpColor extends Base
 {
-    /**
-     * @var PhpColor
-     */
-    protected static $instance;
-
     /**
      * @var Color
      */
     protected $colorInstance;
 
-    public function __construct()
+    public function init()
     {
         $this->colorInstance = new Color();
 
@@ -35,18 +30,6 @@ class PhpColor
         ]);
     }
 
-    /**
-     * @return PhpColor
-     */
-    public static function instance()
-    {
-        if (!self::$instance instanceof self) {
-            self::$instance = new self();
-        }
-
-        return self::$instance;
-    }
-
     /**
      * @return Color
      */

+ 0 - 128
libs/TelegramBot.php

@@ -1,128 +0,0 @@
-<?php
-/**
- * Telegram Bot
- *
- * @author mybsdc <[email protected]>
- * @date 2020/2/3
- * @time 15:23
- */
-
-namespace Luolongfei\Libs;
-
-use GuzzleHttp\Client;
-
-class TelegramBot
-{
-    const TIMEOUT = 34.52;
-
-    /**
-     * @var TelegramBot
-     */
-    protected static $instance;
-
-    /**
-     * @var string chat_id
-     */
-    protected $chatID;
-
-    /**
-     * @var string TelegramBot token
-     */
-    protected $token;
-
-    /**
-     * @var Client
-     */
-    protected $client;
-
-    public function __construct()
-    {
-        $this->chatID = config('telegram.chat_id');
-        $this->token = config('telegram.token');
-
-        $this->client = new Client([
-            'headers' => [
-                'Content-Type' => 'application/x-www-form-urlencoded'
-            ],
-            'cookies' => false,
-            'timeout' => self::TIMEOUT,
-            'verify' => config('verify_ssl'),
-//            'http_errors' => false,
-            'debug' => config('debug')
-        ]);
-    }
-
-    protected static function instance()
-    {
-        if (!self::$instance instanceof self) {
-            self::$instance = new self();
-        }
-
-        return self::$instance;
-    }
-
-    /**
-     * 发送消息
-     *
-     * @param string $content 支持markdown语法,但记得对非标记部分进行转义
-     * @param string $chatID 可单独指定chat_id参数
-     * @param bool $isMarkdown 默认内容为Markdown格式,传否则为Html格式
-     * @desc 注意对markdown标记占用的字符进行转义,否则无法正确发送,根据官方说明,以下字符如果不想被 Telegram Bot 识别为markdown标记,
-     * 应转义后传入,官方说明如下:
-     * In all other places characters '_‘, ’*‘, ’[‘, ’]‘, ’(‘, ’)‘, ’~‘, ’`‘, ’>‘, ’#‘, ’+‘, ’-‘, ’=‘, ’|‘,
-     * ’{‘, ’}‘, ’.‘, ’!‘ must be escaped with the preceding character ’\'.
-     * 如果你不转义,且恰好又不是正确的markdown语法,那 Telegram Bot 就只有报错了您勒
-     *
-     * 官方markdown语法示例:
-     * *bold \*text*
-     * _italic \*text_
-     * __underline__
-     * ~strikethrough~
-     * *bold _italic bold ~italic bold strikethrough~ __underline italic bold___ bold*
-     * [inline URL](http://www.example.com/)
-     * [inline mention of a user](tg://user?id=123456789)
-     * `inline fixed-width code`
-     * ```
-     * pre-formatted fixed-width code block
-     * ```
-     * ```python
-     * pre-formatted fixed-width code block written in the Python programming language
-     * ```
-     * 需要注意的是,普通markdown语法中加粗字体使用的是“**正文**”的形式,但是 Telegram Bot 中是“*加粗我呀*”的形式,更多相关信息请
-     * 参考官网:https://core.telegram.org/bots/api#sendmessage
-     * 另外我干掉了“_”、“~”、“-”、“.”和“>”关键字,分别对应斜体、删除线、无序列表、有序列表和引用符号,这几个我可能用不上:)
-     *
-     * @return bool
-     */
-    public static function send(string $content, $chatID = '', $isMarkdown = true)
-    {
-        if (config('telegram.enable') === false) {
-            system_log('由于没有启用 Telegram Bot 功能,故本次不通过 Telegram Bot 送信。');
-
-            return false;
-        }
-
-        if ($isMarkdown) {
-            // 这几个我可能用不上的markdown关键字我就直接干掉了
-            $content = preg_replace('/([.>~_-])/i', '\\\\$1', $content);
-        }
-
-        $telegramBot = self::instance();
-
-        $response = $telegramBot->client->post(
-            sprintf('https://api.telegram.org/bot%s/sendMessage', $telegramBot->token),
-            [
-                'form_params' => [
-                    'chat_id' => $chatID ? $chatID : $telegramBot->chatID,
-                    'text' => $content,
-                    'parse_mode' => $isMarkdown ? 'MarkdownV2' : 'HTML',
-                    'disable_web_page_preview' => true,
-                    'disable_notification' => false
-                ],
-            ]
-        );
-        $rp = json_decode((string)$response->getBody(), true);
-
-        return $rp['ok'] ?? false;
-    }
-}

+ 9 - 3
resources/lang/zh.php

@@ -10,16 +10,22 @@
 return [
     'exception_msg' => [
         '34520001' => '检测到你尚未配置 freenom 账户信息,请修改 .env 文件中与账户相关的项,否则程序无法正常运作',
-        '34520002' => '登录 freenom 出错,未取得正确的 cookie 值,请检查账户和密码是否正确',
+        '34520002' => '登录 freenom 出错。错误信息:%s',
         '34520003' => '域名数据匹配失败,可能是你暂时没有域名或者页面改版导致正则失效,请及时联系作者',
         '34520004' => 'token 匹配失败,可能是页面改版导致正则失效,请及时联系作者',
         '34520005' => 'putenv() 函数被禁用,无法写入环境变量导致程序无法正常运作,解决方案参考:https://github.com/luolongfei/freenom/issues/22',
         '34520006' => sprintf('不支持 php7 以下的版本,当前版本为%s,请升级到 php7 以上', PHP_VERSION),
         '34520007' => sprintf('已自动在%s目录下生成 .env 配置文件,请将配置文件中的各项内容修改为你自己的', ROOT_PATH),
         '34520008' => sprintf('请将%s目录下的 .env.example 文件复制为 .env 文件,并将 .env 文件中的各项内容修改为你自己的', ROOT_PATH),
-        '34520009' => '登录 freenom 失败,请检查账户和密码是否正确',
+        '34520009' => '获取域名状态页面出错,可能是未登录或者登录失效,请重试。',
         '34520010' => '缺少 curl 模块,无法发送请求,请检查你的 php 环境并在编译时带上 curl 模块',
-        '34520011' => '你尚未配置收信邮箱,可能无法收到通知邮件。请在你 Fork 的本仓库下的 Settings > Secrets 画面追加一个名为 TO 的 secret 变量,变量对应的值为你最常用的邮箱地址,用于接收机器人邮箱发出的域名相关邮件',
         '34520012' => '你尚未配置收信邮箱,可能无法收到通知邮件。请将 .env 文件中的 TO 对应的值改为你最常用的邮箱地址,用于接收机器人邮箱发出的域名相关邮件',
+        '34520013' => '获取域名状态页面出错,错误信息:%s',
+    ],
+    'error_msg' => [
+        '100001' => '未能取得名为 WHMCSZH5eHTGhfvzP 的 cookie 值,故本次登录无效,请检查你的账户或密码是否正确。',
+        '100002' => '不允许发送空内容邮件',
+        '100003' => '非法消息类型',
+        '100004' => '不允许传入的 $content 与 $data 参数同时非空',
     ],
 ];

+ 367 - 326
resources/mail/LlfException.html → resources/mail/no_renewal_required.html

@@ -1,326 +1,367 @@
-<!DOCTYPE html>
-<html lang="zh">
-<head>
-    <meta charset="UTF-8">
-    <title>邮件通知</title>
-    <style>
-        .mmsgLetter {
-            width: 580px;
-            margin: 0 auto;
-            padding: 10px;
-            color: #333;
-            background: #fff;
-            border: 0px solid #aaa;
-            border-radius: 5px;
-            -webkit-box-shadow: 3px 3px 10px #999 !important;
-            -moz-box-shadow: 3px 3px 10px #999 !important;
-            box-shadow: 3px 3px 10px #999 !important;
-            font-family: Verdana, sans-serif;
-        }
-
-        .mmsgLetter a:link,
-        .mmsgLetter a:visited {
-            color: #407700;
-        }
-
-        .mmsgLetterContent {
-            text-align: left;
-            padding: 30px;
-            font-size: 14px;
-            line-height: 1.5;
-            /*background: url('images/ting.jpg') no-repeat top right;*/
-        }
-
-        .mmsgLetterContent h3 {
-            color: #000;
-            font-size: 20px;
-            font-weight: bold;
-            margin: 20px 0 20px;
-            border-top: 2px solid #eee;
-            padding: 20px 0 0 0;
-            font-family: "微软雅黑", "黑体", "Lucida Grande", Verdana, sans-serif;
-        }
-
-        .mmsgLetterContent p {
-            margin: 20px 0;
-            padding: 0;
-        }
-
-        .mmsgLetterContent .salutation {
-            font-weight: bold;
-        }
-
-        .mmsgLetterContent .mmsgMoreInfo {
-        }
-
-        .mmsgLetterContent a.mmsgButton {
-            display: block;
-            float: left;
-            height: 40px;
-            text-decoration: none;
-            text-align: center;
-            cursor: pointer;
-        }
-
-        .mmsgLetterContent a.mmsgButton span {
-            display: block;
-            float: left;
-            padding: 0 25px;
-            height: 40px;
-            line-height: 36px;
-            font-size: 14px;
-            font-weight: bold;
-            color: #fff;
-            text-shadow: 1px 0 0 #235e00;
-        }
-
-        .mmsgLetterContent a.mmsgButton:link,
-        .mmsgLetterContent a.mmsgButton:visited {
-            background: #338702 url('images/mmsgletter_2_btn.png') no-repeat right -40px;
-        }
-
-        .mmsgLetterContent a.mmsgButton:link span,
-        .mmsgLetterContent a.mmsgButton:visited span {
-            background: url('images/mmsgletter_2_btn.png') no-repeat 0 0;
-        }
-
-        .mmsgLetterContent a.mmsgButton:hover,
-        .mmsgLetterContent a.mmsgButton:active {
-            background: #338702 url('images/mmsgletter_2_btn.png') no-repeat right -120px;
-        }
-
-        .mmsgLetterContent a.mmsgButton:hover span,
-        .mmsgLetterContent a.mmsgButton:active span {
-            background: url('images/mmsgletter_2_btn.png') no-repeat 0 -80px;
-        }
-
-        .mmsgLetterInscribe {
-            padding: 40px 0 0;
-        }
-
-        .mmsgLetterInscribe .mmsgAvatar {
-            float: left;
-        }
-
-        .mmsgLetterInscribe .mmsgName {
-            margin: 0 0 10px;
-        }
-
-        .mmsgLetterInscribe .mmsgSender {
-            margin: 0 0 0 54px;
-        }
-
-        .mmsgLetterInscribe .mmsgInfo {
-            font-size: 12px;
-            margin: 0;
-            line-height: 1.2;
-        }
-
-        .mmsgLetterHeader {
-            height: 23px;
-            /*background: url('images/mmsgletter_2_bg_topline.png') repeat-x 0 0;*/
-            background: 7px 0 repeat-x #FFF;
-            background-image: -webkit-repeating-linear-gradient(135deg, #4882CE, #4882CE 20px, #FFF 20px, #FFF 35px, #EB1B2E 35px, #EB1B2E 55px, #FFF 55px, #FFF 70px);
-            background-image: repeating-linear-gradient(-45deg, #4882CE, #4882CE 20px, #FFF 20px, #FFF 35px, #EB1B2E 35px, #EB1B2E 55px, #FFF 55px, #FFF 70px);
-            background-size: 110px 10px;
-        }
-
-        .mmsgLetterFooter {
-            margin: 16px;
-            text-align: center;
-            font-size: 12px;
-            color: #888;
-            text-shadow: 1px 0px 0px #eee;
-        }
-
-        .mmsgLetterClr {
-            clear: both;
-            overflow: hidden;
-            height: 1px;
-        }
-
-        .mmsgLetterUser {
-            padding: 10px 0;
-        }
-
-        .mmsgLetterUserItem {
-            padding: 0 0 20px 0;
-        }
-
-        .mmsgLetterUserAvatar {
-            height: 40px;
-            border: 1px solid #ccc;
-            padding: 2px;
-            display: block;
-            float: left;
-        }
-
-        .mmsgLetterUserAvatar img {
-            width: 40px;
-            height: 40px;
-        }
-
-        .mmsgLetterInfo {
-            margin-left: 48px;
-        }
-
-        .mmsgLetterName {
-            display: block;
-            color: #5fa207;
-            font-weight: bold;
-            margin-left: 10px;
-        }
-
-        .mmsgLetterDesc {
-            font-size: 12px;
-            float: left;
-            height: 43px;
-            background: url('images/mmsgletter_chat_right.gif') no-repeat right top;
-        }
-
-        .mmsgLetterDesc div {
-            white-space: nowrap;
-            float: left;
-            height: 43px;
-            padding: 0 20px;
-            line-height: 40px;
-            background: url('images/mmsgletter_chat_left.gif') no-repeat left top;
-        }
-
-        .mmsgLetterUser {
-        }
-
-        .mmsgLetterAvatar {
-            float: left;
-        }
-
-        .mmsgLetterInfo {
-            margin: 0 0 0 60px;
-        }
-
-        .mmsgLetterNickName {
-            font-size: 14px;
-            font-weight: bold;
-        }
-
-        .mmsgLetterUin {
-            font-size: 12px;
-            color: #666;
-        }
-
-        .mmsgLetterUser {
-            padding: 10px 0;
-        }
-
-        .mmsgLetterUserItem {
-            padding: 0 0 20px 0;
-        }
-
-        .mmsgLetterUserAvatar {
-            height: 40px;
-            border: 1px solid #ccc;
-            padding: 2px;
-            display: block;
-            float: left;
-        }
-
-        .mmsgLetterUserAvatar img {
-            width: 40px;
-            height: 40px;
-        }
-
-        .mmsgLetterInfo {
-            margin-left: 48px;
-        }
-
-        .mmsgLetterName {
-            display: block;
-            color: #5fa207;
-            font-weight: bold;
-            margin-left: 10px;
-            padding-top: 10px;
-        }
-
-        .mmsgLetterDesc {
-            font-size: 12px;
-            float: left;
-            height: 43px;
-            background: url('images/mmsgletter_chat_right.gif') no-repeat right top;
-        }
-
-        .mmsgLetterDesc div {
-            white-space: nowrap;
-            float: left;
-            height: 43px;
-            padding: 0 20px;
-            line-height: 40px;
-            background: url('images/mmsgletter_chat_left.gif') no-repeat left top;
-        }
-
-        .qmbox style,
-        .qmbox script,
-        .qmbox head,
-        .qmbox link,
-        .qmbox meta {
-            display: none !important;
-        }
-
-        #mailContentContainer .txt {
-            height: auto;
-        }
-
-        a {
-            color: #407700 !important;
-        }
-
-        .renewed {
-            color: #407700!important;
-        }
-
-        .notRenewed {
-            color: #eb1b2e!important;
-        }
-
-        .renewed a,
-        .notRenewed a {
-            color: inherit!important;
-        }
-    </style>
-</head>
-<body>
-<div id="mailContentContainer" class="qmbox qm_con_body_content qqmail_webmail_only" style="">
-    <div style="background-color:#d0d0d0;background-image:url('images/mmsgletter_2_bg.png');text-align:center;padding:40px;">
-        <div class="mmsgLetter">
-            <div class="mmsgLetterHeader"
-                 style="height:23px;">
-            </div>
-            <div class="mmsgLetterContent" style="text-align:left;padding:30px;font-size:14px;line-height:1.5;/*background:url('images/ting.jpg') no-repeat top right;background-size: 224px 224px;*/">
-                <div>
-                    <p>主人
-                        <br>呜哇哇哇~程序执行出错啦~
-                        <br>
-                        <br>账户:<a href="#" rel="noopener">%s</a>
-                        <br>异常原因:
-                        <br>%s
-                    </p>
-                </div>
-                <div class="mmsgLetterInscribe" style="padding:40px 0 0;">
-                    <img class="mmsgAvatar" src="https://q2.qlogo.cn/headimg_dl?dst_uin=593198779&spec=100"
-                         style="float:left;width: 40px;height: 40px;">
-                    <div class="mmsgSender" style="margin:0 0 0 54px;">
-                        <p class="mmsgName" style="margin:0 0 10px;">Im Robot</p>
-                        <p class="mmsgInfo" style="font-size:12px;margin:0;line-height:1.2;">
-                            邮件推送机器人<br>
-                            <a href="mailto:[email protected]" rel="noopener" target="_blank">[email protected]</a>
-                        </p>
-                    </div>
-                </div>
-            </div>
-            <div class="mmsgLetterFooter"
-                 style="margin:16px;text-align:center;font-size:12px;color:#888;text-shadow:1px 0px 0px #eee;">
-            </div>
-        </div>
-    </div>
-</div>
-</body>
-</html>
+<!DOCTYPE html>
+<html lang="zh">
+<head>
+    <meta charset="UTF-8">
+    <title>邮件通知</title>
+    <style>
+        .mmsgLetter {
+            width: 580px;
+            margin: 0 auto;
+            padding: 10px;
+            color: #333;
+            background: #fff;
+            border: 0px solid #aaa;
+            border-radius: 5px;
+            -webkit-box-shadow: 3px 3px 10px #999 !important;
+            -moz-box-shadow: 3px 3px 10px #999 !important;
+            box-shadow: 3px 3px 10px #999 !important;
+            font-family: Verdana, sans-serif;
+        }
+
+        .mmsgLetter a:link,
+        .mmsgLetter a:visited {
+            color: #407700;
+        }
+
+        .mmsgLetterContent {
+            text-align: left;
+            padding: 30px;
+            font-size: 14px;
+            line-height: 1.5;
+            /*background: url('images/ting.jpg') no-repeat top right;*/
+        }
+
+        .mmsgLetterContent h3 {
+            color: #000;
+            font-size: 20px;
+            font-weight: bold;
+            margin: 20px 0 20px;
+            border-top: 2px solid #eee;
+            padding: 20px 0 0 0;
+            font-family: "微软雅黑", "黑体", "Lucida Grande", Verdana, sans-serif;
+        }
+
+        .mmsgLetterContent p {
+            margin: 20px 0;
+            padding: 0;
+        }
+
+        .mmsgLetterContent .salutation {
+            font-weight: bold;
+        }
+
+        .mmsgLetterContent .mmsgMoreInfo {
+        }
+
+        .mmsgLetterContent a.mmsgButton {
+            display: block;
+            float: left;
+            height: 40px;
+            text-decoration: none;
+            text-align: center;
+            cursor: pointer;
+        }
+
+        .mmsgLetterContent a.mmsgButton span {
+            display: block;
+            float: left;
+            padding: 0 25px;
+            height: 40px;
+            line-height: 36px;
+            font-size: 14px;
+            font-weight: bold;
+            color: #fff;
+            text-shadow: 1px 0 0 #235e00;
+        }
+
+        .mmsgLetterContent a.mmsgButton:link,
+        .mmsgLetterContent a.mmsgButton:visited {
+            background: #338702 url('images/mmsgletter_2_btn.png') no-repeat right -40px;
+        }
+
+        .mmsgLetterContent a.mmsgButton:link span,
+        .mmsgLetterContent a.mmsgButton:visited span {
+            background: url('images/mmsgletter_2_btn.png') no-repeat 0 0;
+        }
+
+        .mmsgLetterContent a.mmsgButton:hover,
+        .mmsgLetterContent a.mmsgButton:active {
+            background: #338702 url('images/mmsgletter_2_btn.png') no-repeat right -120px;
+        }
+
+        .mmsgLetterContent a.mmsgButton:hover span,
+        .mmsgLetterContent a.mmsgButton:active span {
+            background: url('images/mmsgletter_2_btn.png') no-repeat 0 -80px;
+        }
+
+        .mmsgLetterInscribe {
+            padding: 40px 0 0;
+        }
+
+        .mmsgLetterInscribe .mmsgAvatar {
+            float: left;
+        }
+
+        .mmsgLetterInscribe .mmsgName {
+            margin: 0 0 10px;
+        }
+
+        .mmsgLetterInscribe .mmsgSender {
+            margin: 0 0 0 54px;
+        }
+
+        .mmsgLetterInscribe .mmsgInfo {
+            font-size: 12px;
+            margin: 0;
+            line-height: 1.2;
+        }
+
+        .mmsgLetterHeader {
+            height: 23px;
+            /*background: url('images/mmsgletter_2_bg_topline.png') repeat-x 0 0;*/
+            background: 7px 0 repeat-x #FFF;
+            background-image: -webkit-repeating-linear-gradient(135deg, #4882CE, #4882CE 20px, #FFF 20px, #FFF 35px, #EB1B2E 35px, #EB1B2E 55px, #FFF 55px, #FFF 70px);
+            background-image: repeating-linear-gradient(-45deg, #4882CE, #4882CE 20px, #FFF 20px, #FFF 35px, #EB1B2E 35px, #EB1B2E 55px, #FFF 55px, #FFF 70px);
+            background-size: 110px 10px;
+        }
+
+        .mmsgLetterFooter {
+            margin: 16px;
+            text-align: center;
+            font-size: 12px;
+            color: #888;
+            text-shadow: 1px 0px 0px #eee;
+        }
+
+        .mmsgLetterClr {
+            clear: both;
+            overflow: hidden;
+            height: 1px;
+        }
+
+        .mmsgLetterUser {
+            padding: 10px 0;
+        }
+
+        .mmsgLetterUserItem {
+            padding: 0 0 20px 0;
+        }
+
+        .mmsgLetterUserAvatar {
+            height: 40px;
+            border: 1px solid #ccc;
+            padding: 2px;
+            display: block;
+            float: left;
+        }
+
+        .mmsgLetterUserAvatar img {
+            width: 40px;
+            height: 40px;
+        }
+
+        .mmsgLetterInfo {
+            margin-left: 48px;
+        }
+
+        .mmsgLetterName {
+            display: block;
+            color: #5fa207;
+            font-weight: bold;
+            margin-left: 10px;
+        }
+
+        .mmsgLetterDesc {
+            font-size: 12px;
+            float: left;
+            height: 43px;
+            background: url('images/mmsgletter_chat_right.gif') no-repeat right top;
+        }
+
+        .mmsgLetterDesc div {
+            white-space: nowrap;
+            float: left;
+            height: 43px;
+            padding: 0 20px;
+            line-height: 40px;
+            background: url('images/mmsgletter_chat_left.gif') no-repeat left top;
+        }
+
+        .mmsgLetterUser {
+        }
+
+        .mmsgLetterAvatar {
+            float: left;
+        }
+
+        .mmsgLetterInfo {
+            margin: 0 0 0 60px;
+        }
+
+        .mmsgLetterNickName {
+            font-size: 14px;
+            font-weight: bold;
+        }
+
+        .mmsgLetterUin {
+            font-size: 12px;
+            color: #666;
+        }
+
+        .mmsgLetterUser {
+            padding: 10px 0;
+        }
+
+        .mmsgLetterUserItem {
+            padding: 0 0 20px 0;
+        }
+
+        .mmsgLetterUserAvatar {
+            height: 40px;
+            border: 1px solid #ccc;
+            padding: 2px;
+            display: block;
+            float: left;
+        }
+
+        .mmsgLetterUserAvatar img {
+            width: 40px;
+            height: 40px;
+        }
+
+        .mmsgLetterInfo {
+            margin-left: 48px;
+        }
+
+        .mmsgLetterName {
+            display: block;
+            color: #5fa207;
+            font-weight: bold;
+            margin-left: 10px;
+            padding-top: 10px;
+        }
+
+        .mmsgLetterDesc {
+            font-size: 12px;
+            float: left;
+            height: 43px;
+            background: url('images/mmsgletter_chat_right.gif') no-repeat right top;
+        }
+
+        .mmsgLetterDesc div {
+            white-space: nowrap;
+            float: left;
+            height: 43px;
+            padding: 0 20px;
+            line-height: 40px;
+            background: url('images/mmsgletter_chat_left.gif') no-repeat left top;
+        }
+
+        .qmbox style,
+        .qmbox script,
+        .qmbox head,
+        .qmbox link,
+        .qmbox meta {
+            display: none !important;
+        }
+
+        #mailContentContainer .txt {
+            height: auto;
+        }
+
+        a {
+            color: #407700 !important;
+        }
+
+        .renewed {
+            color: #4882CE !important;
+        }
+
+        .notRenewed {
+            color: #EB1B2E !important;
+        }
+
+        .domainDays a {
+            color: #407700 !important;
+        }
+
+        .renewed a,
+        .notRenewed a,
+        .domainDays a {
+            /*color: inherit !important;*/
+            display: inline-block;
+            background: #ffffff;
+            min-width: 22px;
+            text-align: center;
+            padding: 4px 6px;
+            border-radius: 22px;
+            font-size: 14px;
+            font-weight: bold;
+            transition: 0.2s all ease-in-out;
+            text-decoration: none !important;
+            margin-right: 12px;
+        }
+
+        .renewed a {
+            color: inherit !important;
+            border: 2px solid #4882CE;
+        }
+
+        .notRenewed a {
+            color: inherit !important;
+            border: 2px solid #EB1B2E;
+        }
+
+        .domainDays a {
+            border: 2px solid #407700;
+        }
+
+        .renewed a:hover {
+            background-color: #4882CE;
+            color: #ffffff !important;
+        }
+
+        .notRenewed a:hover {
+            background-color: #EB1B2E;
+            color: #ffffff !important;
+        }
+
+        .domainDays a:hover {
+            background-color: #407700;
+            color: #ffffff !important;
+        }
+    </style>
+</head>
+<body>
+<div id="mailContentContainer" class="qmbox qm_con_body_content qqmail_webmail_only" style="">
+    <div style="background-color:#d0d0d0;background-image:url('images/mmsgletter_2_bg.png');text-align:center;padding:40px;">
+        <div class="mmsgLetter">
+            <div class="mmsgLetterHeader" style="height:23px;"></div>
+            <div class="mmsgLetterContent" style="text-align:left;padding: 0 30px;font-size:14px;line-height:1.5;/*background:url('images/ting.jpg') no-repeat top right;background-size: 224px 224px;*/">
+                <div>
+                    <p>
+                        <br>我刚刚帮小主看了一下,账户 <a href="#" rel="noopener">%s</a> 今天并没有需要续期的域名。所有域名情况如下:
+                        <br>
+                        <br><span class="domainDays">%s</span>
+                        <br><br>更多信息可以参考 <a href="https://my.freenom.com/domains.php?a=renewals" target="_blank" rel="noopener">Freenom官网</a> 哦~
+                        <br>
+                        <br>(如果你不想每次执行都收到推送,请将 .env 中 NOTICE_FREQ 的值设为 0,使程序只在有续期操作时才推送)
+                    </p>
+                </div>
+                <div class="mmsgLetterInscribe" style="padding:40px 0 0;">
+                    <img class="mmsgAvatar" src="https://q2.qlogo.cn/headimg_dl?dst_uin=593198779&spec=100" style="float:left;width:40px;height:40px;">
+                    <div class="mmsgSender" style="margin:0 0 0 54px;">
+                        <p class="mmsgName" style="margin:0 0 10px;">Im Robot</p>
+                        <p class="mmsgInfo" style="font-size:12px;margin:0;line-height:1.2;">
+                            邮件推送机器人<br>
+                            <a href="mailto:[email protected]" rel="noopener" target="_blank">[email protected]</a>
+                        </p>
+                    </div>
+                </div>
+            </div>
+            <div class="mmsgLetterFooter" style="margin:16px;text-align:center;font-size:12px;color:#888;text-shadow:1px 0px 0px #eee;"></div>
+        </div>
+    </div>
+</div>
+</body>
+</html>

+ 362 - 366
resources/mail/notice.html

@@ -1,366 +1,362 @@
-<!DOCTYPE html>
-<html lang="zh">
-<head>
-    <meta charset="UTF-8">
-    <title>邮件通知</title>
-    <style>
-        .mmsgLetter {
-            width: 580px;
-            margin: 0 auto;
-            padding: 10px;
-            color: #333;
-            background: #fff;
-            border: 0px solid #aaa;
-            border-radius: 5px;
-            -webkit-box-shadow: 3px 3px 10px #999 !important;
-            -moz-box-shadow: 3px 3px 10px #999 !important;
-            box-shadow: 3px 3px 10px #999 !important;
-            font-family: Verdana, sans-serif;
-        }
-
-        .mmsgLetter a:link,
-        .mmsgLetter a:visited {
-            color: #407700;
-        }
-
-        .mmsgLetterContent {
-            text-align: left;
-            padding: 30px;
-            font-size: 14px;
-            line-height: 1.5;
-            /*background: url('images/ting.jpg') no-repeat top right;*/
-        }
-
-        .mmsgLetterContent h3 {
-            color: #000;
-            font-size: 20px;
-            font-weight: bold;
-            margin: 20px 0 20px;
-            border-top: 2px solid #eee;
-            padding: 20px 0 0 0;
-            font-family: "微软雅黑", "黑体", "Lucida Grande", Verdana, sans-serif;
-        }
-
-        .mmsgLetterContent p {
-            margin: 20px 0;
-            padding: 0;
-        }
-
-        .mmsgLetterContent .salutation {
-            font-weight: bold;
-        }
-
-        .mmsgLetterContent .mmsgMoreInfo {
-        }
-
-        .mmsgLetterContent a.mmsgButton {
-            display: block;
-            float: left;
-            height: 40px;
-            text-decoration: none;
-            text-align: center;
-            cursor: pointer;
-        }
-
-        .mmsgLetterContent a.mmsgButton span {
-            display: block;
-            float: left;
-            padding: 0 25px;
-            height: 40px;
-            line-height: 36px;
-            font-size: 14px;
-            font-weight: bold;
-            color: #fff;
-            text-shadow: 1px 0 0 #235e00;
-        }
-
-        .mmsgLetterContent a.mmsgButton:link,
-        .mmsgLetterContent a.mmsgButton:visited {
-            background: #338702 url('images/mmsgletter_2_btn.png') no-repeat right -40px;
-        }
-
-        .mmsgLetterContent a.mmsgButton:link span,
-        .mmsgLetterContent a.mmsgButton:visited span {
-            background: url('images/mmsgletter_2_btn.png') no-repeat 0 0;
-        }
-
-        .mmsgLetterContent a.mmsgButton:hover,
-        .mmsgLetterContent a.mmsgButton:active {
-            background: #338702 url('images/mmsgletter_2_btn.png') no-repeat right -120px;
-        }
-
-        .mmsgLetterContent a.mmsgButton:hover span,
-        .mmsgLetterContent a.mmsgButton:active span {
-            background: url('images/mmsgletter_2_btn.png') no-repeat 0 -80px;
-        }
-
-        .mmsgLetterInscribe {
-            padding: 40px 0 0;
-        }
-
-        .mmsgLetterInscribe .mmsgAvatar {
-            float: left;
-        }
-
-        .mmsgLetterInscribe .mmsgName {
-            margin: 0 0 10px;
-        }
-
-        .mmsgLetterInscribe .mmsgSender {
-            margin: 0 0 0 54px;
-        }
-
-        .mmsgLetterInscribe .mmsgInfo {
-            font-size: 12px;
-            margin: 0;
-            line-height: 1.2;
-        }
-
-        .mmsgLetterHeader {
-            height: 23px;
-            /*background: url('images/mmsgletter_2_bg_topline.png') repeat-x 0 0;*/
-            background: 7px 0 repeat-x #FFF;
-            background-image: -webkit-repeating-linear-gradient(135deg, #4882CE, #4882CE 20px, #FFF 20px, #FFF 35px, #EB1B2E 35px, #EB1B2E 55px, #FFF 55px, #FFF 70px);
-            background-image: repeating-linear-gradient(-45deg, #4882CE, #4882CE 20px, #FFF 20px, #FFF 35px, #EB1B2E 35px, #EB1B2E 55px, #FFF 55px, #FFF 70px);
-            background-size: 110px 10px;
-        }
-
-        .mmsgLetterFooter {
-            margin: 16px;
-            text-align: center;
-            font-size: 12px;
-            color: #888;
-            text-shadow: 1px 0px 0px #eee;
-        }
-
-        .mmsgLetterClr {
-            clear: both;
-            overflow: hidden;
-            height: 1px;
-        }
-
-        .mmsgLetterUser {
-            padding: 10px 0;
-        }
-
-        .mmsgLetterUserItem {
-            padding: 0 0 20px 0;
-        }
-
-        .mmsgLetterUserAvatar {
-            height: 40px;
-            border: 1px solid #ccc;
-            padding: 2px;
-            display: block;
-            float: left;
-        }
-
-        .mmsgLetterUserAvatar img {
-            width: 40px;
-            height: 40px;
-        }
-
-        .mmsgLetterInfo {
-            margin-left: 48px;
-        }
-
-        .mmsgLetterName {
-            display: block;
-            color: #5fa207;
-            font-weight: bold;
-            margin-left: 10px;
-        }
-
-        .mmsgLetterDesc {
-            font-size: 12px;
-            float: left;
-            height: 43px;
-            background: url('images/mmsgletter_chat_right.gif') no-repeat right top;
-        }
-
-        .mmsgLetterDesc div {
-            white-space: nowrap;
-            float: left;
-            height: 43px;
-            padding: 0 20px;
-            line-height: 40px;
-            background: url('images/mmsgletter_chat_left.gif') no-repeat left top;
-        }
-
-        .mmsgLetterUser {
-        }
-
-        .mmsgLetterAvatar {
-            float: left;
-        }
-
-        .mmsgLetterInfo {
-            margin: 0 0 0 60px;
-        }
-
-        .mmsgLetterNickName {
-            font-size: 14px;
-            font-weight: bold;
-        }
-
-        .mmsgLetterUin {
-            font-size: 12px;
-            color: #666;
-        }
-
-        .mmsgLetterUser {
-            padding: 10px 0;
-        }
-
-        .mmsgLetterUserItem {
-            padding: 0 0 20px 0;
-        }
-
-        .mmsgLetterUserAvatar {
-            height: 40px;
-            border: 1px solid #ccc;
-            padding: 2px;
-            display: block;
-            float: left;
-        }
-
-        .mmsgLetterUserAvatar img {
-            width: 40px;
-            height: 40px;
-        }
-
-        .mmsgLetterInfo {
-            margin-left: 48px;
-        }
-
-        .mmsgLetterName {
-            display: block;
-            color: #5fa207;
-            font-weight: bold;
-            margin-left: 10px;
-            padding-top: 10px;
-        }
-
-        .mmsgLetterDesc {
-            font-size: 12px;
-            float: left;
-            height: 43px;
-            background: url('images/mmsgletter_chat_right.gif') no-repeat right top;
-        }
-
-        .mmsgLetterDesc div {
-            white-space: nowrap;
-            float: left;
-            height: 43px;
-            padding: 0 20px;
-            line-height: 40px;
-            background: url('images/mmsgletter_chat_left.gif') no-repeat left top;
-        }
-
-        .qmbox style,
-        .qmbox script,
-        .qmbox head,
-        .qmbox link,
-        .qmbox meta {
-            display: none !important;
-        }
-
-        #mailContentContainer .txt {
-            height: auto;
-        }
-
-        a {
-            color: #407700 !important;
-        }
-
-        .renewed {
-            color: #4882CE !important;
-        }
-
-        .notRenewed {
-            color: #EB1B2E !important;
-        }
-
-        .domainDays a {
-            color: #407700 !important;
-        }
-
-        .renewed a,
-        .notRenewed a,
-        .domainDays a {
-            /*color: inherit !important;*/
-            display: inline-block;
-            background: #ffffff;
-            min-width: 22px;
-            text-align: center;
-            padding: 4px 6px;
-            border-radius: 22px;
-            font-size: 14px;
-            font-weight: bold;
-            transition: 0.2s all ease-in-out;
-            text-decoration: none !important;
-            margin-right: 12px;
-        }
-
-        .renewed a {
-            color: inherit !important;
-            border: 2px solid #4882CE;
-        }
-
-        .notRenewed a {
-            color: inherit !important;
-            border: 2px solid #EB1B2E;
-        }
-
-        .domainDays a {
-            border: 2px solid #407700;
-        }
-
-        .renewed a:hover {
-            background-color: #4882CE;
-            color: #ffffff !important;
-        }
-
-        .notRenewed a:hover {
-            background-color: #EB1B2E;
-            color: #ffffff !important;
-        }
-
-        .domainDays a:hover {
-            background-color: #407700;
-            color: #ffffff !important;
-        }
-    </style>
-</head>
-<body>
-<div id="mailContentContainer" class="qmbox qm_con_body_content qqmail_webmail_only" style="">
-    <div style="background-color:#d0d0d0;background-image:url('images/mmsgletter_2_bg.png');text-align:center;padding:40px;">
-        <div class="mmsgLetter">
-            <div class="mmsgLetterHeader" style="height:23px;"></div>
-            <div class="mmsgLetterContent" style="text-align:left;padding:30px;font-size:14px;line-height:1.5;/*background:url('images/ting.jpg') no-repeat top right;background-size: 224px 224px;*/">
-                <div>
-                    <p>
-                        <br>我刚刚帮小主看了一下,账户<a href="#" rel="noopener">%s</a>今天并没有需要续期的域名。所有域名情况如下:
-                        <br>
-                        <br><span class="domainDays">%s</span>更多信息可以参考<a href="https://my.freenom.com/domains.php?a=renewals" target="_blank" rel="noopener">Freenom官网</a>哦~
-                        <br>
-                        <br>(如果你不想每次执行都收到推送,请将 .env 中 NOTICE_FREQ 的值设为0,使程序只在有续期操作时才推送)
-                    </p>
-                </div>
-                <div class="mmsgLetterInscribe" style="padding:40px 0 0;">
-                    <img class="mmsgAvatar" src="https://q2.qlogo.cn/headimg_dl?dst_uin=593198779&spec=100" style="float:left;width:40px;height:40px;">
-                    <div class="mmsgSender" style="margin:0 0 0 54px;">
-                        <p class="mmsgName" style="margin:0 0 10px;">Im Robot</p>
-                        <p class="mmsgInfo" style="font-size:12px;margin:0;line-height:1.2;">
-                            邮件推送机器人<br>
-                            <a href="mailto:[email protected]" rel="noopener" target="_blank">[email protected]</a>
-                        </p>
-                    </div>
-                </div>
-            </div>
-            <div class="mmsgLetterFooter" style="margin:16px;text-align:center;font-size:12px;color:#888;text-shadow:1px 0px 0px #eee;"></div>
-        </div>
-    </div>
-</div>
-</body>
-</html>
+<!DOCTYPE html>
+<html lang="zh">
+<head>
+    <meta charset="UTF-8">
+    <title>邮件通知</title>
+    <style>
+        .mmsgLetter {
+            width: 580px;
+            margin: 0 auto;
+            padding: 10px;
+            color: #333;
+            background: #fff;
+            border: 0px solid #aaa;
+            border-radius: 5px;
+            -webkit-box-shadow: 3px 3px 10px #999 !important;
+            -moz-box-shadow: 3px 3px 10px #999 !important;
+            box-shadow: 3px 3px 10px #999 !important;
+            font-family: Verdana, sans-serif;
+        }
+
+        .mmsgLetter a:link,
+        .mmsgLetter a:visited {
+            color: #407700;
+        }
+
+        .mmsgLetterContent {
+            text-align: left;
+            padding: 30px;
+            font-size: 14px;
+            line-height: 1.5;
+            /*background: url('images/ting.jpg') no-repeat top right;*/
+        }
+
+        .mmsgLetterContent h3 {
+            color: #000;
+            font-size: 20px;
+            font-weight: bold;
+            margin: 20px 0 20px;
+            border-top: 2px solid #eee;
+            padding: 20px 0 0 0;
+            font-family: "微软雅黑", "黑体", "Lucida Grande", Verdana, sans-serif;
+        }
+
+        .mmsgLetterContent p {
+            margin: 20px 0;
+            padding: 0;
+        }
+
+        .mmsgLetterContent .salutation {
+            font-weight: bold;
+        }
+
+        .mmsgLetterContent .mmsgMoreInfo {
+        }
+
+        .mmsgLetterContent a.mmsgButton {
+            display: block;
+            float: left;
+            height: 40px;
+            text-decoration: none;
+            text-align: center;
+            cursor: pointer;
+        }
+
+        .mmsgLetterContent a.mmsgButton span {
+            display: block;
+            float: left;
+            padding: 0 25px;
+            height: 40px;
+            line-height: 36px;
+            font-size: 14px;
+            font-weight: bold;
+            color: #fff;
+            text-shadow: 1px 0 0 #235e00;
+        }
+
+        .mmsgLetterContent a.mmsgButton:link,
+        .mmsgLetterContent a.mmsgButton:visited {
+            background: #338702 url('images/mmsgletter_2_btn.png') no-repeat right -40px;
+        }
+
+        .mmsgLetterContent a.mmsgButton:link span,
+        .mmsgLetterContent a.mmsgButton:visited span {
+            background: url('images/mmsgletter_2_btn.png') no-repeat 0 0;
+        }
+
+        .mmsgLetterContent a.mmsgButton:hover,
+        .mmsgLetterContent a.mmsgButton:active {
+            background: #338702 url('images/mmsgletter_2_btn.png') no-repeat right -120px;
+        }
+
+        .mmsgLetterContent a.mmsgButton:hover span,
+        .mmsgLetterContent a.mmsgButton:active span {
+            background: url('images/mmsgletter_2_btn.png') no-repeat 0 -80px;
+        }
+
+        .mmsgLetterInscribe {
+            padding: 40px 0 0;
+        }
+
+        .mmsgLetterInscribe .mmsgAvatar {
+            float: left;
+        }
+
+        .mmsgLetterInscribe .mmsgName {
+            margin: 0 0 10px;
+        }
+
+        .mmsgLetterInscribe .mmsgSender {
+            margin: 0 0 0 54px;
+        }
+
+        .mmsgLetterInscribe .mmsgInfo {
+            font-size: 12px;
+            margin: 0;
+            line-height: 1.2;
+        }
+
+        .mmsgLetterHeader {
+            height: 23px;
+            /*background: url('images/mmsgletter_2_bg_topline.png') repeat-x 0 0;*/
+            background: 7px 0 repeat-x #FFF;
+            background-image: -webkit-repeating-linear-gradient(135deg, #4882CE, #4882CE 20px, #FFF 20px, #FFF 35px, #EB1B2E 35px, #EB1B2E 55px, #FFF 55px, #FFF 70px);
+            background-image: repeating-linear-gradient(-45deg, #4882CE, #4882CE 20px, #FFF 20px, #FFF 35px, #EB1B2E 35px, #EB1B2E 55px, #FFF 55px, #FFF 70px);
+            background-size: 110px 10px;
+        }
+
+        .mmsgLetterFooter {
+            margin: 16px;
+            text-align: center;
+            font-size: 12px;
+            color: #888;
+            text-shadow: 1px 0px 0px #eee;
+        }
+
+        .mmsgLetterClr {
+            clear: both;
+            overflow: hidden;
+            height: 1px;
+        }
+
+        .mmsgLetterUser {
+            padding: 10px 0;
+        }
+
+        .mmsgLetterUserItem {
+            padding: 0 0 20px 0;
+        }
+
+        .mmsgLetterUserAvatar {
+            height: 40px;
+            border: 1px solid #ccc;
+            padding: 2px;
+            display: block;
+            float: left;
+        }
+
+        .mmsgLetterUserAvatar img {
+            width: 40px;
+            height: 40px;
+        }
+
+        .mmsgLetterInfo {
+            margin-left: 48px;
+        }
+
+        .mmsgLetterName {
+            display: block;
+            color: #5fa207;
+            font-weight: bold;
+            margin-left: 10px;
+        }
+
+        .mmsgLetterDesc {
+            font-size: 12px;
+            float: left;
+            height: 43px;
+            background: url('images/mmsgletter_chat_right.gif') no-repeat right top;
+        }
+
+        .mmsgLetterDesc div {
+            white-space: nowrap;
+            float: left;
+            height: 43px;
+            padding: 0 20px;
+            line-height: 40px;
+            background: url('images/mmsgletter_chat_left.gif') no-repeat left top;
+        }
+
+        .mmsgLetterUser {
+        }
+
+        .mmsgLetterAvatar {
+            float: left;
+        }
+
+        .mmsgLetterInfo {
+            margin: 0 0 0 60px;
+        }
+
+        .mmsgLetterNickName {
+            font-size: 14px;
+            font-weight: bold;
+        }
+
+        .mmsgLetterUin {
+            font-size: 12px;
+            color: #666;
+        }
+
+        .mmsgLetterUser {
+            padding: 10px 0;
+        }
+
+        .mmsgLetterUserItem {
+            padding: 0 0 20px 0;
+        }
+
+        .mmsgLetterUserAvatar {
+            height: 40px;
+            border: 1px solid #ccc;
+            padding: 2px;
+            display: block;
+            float: left;
+        }
+
+        .mmsgLetterUserAvatar img {
+            width: 40px;
+            height: 40px;
+        }
+
+        .mmsgLetterInfo {
+            margin-left: 48px;
+        }
+
+        .mmsgLetterName {
+            display: block;
+            color: #5fa207;
+            font-weight: bold;
+            margin-left: 10px;
+            padding-top: 10px;
+        }
+
+        .mmsgLetterDesc {
+            font-size: 12px;
+            float: left;
+            height: 43px;
+            background: url('images/mmsgletter_chat_right.gif') no-repeat right top;
+        }
+
+        .mmsgLetterDesc div {
+            white-space: nowrap;
+            float: left;
+            height: 43px;
+            padding: 0 20px;
+            line-height: 40px;
+            background: url('images/mmsgletter_chat_left.gif') no-repeat left top;
+        }
+
+        .qmbox style,
+        .qmbox script,
+        .qmbox head,
+        .qmbox link,
+        .qmbox meta {
+            display: none !important;
+        }
+
+        #mailContentContainer .txt {
+            height: auto;
+        }
+
+        a {
+            color: #407700 !important;
+        }
+
+        .renewed {
+            color: #4882CE !important;
+        }
+
+        .notRenewed {
+            color: #EB1B2E !important;
+        }
+
+        .domainDays a {
+            color: #407700 !important;
+        }
+
+        .renewed a,
+        .notRenewed a,
+        .domainDays a {
+            /*color: inherit !important;*/
+            display: inline-block;
+            background: #ffffff;
+            min-width: 22px;
+            text-align: center;
+            padding: 4px 6px;
+            border-radius: 22px;
+            font-size: 14px;
+            font-weight: bold;
+            transition: 0.2s all ease-in-out;
+            text-decoration: none !important;
+            margin-right: 12px;
+        }
+
+        .renewed a {
+            color: inherit !important;
+            border: 2px solid #4882CE;
+        }
+
+        .notRenewed a {
+            color: inherit !important;
+            border: 2px solid #EB1B2E;
+        }
+
+        .domainDays a {
+            border: 2px solid #407700;
+        }
+
+        .renewed a:hover {
+            background-color: #4882CE;
+            color: #ffffff !important;
+        }
+
+        .notRenewed a:hover {
+            background-color: #EB1B2E;
+            color: #ffffff !important;
+        }
+
+        .domainDays a:hover {
+            background-color: #407700;
+            color: #ffffff !important;
+        }
+    </style>
+</head>
+<body>
+<div id="mailContentContainer" class="qmbox qm_con_body_content qqmail_webmail_only" style="">
+    <div style="background-color:#d0d0d0;background-image:url('images/mmsgletter_2_bg.png');text-align:center;padding:40px;">
+        <div class="mmsgLetter">
+            <div class="mmsgLetterHeader" style="height:23px;"></div>
+            <div class="mmsgLetterContent" style="text-align:left;padding: 0 30px;font-size:14px;line-height:1.5;/*background:url('images/ting.jpg') no-repeat top right;background-size: 224px 224px;*/">
+                <div>
+                    <p>
+                        %s
+                    </p>
+                </div>
+                <div class="mmsgLetterInscribe" style="padding:40px 0 0;">
+                    <img class="mmsgAvatar" src="https://q2.qlogo.cn/headimg_dl?dst_uin=593198779&spec=100" style="float:left;width:40px;height:40px;">
+                    <div class="mmsgSender" style="margin:0 0 0 54px;">
+                        <p class="mmsgName" style="margin:0 0 10px;">Im Robot</p>
+                        <p class="mmsgInfo" style="font-size:12px;margin:0;line-height:1.2;">
+                            邮件推送机器人<br>
+                            <a href="mailto:[email protected]" rel="noopener" target="_blank">[email protected]</a>
+                        </p>
+                    </div>
+                </div>
+            </div>
+            <div class="mmsgLetterFooter" style="margin:16px;text-align:center;font-size:12px;color:#888;text-shadow:1px 0px 0px #eee;"></div>
+        </div>
+    </div>
+</div>
+</body>
+</html>

+ 368 - 365
resources/mail/default.html → resources/mail/successful_renewal.html

@@ -1,366 +1,369 @@
-<!DOCTYPE html>
-<html lang="zh">
-<head>
-    <meta charset="UTF-8">
-    <title>邮件通知</title>
-    <style>
-        .mmsgLetter {
-            width: 580px;
-            margin: 0 auto;
-            padding: 10px;
-            color: #333;
-            background: #fff;
-            border: 0px solid #aaa;
-            border-radius: 5px;
-            -webkit-box-shadow: 3px 3px 10px #999 !important;
-            -moz-box-shadow: 3px 3px 10px #999 !important;
-            box-shadow: 3px 3px 10px #999 !important;
-            font-family: Verdana, sans-serif;
-        }
-
-        .mmsgLetter a:link,
-        .mmsgLetter a:visited {
-            color: #407700;
-        }
-
-        .mmsgLetterContent {
-            text-align: left;
-            padding: 30px;
-            font-size: 14px;
-            line-height: 1.5;
-            /*background: url('images/ting.jpg') no-repeat top right;*/
-        }
-
-        .mmsgLetterContent h3 {
-            color: #000;
-            font-size: 20px;
-            font-weight: bold;
-            margin: 20px 0 20px;
-            border-top: 2px solid #eee;
-            padding: 20px 0 0 0;
-            font-family: "微软雅黑", "黑体", "Lucida Grande", Verdana, sans-serif;
-        }
-
-        .mmsgLetterContent p {
-            margin: 20px 0;
-            padding: 0;
-        }
-
-        .mmsgLetterContent .salutation {
-            font-weight: bold;
-        }
-
-        .mmsgLetterContent .mmsgMoreInfo {
-        }
-
-        .mmsgLetterContent a.mmsgButton {
-            display: block;
-            float: left;
-            height: 40px;
-            text-decoration: none;
-            text-align: center;
-            cursor: pointer;
-        }
-
-        .mmsgLetterContent a.mmsgButton span {
-            display: block;
-            float: left;
-            padding: 0 25px;
-            height: 40px;
-            line-height: 36px;
-            font-size: 14px;
-            font-weight: bold;
-            color: #fff;
-            text-shadow: 1px 0 0 #235e00;
-        }
-
-        .mmsgLetterContent a.mmsgButton:link,
-        .mmsgLetterContent a.mmsgButton:visited {
-            background: #338702 url('images/mmsgletter_2_btn.png') no-repeat right -40px;
-        }
-
-        .mmsgLetterContent a.mmsgButton:link span,
-        .mmsgLetterContent a.mmsgButton:visited span {
-            background: url('images/mmsgletter_2_btn.png') no-repeat 0 0;
-        }
-
-        .mmsgLetterContent a.mmsgButton:hover,
-        .mmsgLetterContent a.mmsgButton:active {
-            background: #338702 url('images/mmsgletter_2_btn.png') no-repeat right -120px;
-        }
-
-        .mmsgLetterContent a.mmsgButton:hover span,
-        .mmsgLetterContent a.mmsgButton:active span {
-            background: url('images/mmsgletter_2_btn.png') no-repeat 0 -80px;
-        }
-
-        .mmsgLetterInscribe {
-            padding: 40px 0 0;
-        }
-
-        .mmsgLetterInscribe .mmsgAvatar {
-            float: left;
-        }
-
-        .mmsgLetterInscribe .mmsgName {
-            margin: 0 0 10px;
-        }
-
-        .mmsgLetterInscribe .mmsgSender {
-            margin: 0 0 0 54px;
-        }
-
-        .mmsgLetterInscribe .mmsgInfo {
-            font-size: 12px;
-            margin: 0;
-            line-height: 1.2;
-        }
-
-        .mmsgLetterHeader {
-            height: 23px;
-            /*background: url('images/mmsgletter_2_bg_topline.png') repeat-x 0 0;*/
-            background: 7px 0 repeat-x #FFF;
-            background-image: -webkit-repeating-linear-gradient(135deg, #4882CE, #4882CE 20px, #FFF 20px, #FFF 35px, #EB1B2E 35px, #EB1B2E 55px, #FFF 55px, #FFF 70px);
-            background-image: repeating-linear-gradient(-45deg, #4882CE, #4882CE 20px, #FFF 20px, #FFF 35px, #EB1B2E 35px, #EB1B2E 55px, #FFF 55px, #FFF 70px);
-            background-size: 110px 10px;
-        }
-
-        .mmsgLetterFooter {
-            margin: 16px;
-            text-align: center;
-            font-size: 12px;
-            color: #888;
-            text-shadow: 1px 0px 0px #eee;
-        }
-
-        .mmsgLetterClr {
-            clear: both;
-            overflow: hidden;
-            height: 1px;
-        }
-
-        .mmsgLetterUser {
-            padding: 10px 0;
-        }
-
-        .mmsgLetterUserItem {
-            padding: 0 0 20px 0;
-        }
-
-        .mmsgLetterUserAvatar {
-            height: 40px;
-            border: 1px solid #ccc;
-            padding: 2px;
-            display: block;
-            float: left;
-        }
-
-        .mmsgLetterUserAvatar img {
-            width: 40px;
-            height: 40px;
-        }
-
-        .mmsgLetterInfo {
-            margin-left: 48px;
-        }
-
-        .mmsgLetterName {
-            display: block;
-            color: #5fa207;
-            font-weight: bold;
-            margin-left: 10px;
-        }
-
-        .mmsgLetterDesc {
-            font-size: 12px;
-            float: left;
-            height: 43px;
-            background: url('images/mmsgletter_chat_right.gif') no-repeat right top;
-        }
-
-        .mmsgLetterDesc div {
-            white-space: nowrap;
-            float: left;
-            height: 43px;
-            padding: 0 20px;
-            line-height: 40px;
-            background: url('images/mmsgletter_chat_left.gif') no-repeat left top;
-        }
-
-        .mmsgLetterUser {
-        }
-
-        .mmsgLetterAvatar {
-            float: left;
-        }
-
-        .mmsgLetterInfo {
-            margin: 0 0 0 60px;
-        }
-
-        .mmsgLetterNickName {
-            font-size: 14px;
-            font-weight: bold;
-        }
-
-        .mmsgLetterUin {
-            font-size: 12px;
-            color: #666;
-        }
-
-        .mmsgLetterUser {
-            padding: 10px 0;
-        }
-
-        .mmsgLetterUserItem {
-            padding: 0 0 20px 0;
-        }
-
-        .mmsgLetterUserAvatar {
-            height: 40px;
-            border: 1px solid #ccc;
-            padding: 2px;
-            display: block;
-            float: left;
-        }
-
-        .mmsgLetterUserAvatar img {
-            width: 40px;
-            height: 40px;
-        }
-
-        .mmsgLetterInfo {
-            margin-left: 48px;
-        }
-
-        .mmsgLetterName {
-            display: block;
-            color: #5fa207;
-            font-weight: bold;
-            margin-left: 10px;
-            padding-top: 10px;
-        }
-
-        .mmsgLetterDesc {
-            font-size: 12px;
-            float: left;
-            height: 43px;
-            background: url('images/mmsgletter_chat_right.gif') no-repeat right top;
-        }
-
-        .mmsgLetterDesc div {
-            white-space: nowrap;
-            float: left;
-            height: 43px;
-            padding: 0 20px;
-            line-height: 40px;
-            background: url('images/mmsgletter_chat_left.gif') no-repeat left top;
-        }
-
-        .qmbox style,
-        .qmbox script,
-        .qmbox head,
-        .qmbox link,
-        .qmbox meta {
-            display: none !important;
-        }
-
-        #mailContentContainer .txt {
-            height: auto;
-        }
-
-        a {
-            color: #407700 !important;
-        }
-
-        .renewed {
-            color: #4882CE !important;
-        }
-
-        .notRenewed {
-            color: #EB1B2E !important;
-        }
-
-        .domainDays a {
-            color: #407700 !important;
-        }
-
-        .renewed a,
-        .notRenewed a,
-        .domainDays a {
-            /*color: inherit !important;*/
-            display: inline-block;
-            background: #ffffff;
-            min-width: 22px;
-            text-align: center;
-            padding: 4px 6px;
-            border-radius: 22px;
-            font-size: 14px;
-            font-weight: bold;
-            transition: 0.2s all ease-in-out;
-            text-decoration: none !important;
-            margin-right: 12px;
-        }
-
-        .renewed a {
-            color: inherit !important;
-            border: 2px solid #4882CE;
-        }
-
-        .notRenewed a {
-            color: inherit !important;
-            border: 2px solid #EB1B2E;
-        }
-
-        .domainDays a {
-            border: 2px solid #407700;
-        }
-
-        .renewed a:hover {
-            background-color: #4882CE;
-            color: #ffffff !important;
-        }
-
-        .notRenewed a:hover {
-            background-color: #EB1B2E;
-            color: #ffffff !important;
-        }
-
-        .domainDays a:hover {
-            background-color: #407700;
-            color: #ffffff !important;
-        }
-    </style>
-</head>
-<body>
-<div id="mailContentContainer" class="qmbox qm_con_body_content qqmail_webmail_only" style="">
-    <div style="background-color:#d0d0d0;background-image:url('images/mmsgletter_2_bg.png');text-align:center;padding:40px;">
-        <div class="mmsgLetter">
-            <div class="mmsgLetterHeader" style="height:23px;"></div>
-            <div class="mmsgLetterContent" style="text-align:left;padding:30px;font-size:14px;line-height:1.5;/*background:url('images/ting.jpg') no-repeat top right;background-size: 224px 224px;*/">
-                <div>
-                    <p>主人
-                        <br>刚刚萌萌哒我有帮你续期域名哦~账户<a href="#" rel="noopener">%s</a>这次续期的结果如下
-                        <br>
-                        <span class="renewed">%s</span>
-                        <span class="notRenewed">%s</span>
-                        <br>emmmm,除以上内容外,我还帮小主看了一下:<br><span class="domainDays">%s</span>更多信息可以参考<a href="https://my.freenom.com/domains.php?a=renewals" target="_blank" rel="noopener">Freenom官网</a>哦~
-                    </p>
-                </div>
-                <div class="mmsgLetterInscribe" style="padding:40px 0 0;">
-                    <img class="mmsgAvatar" src="https://q2.qlogo.cn/headimg_dl?dst_uin=593198779&spec=100" style="float:left;width:40px;height:40px;">
-                    <div class="mmsgSender" style="margin:0 0 0 54px;">
-                        <p class="mmsgName" style="margin:0 0 10px;">Im Robot</p>
-                        <p class="mmsgInfo" style="font-size:12px;margin:0;line-height:1.2;">
-                            邮件推送机器人<br>
-                            <a href="mailto:[email protected]" rel="noopener" target="_blank">[email protected]</a>
-                        </p>
-                    </div>
-                </div>
-            </div>
-            <div class="mmsgLetterFooter" style="margin:16px;text-align:center;font-size:12px;color:#888;text-shadow:1px 0px 0px #eee;"></div>
-        </div>
-    </div>
-</div>
-</body>
+<!DOCTYPE html>
+<html lang="zh">
+<head>
+    <meta charset="UTF-8">
+    <title>邮件通知</title>
+    <style>
+        .mmsgLetter {
+            width: 580px;
+            margin: 0 auto;
+            padding: 10px;
+            color: #333;
+            background: #fff;
+            border: 0px solid #aaa;
+            border-radius: 5px;
+            -webkit-box-shadow: 3px 3px 10px #999 !important;
+            -moz-box-shadow: 3px 3px 10px #999 !important;
+            box-shadow: 3px 3px 10px #999 !important;
+            font-family: Verdana, sans-serif;
+        }
+
+        .mmsgLetter a:link,
+        .mmsgLetter a:visited {
+            color: #407700;
+        }
+
+        .mmsgLetterContent {
+            text-align: left;
+            padding: 30px;
+            font-size: 14px;
+            line-height: 1.5;
+            /*background: url('images/ting.jpg') no-repeat top right;*/
+        }
+
+        .mmsgLetterContent h3 {
+            color: #000;
+            font-size: 20px;
+            font-weight: bold;
+            margin: 20px 0 20px;
+            border-top: 2px solid #eee;
+            padding: 20px 0 0 0;
+            font-family: "微软雅黑", "黑体", "Lucida Grande", Verdana, sans-serif;
+        }
+
+        .mmsgLetterContent p {
+            margin: 20px 0;
+            padding: 0;
+        }
+
+        .mmsgLetterContent .salutation {
+            font-weight: bold;
+        }
+
+        .mmsgLetterContent .mmsgMoreInfo {
+        }
+
+        .mmsgLetterContent a.mmsgButton {
+            display: block;
+            float: left;
+            height: 40px;
+            text-decoration: none;
+            text-align: center;
+            cursor: pointer;
+        }
+
+        .mmsgLetterContent a.mmsgButton span {
+            display: block;
+            float: left;
+            padding: 0 25px;
+            height: 40px;
+            line-height: 36px;
+            font-size: 14px;
+            font-weight: bold;
+            color: #fff;
+            text-shadow: 1px 0 0 #235e00;
+        }
+
+        .mmsgLetterContent a.mmsgButton:link,
+        .mmsgLetterContent a.mmsgButton:visited {
+            background: #338702 url('images/mmsgletter_2_btn.png') no-repeat right -40px;
+        }
+
+        .mmsgLetterContent a.mmsgButton:link span,
+        .mmsgLetterContent a.mmsgButton:visited span {
+            background: url('images/mmsgletter_2_btn.png') no-repeat 0 0;
+        }
+
+        .mmsgLetterContent a.mmsgButton:hover,
+        .mmsgLetterContent a.mmsgButton:active {
+            background: #338702 url('images/mmsgletter_2_btn.png') no-repeat right -120px;
+        }
+
+        .mmsgLetterContent a.mmsgButton:hover span,
+        .mmsgLetterContent a.mmsgButton:active span {
+            background: url('images/mmsgletter_2_btn.png') no-repeat 0 -80px;
+        }
+
+        .mmsgLetterInscribe {
+            padding: 40px 0 0;
+        }
+
+        .mmsgLetterInscribe .mmsgAvatar {
+            float: left;
+        }
+
+        .mmsgLetterInscribe .mmsgName {
+            margin: 0 0 10px;
+        }
+
+        .mmsgLetterInscribe .mmsgSender {
+            margin: 0 0 0 54px;
+        }
+
+        .mmsgLetterInscribe .mmsgInfo {
+            font-size: 12px;
+            margin: 0;
+            line-height: 1.2;
+        }
+
+        .mmsgLetterHeader {
+            height: 23px;
+            /*background: url('images/mmsgletter_2_bg_topline.png') repeat-x 0 0;*/
+            background: 7px 0 repeat-x #FFF;
+            background-image: -webkit-repeating-linear-gradient(135deg, #4882CE, #4882CE 20px, #FFF 20px, #FFF 35px, #EB1B2E 35px, #EB1B2E 55px, #FFF 55px, #FFF 70px);
+            background-image: repeating-linear-gradient(-45deg, #4882CE, #4882CE 20px, #FFF 20px, #FFF 35px, #EB1B2E 35px, #EB1B2E 55px, #FFF 55px, #FFF 70px);
+            background-size: 110px 10px;
+        }
+
+        .mmsgLetterFooter {
+            margin: 16px;
+            text-align: center;
+            font-size: 12px;
+            color: #888;
+            text-shadow: 1px 0px 0px #eee;
+        }
+
+        .mmsgLetterClr {
+            clear: both;
+            overflow: hidden;
+            height: 1px;
+        }
+
+        .mmsgLetterUser {
+            padding: 10px 0;
+        }
+
+        .mmsgLetterUserItem {
+            padding: 0 0 20px 0;
+        }
+
+        .mmsgLetterUserAvatar {
+            height: 40px;
+            border: 1px solid #ccc;
+            padding: 2px;
+            display: block;
+            float: left;
+        }
+
+        .mmsgLetterUserAvatar img {
+            width: 40px;
+            height: 40px;
+        }
+
+        .mmsgLetterInfo {
+            margin-left: 48px;
+        }
+
+        .mmsgLetterName {
+            display: block;
+            color: #5fa207;
+            font-weight: bold;
+            margin-left: 10px;
+        }
+
+        .mmsgLetterDesc {
+            font-size: 12px;
+            float: left;
+            height: 43px;
+            background: url('images/mmsgletter_chat_right.gif') no-repeat right top;
+        }
+
+        .mmsgLetterDesc div {
+            white-space: nowrap;
+            float: left;
+            height: 43px;
+            padding: 0 20px;
+            line-height: 40px;
+            background: url('images/mmsgletter_chat_left.gif') no-repeat left top;
+        }
+
+        .mmsgLetterUser {
+        }
+
+        .mmsgLetterAvatar {
+            float: left;
+        }
+
+        .mmsgLetterInfo {
+            margin: 0 0 0 60px;
+        }
+
+        .mmsgLetterNickName {
+            font-size: 14px;
+            font-weight: bold;
+        }
+
+        .mmsgLetterUin {
+            font-size: 12px;
+            color: #666;
+        }
+
+        .mmsgLetterUser {
+            padding: 10px 0;
+        }
+
+        .mmsgLetterUserItem {
+            padding: 0 0 20px 0;
+        }
+
+        .mmsgLetterUserAvatar {
+            height: 40px;
+            border: 1px solid #ccc;
+            padding: 2px;
+            display: block;
+            float: left;
+        }
+
+        .mmsgLetterUserAvatar img {
+            width: 40px;
+            height: 40px;
+        }
+
+        .mmsgLetterInfo {
+            margin-left: 48px;
+        }
+
+        .mmsgLetterName {
+            display: block;
+            color: #5fa207;
+            font-weight: bold;
+            margin-left: 10px;
+            padding-top: 10px;
+        }
+
+        .mmsgLetterDesc {
+            font-size: 12px;
+            float: left;
+            height: 43px;
+            background: url('images/mmsgletter_chat_right.gif') no-repeat right top;
+        }
+
+        .mmsgLetterDesc div {
+            white-space: nowrap;
+            float: left;
+            height: 43px;
+            padding: 0 20px;
+            line-height: 40px;
+            background: url('images/mmsgletter_chat_left.gif') no-repeat left top;
+        }
+
+        .qmbox style,
+        .qmbox script,
+        .qmbox head,
+        .qmbox link,
+        .qmbox meta {
+            display: none !important;
+        }
+
+        #mailContentContainer .txt {
+            height: auto;
+        }
+
+        a {
+            color: #407700 !important;
+        }
+
+        .renewed {
+            color: #4882CE !important;
+        }
+
+        .notRenewed {
+            color: #EB1B2E !important;
+        }
+
+        .domainDays a {
+            color: #407700 !important;
+        }
+
+        .renewed a,
+        .notRenewed a,
+        .domainDays a {
+            /*color: inherit !important;*/
+            display: inline-block;
+            background: #ffffff;
+            min-width: 22px;
+            text-align: center;
+            padding: 4px 6px;
+            border-radius: 22px;
+            font-size: 14px;
+            font-weight: bold;
+            transition: 0.2s all ease-in-out;
+            text-decoration: none !important;
+            margin-right: 12px;
+        }
+
+        .renewed a {
+            color: inherit !important;
+            border: 2px solid #4882CE;
+        }
+
+        .notRenewed a {
+            color: inherit !important;
+            border: 2px solid #EB1B2E;
+        }
+
+        .domainDays a {
+            border: 2px solid #407700;
+        }
+
+        .renewed a:hover {
+            background-color: #4882CE;
+            color: #ffffff !important;
+        }
+
+        .notRenewed a:hover {
+            background-color: #EB1B2E;
+            color: #ffffff !important;
+        }
+
+        .domainDays a:hover {
+            background-color: #407700;
+            color: #ffffff !important;
+        }
+    </style>
+</head>
+<body>
+<div id="mailContentContainer" class="qmbox qm_con_body_content qqmail_webmail_only" style="">
+    <div style="background-color:#d0d0d0;background-image:url('images/mmsgletter_2_bg.png');text-align:center;padding:40px;">
+        <div class="mmsgLetter">
+            <div class="mmsgLetterHeader" style="height:23px;"></div>
+            <div class="mmsgLetterContent" style="text-align:left;padding: 0 30px;font-size:14px;line-height:1.5;/*background:url('images/ting.jpg') no-repeat top right;background-size: 224px 224px;*/">
+                <div>
+                    <p>主人,我刚刚帮你续期域名啦~
+                        <br>
+                        <br>账户 <a href="#" rel="noopener">%s</a> 这次续期的结果如下
+                        <br><br>
+                        <span class="renewed">%s</span>
+                        <br>
+                        <span class="notRenewed">%s</span>
+                        <br>emmmm,除以上内容外,我还帮小主看了一下:<br><br><span class="domainDays">%s</span>
+                        <br><br>更多信息可以参考 <a href="https://my.freenom.com/domains.php?a=renewals" target="_blank" rel="noopener">Freenom官网</a> 哦~
+                    </p>
+                </div>
+                <div class="mmsgLetterInscribe" style="padding:40px 0 0;">
+                    <img class="mmsgAvatar" src="https://q2.qlogo.cn/headimg_dl?dst_uin=593198779&spec=100" style="float:left;width:40px;height:40px;">
+                    <div class="mmsgSender" style="margin:0 0 0 54px;">
+                        <p class="mmsgName" style="margin:0 0 10px;">Im Robot</p>
+                        <p class="mmsgInfo" style="font-size:12px;margin:0;line-height:1.2;">
+                            邮件推送机器人<br>
+                            <a href="mailto:[email protected]" rel="noopener" target="_blank">[email protected]</a>
+                        </p>
+                    </div>
+                </div>
+            </div>
+            <div class="mmsgLetterFooter" style="margin:16px;text-align:center;font-size:12px;color:#888;text-shadow:1px 0px 0px #eee;"></div>
+        </div>
+    </div>
+</div>
+</body>
 </html>

+ 4 - 6
run

@@ -35,8 +35,7 @@ set_exception_handler('exception_handler');
 require VENDOR_PATH . '/autoload.php';
 
 use Luolongfei\Libs\Log;
-use Luolongfei\Libs\Mail;
-use Luolongfei\Libs\TelegramBot;
+use Luolongfei\Libs\Message;
 
 /**
  * @throws Exception
@@ -45,8 +44,7 @@ function customize_error_handler()
 {
     if (!is_null($error = error_get_last())) {
         Log::error('程序意外终止', $error);
-//        Mail::send('主人,程序意外终止', '具体情况我也不清楚,请查看服务器日志定位问题。');
-//        TelegramBot::send('主人,程序意外终止,具体情况我也不清楚,请查看服务器日志定位问题。');
+        Message::send('具体情况我也不清楚,请查看服务器日志定位问题。', '主人,程序意外终止');
     }
 }
 
@@ -58,8 +56,7 @@ function customize_error_handler()
 function exception_handler($e)
 {
     Log::error('未捕获的异常:' . $e->getMessage());
-    Mail::send('主人,未捕获的异常', "具体的异常内容是:\n" . $e->getMessage());
-    TelegramBot::send('主人,发现未捕获的异常:' . $e->getMessage());
+    Message::send("具体的异常内容是:\n" . $e->getMessage(), '主人,未捕获的异常');
 }
 
 try {
@@ -71,4 +68,5 @@ try {
     $class::instance()->$fn();
 } catch (\Exception $e) {
     system_log(sprintf('执行出错:<red>%s</red>', $e->getMessage()), $e->getTrace());
+    Message::send("执行出错:\n" . $e->getMessage(), '主人,捕获异常');
 }

+ 337 - 0
vendor/composer/InstalledVersions.php

@@ -0,0 +1,337 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <[email protected]>
+ *     Jordi Boggiano <[email protected]>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer;
+
+use Composer\Autoload\ClassLoader;
+use Composer\Semver\VersionParser;
+
+/**
+ * This class is copied in every Composer installed project and available to all
+ *
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
+ *
+ * To require its presence, you can require `composer-runtime-api ^2.0`
+ */
+class InstalledVersions
+{
+    private static $installed;
+    private static $canGetVendors;
+    private static $installedByVendor = array();
+
+    /**
+     * Returns a list of all package names which are present, either by being installed, replaced or provided
+     *
+     * @return string[]
+     * @psalm-return list<string>
+     */
+    public static function getInstalledPackages()
+    {
+        $packages = array();
+        foreach (self::getInstalled() as $installed) {
+            $packages[] = array_keys($installed['versions']);
+        }
+
+        if (1 === \count($packages)) {
+            return $packages[0];
+        }
+
+        return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
+    }
+
+    /**
+     * Returns a list of all package names with a specific type e.g. 'library'
+     *
+     * @param  string   $type
+     * @return string[]
+     * @psalm-return list<string>
+     */
+    public static function getInstalledPackagesByType($type)
+    {
+        $packagesByType = array();
+
+        foreach (self::getInstalled() as $installed) {
+            foreach ($installed['versions'] as $name => $package) {
+                if (isset($package['type']) && $package['type'] === $type) {
+                    $packagesByType[] = $name;
+                }
+            }
+        }
+
+        return $packagesByType;
+    }
+
+    /**
+     * Checks whether the given package is installed
+     *
+     * This also returns true if the package name is provided or replaced by another package
+     *
+     * @param  string $packageName
+     * @param  bool   $includeDevRequirements
+     * @return bool
+     */
+    public static function isInstalled($packageName, $includeDevRequirements = true)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (isset($installed['versions'][$packageName])) {
+                return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Checks whether the given package satisfies a version constraint
+     *
+     * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
+     *
+     *   Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
+     *
+     * @param  VersionParser $parser      Install composer/semver to have access to this class and functionality
+     * @param  string        $packageName
+     * @param  string|null   $constraint  A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
+     * @return bool
+     */
+    public static function satisfies(VersionParser $parser, $packageName, $constraint)
+    {
+        $constraint = $parser->parseConstraints($constraint);
+        $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
+
+        return $provided->matches($constraint);
+    }
+
+    /**
+     * Returns a version constraint representing all the range(s) which are installed for a given package
+     *
+     * It is easier to use this via isInstalled() with the $constraint argument if you need to check
+     * whether a given version of a package is installed, and not just whether it exists
+     *
+     * @param  string $packageName
+     * @return string Version constraint usable with composer/semver
+     */
+    public static function getVersionRanges($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            $ranges = array();
+            if (isset($installed['versions'][$packageName]['pretty_version'])) {
+                $ranges[] = $installed['versions'][$packageName]['pretty_version'];
+            }
+            if (array_key_exists('aliases', $installed['versions'][$packageName])) {
+                $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
+            }
+            if (array_key_exists('replaced', $installed['versions'][$packageName])) {
+                $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
+            }
+            if (array_key_exists('provided', $installed['versions'][$packageName])) {
+                $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
+            }
+
+            return implode(' || ', $ranges);
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+     */
+    public static function getVersion($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            if (!isset($installed['versions'][$packageName]['version'])) {
+                return null;
+            }
+
+            return $installed['versions'][$packageName]['version'];
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+     */
+    public static function getPrettyVersion($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            if (!isset($installed['versions'][$packageName]['pretty_version'])) {
+                return null;
+            }
+
+            return $installed['versions'][$packageName]['pretty_version'];
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
+     */
+    public static function getReference($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            if (!isset($installed['versions'][$packageName]['reference'])) {
+                return null;
+            }
+
+            return $installed['versions'][$packageName]['reference'];
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
+     */
+    public static function getInstallPath($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @return array
+     * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}
+     */
+    public static function getRootPackage()
+    {
+        $installed = self::getInstalled();
+
+        return $installed[0]['root'];
+    }
+
+    /**
+     * Returns the raw installed.php data for custom implementations
+     *
+     * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
+     * @return array[]
+     * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}
+     */
+    public static function getRawData()
+    {
+        @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
+
+        if (null === self::$installed) {
+            // only require the installed.php file if this file is loaded from its dumped location,
+            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+            if (substr(__DIR__, -8, 1) !== 'C') {
+                self::$installed = include __DIR__ . '/installed.php';
+            } else {
+                self::$installed = array();
+            }
+        }
+
+        return self::$installed;
+    }
+
+    /**
+     * Returns the raw data of all installed.php which are currently loaded for custom implementations
+     *
+     * @return array[]
+     * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
+     */
+    public static function getAllRawData()
+    {
+        return self::getInstalled();
+    }
+
+    /**
+     * Lets you reload the static array from another file
+     *
+     * This is only useful for complex integrations in which a project needs to use
+     * this class but then also needs to execute another project's autoloader in process,
+     * and wants to ensure both projects have access to their version of installed.php.
+     *
+     * A typical case would be PHPUnit, where it would need to make sure it reads all
+     * the data it needs from this class, then call reload() with
+     * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
+     * the project in which it runs can then also use this class safely, without
+     * interference between PHPUnit's dependencies and the project's dependencies.
+     *
+     * @param  array[] $data A vendor/composer/installed.php data set
+     * @return void
+     *
+     * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} $data
+     */
+    public static function reload($data)
+    {
+        self::$installed = $data;
+        self::$installedByVendor = array();
+    }
+
+    /**
+     * @return array[]
+     * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
+     */
+    private static function getInstalled()
+    {
+        if (null === self::$canGetVendors) {
+            self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
+        }
+
+        $installed = array();
+
+        if (self::$canGetVendors) {
+            foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+                if (isset(self::$installedByVendor[$vendorDir])) {
+                    $installed[] = self::$installedByVendor[$vendorDir];
+                } elseif (is_file($vendorDir.'/composer/installed.php')) {
+                    $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
+                    if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
+                        self::$installed = $installed[count($installed) - 1];
+                    }
+                }
+            }
+        }
+
+        if (null === self::$installed) {
+            // only require the installed.php file if this file is loaded from its dumped location,
+            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+            if (substr(__DIR__, -8, 1) !== 'C') {
+                self::$installed = require __DIR__ . '/installed.php';
+            } else {
+                self::$installed = array();
+            }
+        }
+        $installed[] = self::$installed;
+
+        return $installed;
+    }
+}

+ 924 - 911
vendor/composer/installed.json

@@ -1,941 +1,954 @@
-[
-    {
-        "name": "bramus/ansi-php",
-        "version": "3.0.3",
-        "version_normalized": "3.0.3.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/bramus/ansi-php.git",
-            "reference": "fb0be33f36053af7454d462e3ddc0a2ac0b2f311"
-        },
-        "dist": {
-            "type": "zip",
-            "url": "https://api.github.com/repos/bramus/ansi-php/zipball/fb0be33f36053af7454d462e3ddc0a2ac0b2f311",
-            "reference": "fb0be33f36053af7454d462e3ddc0a2ac0b2f311",
-            "shasum": "",
-            "mirrors": [
+{
+    "packages": [
+        {
+            "name": "bramus/ansi-php",
+            "version": "3.0.3",
+            "version_normalized": "3.0.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/bramus/ansi-php.git",
+                "reference": "fb0be33f36053af7454d462e3ddc0a2ac0b2f311"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/bramus/ansi-php/zipball/fb0be33f36053af7454d462e3ddc0a2ac0b2f311",
+                "reference": "fb0be33f36053af7454d462e3ddc0a2ac0b2f311",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.4.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0"
+            },
+            "time": "2019-12-03T09:04:38+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Bramus\\Ansi\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
                 {
-                    "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
-                    "preferred": true
+                    "name": "Bramus Van Damme",
+                    "email": "[email protected]",
+                    "homepage": "https://www.bram.us/"
                 }
-            ]
-        },
-        "require": {
-            "php": ">=5.4.0"
-        },
-        "require-dev": {
-            "phpunit/phpunit": "~4.0"
-        },
-        "time": "2019-12-03T09:04:38+00:00",
-        "type": "library",
-        "installation-source": "dist",
-        "autoload": {
-            "psr-4": {
-                "Bramus\\Ansi\\": "src/"
-            }
-        },
-        "notification-url": "https://packagist.org/downloads/",
-        "license": [
-            "MIT"
-        ],
-        "authors": [
-            {
-                "name": "Bramus Van Damme",
-                "email": "[email protected]",
-                "homepage": "https://www.bram.us/"
-            }
-        ],
-        "description": "ANSI Control Functions and ANSI Control Sequences (Colors, Erasing, etc.) for PHP CLI Apps"
-    },
-    {
-        "name": "bramus/monolog-colored-line-formatter",
-        "version": "2.0.3",
-        "version_normalized": "2.0.3.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/bramus/monolog-colored-line-formatter.git",
-            "reference": "6bff15eee00afe2690642535b0f1541f10852c2b"
-        },
-        "dist": {
-            "type": "zip",
-            "url": "https://api.github.com/repos/bramus/monolog-colored-line-formatter/zipball/6bff15eee00afe2690642535b0f1541f10852c2b",
-            "reference": "6bff15eee00afe2690642535b0f1541f10852c2b",
-            "shasum": "",
-            "mirrors": [
+            ],
+            "description": "ANSI Control Functions and ANSI Control Sequences (Colors, Erasing, etc.) for PHP CLI Apps",
+            "install-path": "../bramus/ansi-php"
+        },
+        {
+            "name": "bramus/monolog-colored-line-formatter",
+            "version": "2.0.3",
+            "version_normalized": "2.0.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/bramus/monolog-colored-line-formatter.git",
+                "reference": "6bff15eee00afe2690642535b0f1541f10852c2b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/bramus/monolog-colored-line-formatter/zipball/6bff15eee00afe2690642535b0f1541f10852c2b",
+                "reference": "6bff15eee00afe2690642535b0f1541f10852c2b",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "bramus/ansi-php": "~3.0",
+                "php": ">=5.4.0"
+            },
+            "require-dev": {
+                "monolog/monolog": "~1.0",
+                "phpunit/phpunit": "~4.0"
+            },
+            "time": "2015-01-07T22:12:35+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Bramus\\Monolog\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
                 {
-                    "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
-                    "preferred": true
+                    "name": "Bramus Van Damme",
+                    "email": "[email protected]",
+                    "homepage": "https://www.bram.us/"
                 }
-            ]
-        },
-        "require": {
-            "bramus/ansi-php": "~3.0",
-            "php": ">=5.4.0"
-        },
-        "require-dev": {
-            "monolog/monolog": "~1.0",
-            "phpunit/phpunit": "~4.0"
-        },
-        "time": "2015-01-07T22:12:35+00:00",
-        "type": "library",
-        "installation-source": "dist",
-        "autoload": {
-            "psr-4": {
-                "Bramus\\Monolog\\": "src/"
-            }
-        },
-        "notification-url": "https://packagist.org/downloads/",
-        "license": [
-            "MIT"
-        ],
-        "authors": [
-            {
-                "name": "Bramus Van Damme",
-                "email": "[email protected]",
-                "homepage": "https://www.bram.us/"
-            }
-        ],
-        "description": "Colored Line Formatter for Monolog"
-    },
-    {
-        "name": "guzzlehttp/guzzle",
-        "version": "6.5.2",
-        "version_normalized": "6.5.2.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/guzzle/guzzle.git",
-            "reference": "43ece0e75098b7ecd8d13918293029e555a50f82"
-        },
-        "dist": {
-            "type": "zip",
-            "url": "https://api.github.com/repos/guzzle/guzzle/zipball/43ece0e75098b7ecd8d13918293029e555a50f82",
-            "reference": "43ece0e75098b7ecd8d13918293029e555a50f82",
-            "shasum": "",
-            "mirrors": [
+            ],
+            "description": "Colored Line Formatter for Monolog",
+            "install-path": "../bramus/monolog-colored-line-formatter"
+        },
+        {
+            "name": "guzzlehttp/guzzle",
+            "version": "6.5.2",
+            "version_normalized": "6.5.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/guzzle.git",
+                "reference": "43ece0e75098b7ecd8d13918293029e555a50f82"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/43ece0e75098b7ecd8d13918293029e555a50f82",
+                "reference": "43ece0e75098b7ecd8d13918293029e555a50f82",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-json": "*",
+                "guzzlehttp/promises": "^1.0",
+                "guzzlehttp/psr7": "^1.6.1",
+                "php": ">=5.5"
+            },
+            "require-dev": {
+                "ext-curl": "*",
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
+                "psr/log": "^1.1"
+            },
+            "suggest": {
+                "ext-intl": "Required for Internationalized Domain Name (IDN) support",
+                "psr/log": "Required for using the Log middleware"
+            },
+            "time": "2019-12-23T11:57:10+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "6.5-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
                 {
-                    "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
-                    "preferred": true
+                    "name": "Michael Dowling",
+                    "email": "[email protected]",
+                    "homepage": "https://github.com/mtdowling"
                 }
-            ]
-        },
-        "require": {
-            "ext-json": "*",
-            "guzzlehttp/promises": "^1.0",
-            "guzzlehttp/psr7": "^1.6.1",
-            "php": ">=5.5"
-        },
-        "require-dev": {
-            "ext-curl": "*",
-            "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
-            "psr/log": "^1.1"
-        },
-        "suggest": {
-            "ext-intl": "Required for Internationalized Domain Name (IDN) support",
-            "psr/log": "Required for using the Log middleware"
-        },
-        "time": "2019-12-23T11:57:10+00:00",
-        "type": "library",
-        "extra": {
-            "branch-alias": {
-                "dev-master": "6.5-dev"
-            }
-        },
-        "installation-source": "dist",
-        "autoload": {
-            "psr-4": {
-                "GuzzleHttp\\": "src/"
-            },
-            "files": [
-                "src/functions_include.php"
-            ]
-        },
-        "notification-url": "https://packagist.org/downloads/",
-        "license": [
-            "MIT"
-        ],
-        "authors": [
-            {
-                "name": "Michael Dowling",
-                "email": "[email protected]",
-                "homepage": "https://github.com/mtdowling"
-            }
-        ],
-        "description": "Guzzle is a PHP HTTP client library",
-        "homepage": "http://guzzlephp.org/",
-        "keywords": [
-            "client",
-            "curl",
-            "framework",
-            "http",
-            "http client",
-            "rest",
-            "web service"
-        ]
-    },
-    {
-        "name": "guzzlehttp/promises",
-        "version": "v1.3.1",
-        "version_normalized": "1.3.1.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/guzzle/promises.git",
-            "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
-        },
-        "dist": {
-            "type": "zip",
-            "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
-            "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
-            "shasum": "",
-            "mirrors": [
+            ],
+            "description": "Guzzle is a PHP HTTP client library",
+            "homepage": "http://guzzlephp.org/",
+            "keywords": [
+                "client",
+                "curl",
+                "framework",
+                "http",
+                "http client",
+                "rest",
+                "web service"
+            ],
+            "install-path": "../guzzlehttp/guzzle"
+        },
+        {
+            "name": "guzzlehttp/promises",
+            "version": "v1.3.1",
+            "version_normalized": "1.3.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/promises.git",
+                "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+                "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.5.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.0"
+            },
+            "time": "2016-12-20T10:07:11+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Promise\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
                 {
-                    "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
-                    "preferred": true
+                    "name": "Michael Dowling",
+                    "email": "[email protected]",
+                    "homepage": "https://github.com/mtdowling"
                 }
-            ]
-        },
-        "require": {
-            "php": ">=5.5.0"
-        },
-        "require-dev": {
-            "phpunit/phpunit": "^4.0"
-        },
-        "time": "2016-12-20T10:07:11+00:00",
-        "type": "library",
-        "extra": {
-            "branch-alias": {
-                "dev-master": "1.4-dev"
-            }
-        },
-        "installation-source": "dist",
-        "autoload": {
-            "psr-4": {
-                "GuzzleHttp\\Promise\\": "src/"
-            },
-            "files": [
-                "src/functions_include.php"
-            ]
-        },
-        "notification-url": "https://packagist.org/downloads/",
-        "license": [
-            "MIT"
-        ],
-        "authors": [
-            {
-                "name": "Michael Dowling",
-                "email": "[email protected]",
-                "homepage": "https://github.com/mtdowling"
-            }
-        ],
-        "description": "Guzzle promises library",
-        "keywords": [
-            "promise"
-        ]
-    },
-    {
-        "name": "guzzlehttp/psr7",
-        "version": "1.6.1",
-        "version_normalized": "1.6.1.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/guzzle/psr7.git",
-            "reference": "239400de7a173fe9901b9ac7c06497751f00727a"
-        },
-        "dist": {
-            "type": "zip",
-            "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a",
-            "reference": "239400de7a173fe9901b9ac7c06497751f00727a",
-            "shasum": "",
-            "mirrors": [
+            ],
+            "description": "Guzzle promises library",
+            "keywords": [
+                "promise"
+            ],
+            "install-path": "../guzzlehttp/promises"
+        },
+        {
+            "name": "guzzlehttp/psr7",
+            "version": "1.6.1",
+            "version_normalized": "1.6.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/psr7.git",
+                "reference": "239400de7a173fe9901b9ac7c06497751f00727a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a",
+                "reference": "239400de7a173fe9901b9ac7c06497751f00727a",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.4.0",
+                "psr/http-message": "~1.0",
+                "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
+            },
+            "provide": {
+                "psr/http-message-implementation": "1.0"
+            },
+            "require-dev": {
+                "ext-zlib": "*",
+                "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
+            },
+            "suggest": {
+                "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses"
+            },
+            "time": "2019-07-01T23:21:34+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.6-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Psr7\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "[email protected]",
+                    "homepage": "https://github.com/mtdowling"
+                },
                 {
-                    "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
-                    "preferred": true
+                    "name": "Tobias Schultze",
+                    "homepage": "https://github.com/Tobion"
                 }
-            ]
-        },
-        "require": {
-            "php": ">=5.4.0",
-            "psr/http-message": "~1.0",
-            "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
-        },
-        "provide": {
-            "psr/http-message-implementation": "1.0"
-        },
-        "require-dev": {
-            "ext-zlib": "*",
-            "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
-        },
-        "suggest": {
-            "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses"
-        },
-        "time": "2019-07-01T23:21:34+00:00",
-        "type": "library",
-        "extra": {
-            "branch-alias": {
-                "dev-master": "1.6-dev"
-            }
-        },
-        "installation-source": "dist",
-        "autoload": {
-            "psr-4": {
-                "GuzzleHttp\\Psr7\\": "src/"
-            },
-            "files": [
-                "src/functions_include.php"
-            ]
-        },
-        "notification-url": "https://packagist.org/downloads/",
-        "license": [
-            "MIT"
-        ],
-        "authors": [
-            {
-                "name": "Michael Dowling",
-                "email": "[email protected]",
-                "homepage": "https://github.com/mtdowling"
-            },
-            {
-                "name": "Tobias Schultze",
-                "homepage": "https://github.com/Tobion"
-            }
-        ],
-        "description": "PSR-7 message implementation that also provides common utility methods",
-        "keywords": [
-            "http",
-            "message",
-            "psr-7",
-            "request",
-            "response",
-            "stream",
-            "uri",
-            "url"
-        ]
-    },
-    {
-        "name": "kevinlebrun/colors.php",
-        "version": "1.0.3",
-        "version_normalized": "1.0.3.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/kevinlebrun/colors.php.git",
-            "reference": "cdda5eee41314b87cd5a8bb91b1ffc7c0210e673"
-        },
-        "dist": {
-            "type": "zip",
-            "url": "https://api.github.com/repos/kevinlebrun/colors.php/zipball/cdda5eee41314b87cd5a8bb91b1ffc7c0210e673",
-            "reference": "cdda5eee41314b87cd5a8bb91b1ffc7c0210e673",
-            "shasum": "",
-            "mirrors": [
+            ],
+            "description": "PSR-7 message implementation that also provides common utility methods",
+            "keywords": [
+                "http",
+                "message",
+                "psr-7",
+                "request",
+                "response",
+                "stream",
+                "uri",
+                "url"
+            ],
+            "install-path": "../guzzlehttp/psr7"
+        },
+        {
+            "name": "kevinlebrun/colors.php",
+            "version": "1.0.3",
+            "version_normalized": "1.0.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/kevinlebrun/colors.php.git",
+                "reference": "cdda5eee41314b87cd5a8bb91b1ffc7c0210e673"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/kevinlebrun/colors.php/zipball/cdda5eee41314b87cd5a8bb91b1ffc7c0210e673",
+                "reference": "cdda5eee41314b87cd5a8bb91b1ffc7c0210e673",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "3.7.*",
+                "satooshi/php-coveralls": "1.0.*",
+                "squizlabs/php_codesniffer": "1.*"
+            },
+            "time": "2018-05-30T08:34:23+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-0": {
+                    "Colors": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
                 {
-                    "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
-                    "preferred": true
+                    "name": "Kevin Le Brun",
+                    "email": "[email protected]",
+                    "homepage": "http://kevinlebrun.fr",
+                    "role": "developer"
                 }
-            ]
-        },
-        "require": {
-            "php": ">=5.3.0"
-        },
-        "require-dev": {
-            "phpunit/phpunit": "3.7.*",
-            "satooshi/php-coveralls": "1.0.*",
-            "squizlabs/php_codesniffer": "1.*"
-        },
-        "time": "2018-05-30T08:34:23+00:00",
-        "type": "library",
-        "installation-source": "dist",
-        "autoload": {
-            "psr-0": {
-                "Colors": "src/"
-            }
-        },
-        "notification-url": "https://packagist.org/downloads/",
-        "license": [
-            "MIT"
-        ],
-        "authors": [
-            {
-                "name": "Kevin Le Brun",
-                "email": "[email protected]",
-                "homepage": "http://kevinlebrun.fr",
-                "role": "developer"
-            }
-        ],
-        "description": "Colors for PHP CLI scripts",
-        "homepage": "https://github.com/kevinlebrun/colors.php",
-        "keywords": [
-            "cli",
-            "color",
-            "colors",
-            "console",
-            "shell"
-        ]
-    },
-    {
-        "name": "monolog/monolog",
-        "version": "1.25.3",
-        "version_normalized": "1.25.3.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/Seldaek/monolog.git",
-            "reference": "fa82921994db851a8becaf3787a9e73c5976b6f1"
-        },
-        "dist": {
-            "type": "zip",
-            "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fa82921994db851a8becaf3787a9e73c5976b6f1",
-            "reference": "fa82921994db851a8becaf3787a9e73c5976b6f1",
-            "shasum": "",
-            "mirrors": [
+            ],
+            "description": "Colors for PHP CLI scripts",
+            "homepage": "https://github.com/kevinlebrun/colors.php",
+            "keywords": [
+                "cli",
+                "color",
+                "colors",
+                "console",
+                "shell"
+            ],
+            "install-path": "../kevinlebrun/colors.php"
+        },
+        {
+            "name": "monolog/monolog",
+            "version": "1.25.3",
+            "version_normalized": "1.25.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Seldaek/monolog.git",
+                "reference": "fa82921994db851a8becaf3787a9e73c5976b6f1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fa82921994db851a8becaf3787a9e73c5976b6f1",
+                "reference": "fa82921994db851a8becaf3787a9e73c5976b6f1",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.0",
+                "psr/log": "~1.0"
+            },
+            "provide": {
+                "psr/log-implementation": "1.0.0"
+            },
+            "require-dev": {
+                "aws/aws-sdk-php": "^2.4.9 || ^3.0",
+                "doctrine/couchdb": "~1.0@dev",
+                "graylog2/gelf-php": "~1.0",
+                "jakub-onderka/php-parallel-lint": "0.9",
+                "php-amqplib/php-amqplib": "~2.4",
+                "php-console/php-console": "^3.1.3",
+                "phpunit/phpunit": "~4.5",
+                "phpunit/phpunit-mock-objects": "2.3.0",
+                "ruflin/elastica": ">=0.90 <3.0",
+                "sentry/sentry": "^0.13",
+                "swiftmailer/swiftmailer": "^5.3|^6.0"
+            },
+            "suggest": {
+                "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
+                "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
+                "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
+                "ext-mongo": "Allow sending log messages to a MongoDB server",
+                "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
+                "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
+                "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
+                "php-console/php-console": "Allow sending log messages to Google Chrome",
+                "rollbar/rollbar": "Allow sending log messages to Rollbar",
+                "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
+                "sentry/sentry": "Allow sending log messages to a Sentry server"
+            },
+            "time": "2019-12-20T14:15:16+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Monolog\\": "src/Monolog"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
                 {
-                    "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
-                    "preferred": true
+                    "name": "Jordi Boggiano",
+                    "email": "[email protected]",
+                    "homepage": "http://seld.be"
                 }
-            ]
-        },
-        "require": {
-            "php": ">=5.3.0",
-            "psr/log": "~1.0"
-        },
-        "provide": {
-            "psr/log-implementation": "1.0.0"
-        },
-        "require-dev": {
-            "aws/aws-sdk-php": "^2.4.9 || ^3.0",
-            "doctrine/couchdb": "~1.0@dev",
-            "graylog2/gelf-php": "~1.0",
-            "jakub-onderka/php-parallel-lint": "0.9",
-            "php-amqplib/php-amqplib": "~2.4",
-            "php-console/php-console": "^3.1.3",
-            "phpunit/phpunit": "~4.5",
-            "phpunit/phpunit-mock-objects": "2.3.0",
-            "ruflin/elastica": ">=0.90 <3.0",
-            "sentry/sentry": "^0.13",
-            "swiftmailer/swiftmailer": "^5.3|^6.0"
-        },
-        "suggest": {
-            "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
-            "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
-            "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
-            "ext-mongo": "Allow sending log messages to a MongoDB server",
-            "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
-            "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
-            "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
-            "php-console/php-console": "Allow sending log messages to Google Chrome",
-            "rollbar/rollbar": "Allow sending log messages to Rollbar",
-            "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
-            "sentry/sentry": "Allow sending log messages to a Sentry server"
-        },
-        "time": "2019-12-20T14:15:16+00:00",
-        "type": "library",
-        "extra": {
-            "branch-alias": {
-                "dev-master": "2.0.x-dev"
-            }
-        },
-        "installation-source": "dist",
-        "autoload": {
-            "psr-4": {
-                "Monolog\\": "src/Monolog"
-            }
-        },
-        "notification-url": "https://packagist.org/downloads/",
-        "license": [
-            "MIT"
-        ],
-        "authors": [
-            {
-                "name": "Jordi Boggiano",
-                "email": "[email protected]",
-                "homepage": "http://seld.be"
-            }
-        ],
-        "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
-        "homepage": "http://github.com/Seldaek/monolog",
-        "keywords": [
-            "log",
-            "logging",
-            "psr-3"
-        ]
-    },
-    {
-        "name": "phpmailer/phpmailer",
-        "version": "v6.1.4",
-        "version_normalized": "6.1.4.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/PHPMailer/PHPMailer.git",
-            "reference": "c5e61d0729507049cec9673aa1a679f9adefd683"
-        },
-        "dist": {
-            "type": "zip",
-            "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/c5e61d0729507049cec9673aa1a679f9adefd683",
-            "reference": "c5e61d0729507049cec9673aa1a679f9adefd683",
-            "shasum": "",
-            "mirrors": [
+            ],
+            "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
+            "homepage": "http://github.com/Seldaek/monolog",
+            "keywords": [
+                "log",
+                "logging",
+                "psr-3"
+            ],
+            "install-path": "../monolog/monolog"
+        },
+        {
+            "name": "phpmailer/phpmailer",
+            "version": "v6.1.6",
+            "version_normalized": "6.1.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHPMailer/PHPMailer.git",
+                "reference": "c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3",
+                "reference": "c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3",
+                "shasum": ""
+            },
+            "require": {
+                "ext-ctype": "*",
+                "ext-filter": "*",
+                "php": ">=5.5.0"
+            },
+            "require-dev": {
+                "doctrine/annotations": "^1.2",
+                "friendsofphp/php-cs-fixer": "^2.2",
+                "phpunit/phpunit": "^4.8 || ^5.7"
+            },
+            "suggest": {
+                "ext-mbstring": "Needed to send email in multibyte encoding charset",
+                "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
+                "league/oauth2-google": "Needed for Google XOAUTH2 authentication",
+                "psr/log": "For optional PSR-3 debug logging",
+                "stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication",
+                "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)"
+            },
+            "time": "2020-05-27T12:24:03+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "PHPMailer\\PHPMailer\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-2.1-only"
+            ],
+            "authors": [
+                {
+                    "name": "Marcus Bointon",
+                    "email": "[email protected]"
+                },
                 {
-                    "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
-                    "preferred": true
+                    "name": "Jim Jagielski",
+                    "email": "[email protected]"
+                },
+                {
+                    "name": "Andy Prevost",
+                    "email": "[email protected]"
+                },
+                {
+                    "name": "Brent R. Matzelle"
                 }
-            ]
-        },
-        "require": {
-            "ext-ctype": "*",
-            "ext-filter": "*",
-            "php": ">=5.5.0"
-        },
-        "require-dev": {
-            "doctrine/annotations": "^1.2",
-            "friendsofphp/php-cs-fixer": "^2.2",
-            "phpunit/phpunit": "^4.8 || ^5.7"
-        },
-        "suggest": {
-            "ext-mbstring": "Needed to send email in multibyte encoding charset",
-            "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
-            "league/oauth2-google": "Needed for Google XOAUTH2 authentication",
-            "psr/log": "For optional PSR-3 debug logging",
-            "stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication",
-            "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)"
-        },
-        "time": "2019-12-10T11:17:38+00:00",
-        "type": "library",
-        "installation-source": "dist",
-        "autoload": {
-            "psr-4": {
-                "PHPMailer\\PHPMailer\\": "src/"
-            }
-        },
-        "notification-url": "https://packagist.org/downloads/",
-        "license": [
-            "LGPL-2.1-only"
-        ],
-        "authors": [
-            {
-                "name": "Marcus Bointon",
-                "email": "[email protected]"
-            },
-            {
-                "name": "Jim Jagielski",
-                "email": "[email protected]"
-            },
-            {
-                "name": "Andy Prevost",
-                "email": "[email protected]"
-            },
-            {
-                "name": "Brent R. Matzelle"
-            }
-        ],
-        "description": "PHPMailer is a full-featured email creation and transfer class for PHP"
-    },
-    {
-        "name": "phpoption/phpoption",
-        "version": "1.7.2",
-        "version_normalized": "1.7.2.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/schmittjoh/php-option.git",
-            "reference": "77f7c4d2e65413aff5b5a8cc8b3caf7a28d81959"
-        },
-        "dist": {
-            "type": "zip",
-            "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/77f7c4d2e65413aff5b5a8cc8b3caf7a28d81959",
-            "reference": "77f7c4d2e65413aff5b5a8cc8b3caf7a28d81959",
-            "shasum": "",
-            "mirrors": [
+            ],
+            "description": "PHPMailer is a full-featured email creation and transfer class for PHP",
+            "install-path": "../phpmailer/phpmailer"
+        },
+        {
+            "name": "phpoption/phpoption",
+            "version": "1.7.2",
+            "version_normalized": "1.7.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/schmittjoh/php-option.git",
+                "reference": "77f7c4d2e65413aff5b5a8cc8b3caf7a28d81959"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/77f7c4d2e65413aff5b5a8cc8b3caf7a28d81959",
+                "reference": "77f7c4d2e65413aff5b5a8cc8b3caf7a28d81959",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^5.5.9 || ^7.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.3",
+                "phpunit/phpunit": "^4.8.35 || ^5.0 || ^6.0 || ^7.0"
+            },
+            "time": "2019-12-15T19:35:24+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.7-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "PhpOption\\": "src/PhpOption/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
                 {
-                    "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
-                    "preferred": true
+                    "name": "Johannes M. Schmitt",
+                    "email": "[email protected]"
+                },
+                {
+                    "name": "Graham Campbell",
+                    "email": "[email protected]"
                 }
-            ]
-        },
-        "require": {
-            "php": "^5.5.9 || ^7.0"
-        },
-        "require-dev": {
-            "bamarni/composer-bin-plugin": "^1.3",
-            "phpunit/phpunit": "^4.8.35 || ^5.0 || ^6.0 || ^7.0"
-        },
-        "time": "2019-12-15T19:35:24+00:00",
-        "type": "library",
-        "extra": {
-            "branch-alias": {
-                "dev-master": "1.7-dev"
-            }
-        },
-        "installation-source": "dist",
-        "autoload": {
-            "psr-4": {
-                "PhpOption\\": "src/PhpOption/"
-            }
-        },
-        "notification-url": "https://packagist.org/downloads/",
-        "license": [
-            "Apache-2.0"
-        ],
-        "authors": [
-            {
-                "name": "Johannes M. Schmitt",
-                "email": "[email protected]"
-            },
-            {
-                "name": "Graham Campbell",
-                "email": "[email protected]"
-            }
-        ],
-        "description": "Option Type for PHP",
-        "keywords": [
-            "language",
-            "option",
-            "php",
-            "type"
-        ]
-    },
-    {
-        "name": "predis/predis",
-        "version": "v1.1.1",
-        "version_normalized": "1.1.1.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/nrk/predis.git",
-            "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1"
-        },
-        "dist": {
-            "type": "zip",
-            "url": "https://api.github.com/repos/nrk/predis/zipball/f0210e38881631afeafb56ab43405a92cafd9fd1",
-            "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1",
-            "shasum": "",
-            "mirrors": [
+            ],
+            "description": "Option Type for PHP",
+            "keywords": [
+                "language",
+                "option",
+                "php",
+                "type"
+            ],
+            "install-path": "../phpoption/phpoption"
+        },
+        {
+            "name": "predis/predis",
+            "version": "v1.1.1",
+            "version_normalized": "1.1.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nrk/predis.git",
+                "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nrk/predis/zipball/f0210e38881631afeafb56ab43405a92cafd9fd1",
+                "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.9"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.8"
+            },
+            "suggest": {
+                "ext-curl": "Allows access to Webdis when paired with phpiredis",
+                "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol"
+            },
+            "time": "2016-06-16T16:22:20+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Predis\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
                 {
-                    "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
-                    "preferred": true
+                    "name": "Daniele Alessandri",
+                    "email": "[email protected]",
+                    "homepage": "http://clorophilla.net"
                 }
-            ]
-        },
-        "require": {
-            "php": ">=5.3.9"
-        },
-        "require-dev": {
-            "phpunit/phpunit": "~4.8"
-        },
-        "suggest": {
-            "ext-curl": "Allows access to Webdis when paired with phpiredis",
-            "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol"
-        },
-        "time": "2016-06-16T16:22:20+00:00",
-        "type": "library",
-        "installation-source": "dist",
-        "autoload": {
-            "psr-4": {
-                "Predis\\": "src/"
-            }
-        },
-        "notification-url": "https://packagist.org/downloads/",
-        "license": [
-            "MIT"
-        ],
-        "authors": [
-            {
-                "name": "Daniele Alessandri",
-                "email": "[email protected]",
-                "homepage": "http://clorophilla.net"
-            }
-        ],
-        "description": "Flexible and feature-complete Redis client for PHP and HHVM",
-        "homepage": "http://github.com/nrk/predis",
-        "keywords": [
-            "nosql",
-            "predis",
-            "redis"
-        ]
-    },
-    {
-        "name": "psr/http-message",
-        "version": "1.0.1",
-        "version_normalized": "1.0.1.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/php-fig/http-message.git",
-            "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
-        },
-        "dist": {
-            "type": "zip",
-            "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
-            "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
-            "shasum": "",
-            "mirrors": [
+            ],
+            "description": "Flexible and feature-complete Redis client for PHP and HHVM",
+            "homepage": "http://github.com/nrk/predis",
+            "keywords": [
+                "nosql",
+                "predis",
+                "redis"
+            ],
+            "install-path": "../predis/predis"
+        },
+        {
+            "name": "psr/http-message",
+            "version": "1.0.1",
+            "version_normalized": "1.0.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-message.git",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "time": "2016-08-06T14:39:51+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
                 {
-                    "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
-                    "preferred": true
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
                 }
-            ]
-        },
-        "require": {
-            "php": ">=5.3.0"
-        },
-        "time": "2016-08-06T14:39:51+00:00",
-        "type": "library",
-        "extra": {
-            "branch-alias": {
-                "dev-master": "1.0.x-dev"
-            }
-        },
-        "installation-source": "dist",
-        "autoload": {
-            "psr-4": {
-                "Psr\\Http\\Message\\": "src/"
-            }
-        },
-        "notification-url": "https://packagist.org/downloads/",
-        "license": [
-            "MIT"
-        ],
-        "authors": [
-            {
-                "name": "PHP-FIG",
-                "homepage": "http://www.php-fig.org/"
-            }
-        ],
-        "description": "Common interface for HTTP messages",
-        "homepage": "https://github.com/php-fig/http-message",
-        "keywords": [
-            "http",
-            "http-message",
-            "psr",
-            "psr-7",
-            "request",
-            "response"
-        ]
-    },
-    {
-        "name": "psr/log",
-        "version": "1.1.2",
-        "version_normalized": "1.1.2.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/php-fig/log.git",
-            "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801"
-        },
-        "dist": {
-            "type": "zip",
-            "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801",
-            "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801",
-            "shasum": "",
-            "mirrors": [
+            ],
+            "description": "Common interface for HTTP messages",
+            "homepage": "https://github.com/php-fig/http-message",
+            "keywords": [
+                "http",
+                "http-message",
+                "psr",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "install-path": "../psr/http-message"
+        },
+        {
+            "name": "psr/log",
+            "version": "1.1.2",
+            "version_normalized": "1.1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/log.git",
+                "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801",
+                "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "time": "2019-11-01T11:05:21+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1.x-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Log\\": "Psr/Log/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
                 {
-                    "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
-                    "preferred": true
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
                 }
-            ]
-        },
-        "require": {
-            "php": ">=5.3.0"
-        },
-        "time": "2019-11-01T11:05:21+00:00",
-        "type": "library",
-        "extra": {
-            "branch-alias": {
-                "dev-master": "1.1.x-dev"
-            }
-        },
-        "installation-source": "dist",
-        "autoload": {
-            "psr-4": {
-                "Psr\\Log\\": "Psr/Log/"
-            }
-        },
-        "notification-url": "https://packagist.org/downloads/",
-        "license": [
-            "MIT"
-        ],
-        "authors": [
-            {
-                "name": "PHP-FIG",
-                "homepage": "http://www.php-fig.org/"
-            }
-        ],
-        "description": "Common interface for logging libraries",
-        "homepage": "https://github.com/php-fig/log",
-        "keywords": [
-            "log",
-            "psr",
-            "psr-3"
-        ]
-    },
-    {
-        "name": "ralouphie/getallheaders",
-        "version": "3.0.3",
-        "version_normalized": "3.0.3.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/ralouphie/getallheaders.git",
-            "reference": "120b605dfeb996808c31b6477290a714d356e822"
-        },
-        "dist": {
-            "type": "zip",
-            "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
-            "reference": "120b605dfeb996808c31b6477290a714d356e822",
-            "shasum": "",
-            "mirrors": [
+            ],
+            "description": "Common interface for logging libraries",
+            "homepage": "https://github.com/php-fig/log",
+            "keywords": [
+                "log",
+                "psr",
+                "psr-3"
+            ],
+            "install-path": "../psr/log"
+        },
+        {
+            "name": "ralouphie/getallheaders",
+            "version": "3.0.3",
+            "version_normalized": "3.0.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ralouphie/getallheaders.git",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.6"
+            },
+            "require-dev": {
+                "php-coveralls/php-coveralls": "^2.1",
+                "phpunit/phpunit": "^5 || ^6.5"
+            },
+            "time": "2019-03-08T08:55:37+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "files": [
+                    "src/getallheaders.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
                 {
-                    "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
-                    "preferred": true
+                    "name": "Ralph Khattar",
+                    "email": "[email protected]"
                 }
-            ]
-        },
-        "require": {
-            "php": ">=5.6"
-        },
-        "require-dev": {
-            "php-coveralls/php-coveralls": "^2.1",
-            "phpunit/phpunit": "^5 || ^6.5"
-        },
-        "time": "2019-03-08T08:55:37+00:00",
-        "type": "library",
-        "installation-source": "dist",
-        "autoload": {
-            "files": [
-                "src/getallheaders.php"
-            ]
-        },
-        "notification-url": "https://packagist.org/downloads/",
-        "license": [
-            "MIT"
-        ],
-        "authors": [
-            {
-                "name": "Ralph Khattar",
-                "email": "[email protected]"
-            }
-        ],
-        "description": "A polyfill for getallheaders."
-    },
-    {
-        "name": "symfony/polyfill-ctype",
-        "version": "v1.13.1",
-        "version_normalized": "1.13.1.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/symfony/polyfill-ctype.git",
-            "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3"
-        },
-        "dist": {
-            "type": "zip",
-            "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
-            "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
-            "shasum": "",
-            "mirrors": [
+            ],
+            "description": "A polyfill for getallheaders.",
+            "install-path": "../ralouphie/getallheaders"
+        },
+        {
+            "name": "symfony/polyfill-ctype",
+            "version": "v1.13.1",
+            "version_normalized": "1.13.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-ctype.git",
+                "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
+                "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "suggest": {
+                "ext-ctype": "For best performance"
+            },
+            "time": "2019-11-27T13:56:44+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.13-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Ctype\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
                 {
-                    "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
-                    "preferred": true
+                    "name": "Gert de Pagter",
+                    "email": "[email protected]"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
                 }
-            ]
-        },
-        "require": {
-            "php": ">=5.3.3"
-        },
-        "suggest": {
-            "ext-ctype": "For best performance"
-        },
-        "time": "2019-11-27T13:56:44+00:00",
-        "type": "library",
-        "extra": {
-            "branch-alias": {
-                "dev-master": "1.13-dev"
-            }
-        },
-        "installation-source": "dist",
-        "autoload": {
-            "psr-4": {
-                "Symfony\\Polyfill\\Ctype\\": ""
-            },
-            "files": [
-                "bootstrap.php"
-            ]
-        },
-        "notification-url": "https://packagist.org/downloads/",
-        "license": [
-            "MIT"
-        ],
-        "authors": [
-            {
-                "name": "Gert de Pagter",
-                "email": "[email protected]"
-            },
-            {
-                "name": "Symfony Community",
-                "homepage": "https://symfony.com/contributors"
-            }
-        ],
-        "description": "Symfony polyfill for ctype functions",
-        "homepage": "https://symfony.com",
-        "keywords": [
-            "compatibility",
-            "ctype",
-            "polyfill",
-            "portable"
-        ]
-    },
-    {
-        "name": "vlucas/phpdotenv",
-        "version": "v3.6.0",
-        "version_normalized": "3.6.0.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/vlucas/phpdotenv.git",
-            "reference": "1bdf24f065975594f6a117f0f1f6cabf1333b156"
-        },
-        "dist": {
-            "type": "zip",
-            "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/1bdf24f065975594f6a117f0f1f6cabf1333b156",
-            "reference": "1bdf24f065975594f6a117f0f1f6cabf1333b156",
-            "shasum": "",
-            "mirrors": [
+            ],
+            "description": "Symfony polyfill for ctype functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "ctype",
+                "polyfill",
+                "portable"
+            ],
+            "install-path": "../symfony/polyfill-ctype"
+        },
+        {
+            "name": "vlucas/phpdotenv",
+            "version": "v3.6.0",
+            "version_normalized": "3.6.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/vlucas/phpdotenv.git",
+                "reference": "1bdf24f065975594f6a117f0f1f6cabf1333b156"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/1bdf24f065975594f6a117f0f1f6cabf1333b156",
+                "reference": "1bdf24f065975594f6a117f0f1f6cabf1333b156",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^5.4 || ^7.0",
+                "phpoption/phpoption": "^1.5",
+                "symfony/polyfill-ctype": "^1.9"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8.35 || ^5.0 || ^6.0 || ^7.0"
+            },
+            "time": "2019-09-10T21:37:39+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.6-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Dotenv\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
                 {
-                    "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
-                    "preferred": true
+                    "name": "Graham Campbell",
+                    "email": "[email protected]",
+                    "homepage": "https://gjcampbell.co.uk/"
+                },
+                {
+                    "name": "Vance Lucas",
+                    "email": "[email protected]",
+                    "homepage": "https://vancelucas.com/"
                 }
-            ]
-        },
-        "require": {
-            "php": "^5.4 || ^7.0",
-            "phpoption/phpoption": "^1.5",
-            "symfony/polyfill-ctype": "^1.9"
-        },
-        "require-dev": {
-            "phpunit/phpunit": "^4.8.35 || ^5.0 || ^6.0 || ^7.0"
-        },
-        "time": "2019-09-10T21:37:39+00:00",
-        "type": "library",
-        "extra": {
-            "branch-alias": {
-                "dev-master": "3.6-dev"
-            }
-        },
-        "installation-source": "dist",
-        "autoload": {
-            "psr-4": {
-                "Dotenv\\": "src/"
-            }
-        },
-        "notification-url": "https://packagist.org/downloads/",
-        "license": [
-            "BSD-3-Clause"
-        ],
-        "authors": [
-            {
-                "name": "Graham Campbell",
-                "email": "[email protected]",
-                "homepage": "https://gjcampbell.co.uk/"
-            },
-            {
-                "name": "Vance Lucas",
-                "email": "[email protected]",
-                "homepage": "https://vancelucas.com/"
-            }
-        ],
-        "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
-        "keywords": [
-            "dotenv",
-            "env",
-            "environment"
-        ]
-    }
-]
+            ],
+            "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
+            "keywords": [
+                "dotenv",
+                "env",
+                "environment"
+            ],
+            "install-path": "../vlucas/phpdotenv"
+        }
+    ],
+    "dev": true,
+    "dev-package-names": []
+}

+ 170 - 0
vendor/composer/installed.php

@@ -0,0 +1,170 @@
+<?php return array(
+    'root' => array(
+        'pretty_version' => 'dev-main',
+        'version' => 'dev-main',
+        'type' => 'project',
+        'install_path' => __DIR__ . '/../../',
+        'aliases' => array(),
+        'reference' => '8360a3f7db50835acb8a1efcd0292437099d01bc',
+        'name' => 'luolongfei/freenom',
+        'dev' => true,
+    ),
+    'versions' => array(
+        'bramus/ansi-php' => array(
+            'pretty_version' => '3.0.3',
+            'version' => '3.0.3.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../bramus/ansi-php',
+            'aliases' => array(),
+            'reference' => 'fb0be33f36053af7454d462e3ddc0a2ac0b2f311',
+            'dev_requirement' => false,
+        ),
+        'bramus/monolog-colored-line-formatter' => array(
+            'pretty_version' => '2.0.3',
+            'version' => '2.0.3.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../bramus/monolog-colored-line-formatter',
+            'aliases' => array(),
+            'reference' => '6bff15eee00afe2690642535b0f1541f10852c2b',
+            'dev_requirement' => false,
+        ),
+        'guzzlehttp/guzzle' => array(
+            'pretty_version' => '6.5.2',
+            'version' => '6.5.2.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../guzzlehttp/guzzle',
+            'aliases' => array(),
+            'reference' => '43ece0e75098b7ecd8d13918293029e555a50f82',
+            'dev_requirement' => false,
+        ),
+        'guzzlehttp/promises' => array(
+            'pretty_version' => 'v1.3.1',
+            'version' => '1.3.1.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../guzzlehttp/promises',
+            'aliases' => array(),
+            'reference' => 'a59da6cf61d80060647ff4d3eb2c03a2bc694646',
+            'dev_requirement' => false,
+        ),
+        'guzzlehttp/psr7' => array(
+            'pretty_version' => '1.6.1',
+            'version' => '1.6.1.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../guzzlehttp/psr7',
+            'aliases' => array(),
+            'reference' => '239400de7a173fe9901b9ac7c06497751f00727a',
+            'dev_requirement' => false,
+        ),
+        'kevinlebrun/colors.php' => array(
+            'pretty_version' => '1.0.3',
+            'version' => '1.0.3.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../kevinlebrun/colors.php',
+            'aliases' => array(),
+            'reference' => 'cdda5eee41314b87cd5a8bb91b1ffc7c0210e673',
+            'dev_requirement' => false,
+        ),
+        'luolongfei/freenom' => array(
+            'pretty_version' => 'dev-main',
+            'version' => 'dev-main',
+            'type' => 'project',
+            'install_path' => __DIR__ . '/../../',
+            'aliases' => array(),
+            'reference' => '8360a3f7db50835acb8a1efcd0292437099d01bc',
+            'dev_requirement' => false,
+        ),
+        'monolog/monolog' => array(
+            'pretty_version' => '1.25.3',
+            'version' => '1.25.3.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../monolog/monolog',
+            'aliases' => array(),
+            'reference' => 'fa82921994db851a8becaf3787a9e73c5976b6f1',
+            'dev_requirement' => false,
+        ),
+        'phpmailer/phpmailer' => array(
+            'pretty_version' => 'v6.1.6',
+            'version' => '6.1.6.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../phpmailer/phpmailer',
+            'aliases' => array(),
+            'reference' => 'c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3',
+            'dev_requirement' => false,
+        ),
+        'phpoption/phpoption' => array(
+            'pretty_version' => '1.7.2',
+            'version' => '1.7.2.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../phpoption/phpoption',
+            'aliases' => array(),
+            'reference' => '77f7c4d2e65413aff5b5a8cc8b3caf7a28d81959',
+            'dev_requirement' => false,
+        ),
+        'predis/predis' => array(
+            'pretty_version' => 'v1.1.1',
+            'version' => '1.1.1.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../predis/predis',
+            'aliases' => array(),
+            'reference' => 'f0210e38881631afeafb56ab43405a92cafd9fd1',
+            'dev_requirement' => false,
+        ),
+        'psr/http-message' => array(
+            'pretty_version' => '1.0.1',
+            'version' => '1.0.1.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../psr/http-message',
+            'aliases' => array(),
+            'reference' => 'f6561bf28d520154e4b0ec72be95418abe6d9363',
+            'dev_requirement' => false,
+        ),
+        'psr/http-message-implementation' => array(
+            'dev_requirement' => false,
+            'provided' => array(
+                0 => '1.0',
+            ),
+        ),
+        'psr/log' => array(
+            'pretty_version' => '1.1.2',
+            'version' => '1.1.2.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../psr/log',
+            'aliases' => array(),
+            'reference' => '446d54b4cb6bf489fc9d75f55843658e6f25d801',
+            'dev_requirement' => false,
+        ),
+        'psr/log-implementation' => array(
+            'dev_requirement' => false,
+            'provided' => array(
+                0 => '1.0.0',
+            ),
+        ),
+        'ralouphie/getallheaders' => array(
+            'pretty_version' => '3.0.3',
+            'version' => '3.0.3.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../ralouphie/getallheaders',
+            'aliases' => array(),
+            'reference' => '120b605dfeb996808c31b6477290a714d356e822',
+            'dev_requirement' => false,
+        ),
+        'symfony/polyfill-ctype' => array(
+            'pretty_version' => 'v1.13.1',
+            'version' => '1.13.1.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../symfony/polyfill-ctype',
+            'aliases' => array(),
+            'reference' => 'f8f0b461be3385e56d6de3dbb5a0df24c0c275e3',
+            'dev_requirement' => false,
+        ),
+        'vlucas/phpdotenv' => array(
+            'pretty_version' => 'v3.6.0',
+            'version' => '3.6.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../vlucas/phpdotenv',
+            'aliases' => array(),
+            'reference' => '1bdf24f065975594f6a117f0f1f6cabf1333b156',
+            'dev_requirement' => false,
+        ),
+    ),
+);

+ 3 - 3
vendor/phpmailer/phpmailer/README.md

@@ -102,8 +102,8 @@ try {
     $mail->SMTPAuth   = true;                                   // Enable SMTP authentication
     $mail->Username   = '[email protected]';                     // SMTP username
     $mail->Password   = 'secret';                               // SMTP password
-    $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;         // Enable TLS encryption; `PHPMailer::ENCRYPTION_SMTPS` also accepted
-    $mail->Port       = 587;                                    // TCP port to connect to
+    $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;         // Enable TLS encryption; `PHPMailer::ENCRYPTION_SMTPS` encouraged
+    $mail->Port       = 587;                                    // TCP port to connect to, use 465 for `PHPMailer::ENCRYPTION_SMTPS` above
 
     //Recipients
     $mail->setFrom('[email protected]', 'Mailer');
@@ -155,7 +155,7 @@ Note that in order to reduce PHPMailer's deployed code footprint, the examples a
 
 Complete generated API documentation is [available online](http://phpmailer.github.io/PHPMailer/).
 
-You can generate complete API-level documentation by running `phpdoc` in the top-level folder, and documentation will appear in the `docs` folder, though you'll need to have [PHPDocumentor](http://www.phpdoc.org) installed. You may find [the unit tests](https://github.com/PHPMailer/PHPMailer/tree/master/test/phpmailerTest.php) a good source of how to do various operations such as encryption.
+You can generate complete API-level documentation by running `phpdoc` in the top-level folder, and documentation will appear in the `docs` folder, though you'll need to have [PHPDocumentor](http://www.phpdoc.org) installed. You may find [the unit tests](https://github.com/PHPMailer/PHPMailer/blob/master/test/PHPMailerTest.php) a good source of how to do various operations such as encryption.
 
 If the documentation doesn't cover what you need, search the [many questions on Stack Overflow](http://stackoverflow.com/questions/tagged/phpmailer), and before you ask a question about "SMTP Error: Could not connect to SMTP host.", [read the troubleshooting guide](https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting).
 

+ 2 - 0
vendor/phpmailer/phpmailer/SECURITY.md

@@ -2,6 +2,8 @@
 
 Please disclose any vulnerabilities found responsibly - report any security problems found to the maintainers privately.
 
+PHPMailer versions 6.1.5 and earlier contain an output escaping bug that occurs in `Content-Type` and `Content-Disposition` when filenames passed into `addAttachment` and other methods that accept attachment names contain double quote characters, in contravention of RFC822 3.4.1. No specific vulnerability has been found relating to this, but it could allow file attachments to bypass attachment filters that are based on matching filename extensions. Recorded as [CVE-2020-13625](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2020-13625). Reported by Elar Lang of Clarified Security.
+
 PHPMailer versions prior to 6.0.6 and 5.2.27 are vulnerable to an object injection attack by passing `phar://` paths into `addAttachment()` and other functions that may receive unfiltered local paths, possibly leading to RCE. Recorded as [CVE-2018-19296](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2018-19296). See [this article](https://knasmueller.net/5-answers-about-php-phar-exploitation) for more info on this type of vulnerability. Mitigated by blocking the use of paths containing URL-protocol style prefixes such as `phar://`. Reported by Sehun Oh of cyberone.kr.
 
 PHPMailer versions prior to 5.2.24 (released July 26th 2017) have an XSS vulnerability in one of the code examples, [CVE-2017-11503](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2017-11503). The `code_generator.phps` example did not filter user input prior to output. This file is distributed with a `.phps` extension, so it it not normally executable unless it is explicitly renamed, and the file is not included when PHPMailer is loaded through composer, so it is safe by default. There was also an undisclosed potential XSS vulnerability in the default exception handler (unused by default). Patches for both issues kindly provided by Patrick Monnerat of the Fedora Project.

+ 1 - 1
vendor/phpmailer/phpmailer/VERSION

@@ -1 +1 @@
-6.1.4
+6.1.6

+ 6 - 0
vendor/phpmailer/phpmailer/composer.json

@@ -19,6 +19,12 @@
             "name": "Brent R. Matzelle"
         }
     ],
+    "funding": [
+        {
+            "url": "https://github.com/synchro",
+            "type": "github"
+        }
+    ],
     "require": {
         "php": ">=5.5.0",
         "ext-ctype": "*",

+ 17 - 15
vendor/phpmailer/phpmailer/language/phpmailer.lang-da.php

@@ -2,25 +2,27 @@
 /**
  * Danish PHPMailer language file: refer to English translation for definitive list
  * @package PHPMailer
- * @author Mikael Stokkebro <[email protected]>
+ * @author John Sebastian <[email protected]>
+ * Rewrite and extension of the work by Mikael Stokkebro <[email protected]> 
+ *  
  */
 
-$PHPMAILER_LANG['authenticate']         = 'SMTP fejl: Kunne ikke logge på.';
-$PHPMAILER_LANG['connect_host']         = 'SMTP fejl: Kunne ikke tilslutte SMTP serveren.';
-$PHPMAILER_LANG['data_not_accepted']    = 'SMTP fejl: Data kunne ikke accepteres.';
-//$PHPMAILER_LANG['empty_message']        = 'Message body empty';
+$PHPMAILER_LANG['authenticate']         = 'SMTP fejl: Login mislykkedes.';
+$PHPMAILER_LANG['connect_host']         = 'SMTP fejl: Forbindelse til SMTP serveren kunne ikke oprettes.';
+$PHPMAILER_LANG['data_not_accepted']    = 'SMTP fejl: Data blev ikke accepteret.';
+$PHPMAILER_LANG['empty_message']        = 'Meddelelsen er uden indhold';
 $PHPMAILER_LANG['encoding']             = 'Ukendt encode-format: ';
-$PHPMAILER_LANG['execute']              = 'Kunne ikke køre: ';
-$PHPMAILER_LANG['file_access']          = 'Ingen adgang til fil: ';
+$PHPMAILER_LANG['execute']              = 'Kunne ikke afvikle: ';
+$PHPMAILER_LANG['file_access']          = 'Kunne ikke tilgå filen: ';
 $PHPMAILER_LANG['file_open']            = 'Fil fejl: Kunne ikke åbne filen: ';
 $PHPMAILER_LANG['from_failed']          = 'Følgende afsenderadresse er forkert: ';
-$PHPMAILER_LANG['instantiate']          = 'Kunne ikke initialisere email funktionen.';
-//$PHPMAILER_LANG['invalid_address']        = 'Invalid address: ';
+$PHPMAILER_LANG['instantiate']          = 'Email funktionen kunne ikke initialiseres.';
+$PHPMAILER_LANG['invalid_address']      = 'Udgyldig adresse: ';
 $PHPMAILER_LANG['mailer_not_supported'] = ' mailer understøttes ikke.';
-$PHPMAILER_LANG['provide_address']      = 'Du skal indtaste mindst en modtagers emailadresse.';
+$PHPMAILER_LANG['provide_address']      = 'Indtast mindst en modtagers email adresse.';
 $PHPMAILER_LANG['recipients_failed']    = 'SMTP fejl: Følgende modtagere er forkerte: ';
-//$PHPMAILER_LANG['signing']              = 'Signing Error: ';
-//$PHPMAILER_LANG['smtp_connect_failed']  = 'SMTP Connect() failed.';
-//$PHPMAILER_LANG['smtp_error']           = 'SMTP server error: ';
-//$PHPMAILER_LANG['variable_set']         = 'Cannot set or reset variable: ';
-//$PHPMAILER_LANG['extension_missing']    = 'Extension missing: ';
+$PHPMAILER_LANG['signing']              = 'Signeringsfejl: ';
+$PHPMAILER_LANG['smtp_connect_failed']  = 'SMTP Connect() fejlede.';
+$PHPMAILER_LANG['smtp_error']           = 'SMTP server fejl: ';
+$PHPMAILER_LANG['variable_set']         = 'Kunne ikke definere eller nulstille variablen: ';
+$PHPMAILER_LANG['extension_missing']    = 'Udvidelse mangler: ';

+ 0 - 0
vendor/phpmailer/phpmailer/language/phpmailer.lang-am.php → vendor/phpmailer/phpmailer/language/phpmailer.lang-hy.php


+ 113 - 57
vendor/phpmailer/phpmailer/src/PHPMailer.php

@@ -64,7 +64,7 @@ class PHPMailer
      * Options: null (default), 1 = High, 3 = Normal, 5 = low.
      * When null, the header is not set at all.
      *
-     * @var int
+     * @var int|null
      */
     public $Priority;
 
@@ -745,7 +745,7 @@ class PHPMailer
      *
      * @var string
      */
-    const VERSION = '6.1.4';
+    const VERSION = '6.1.6';
 
     /**
      * Error severity: message only, continue processing.
@@ -769,11 +769,22 @@ class PHPMailer
     const STOP_CRITICAL = 2;
 
     /**
-     * SMTP RFC standard line ending.
+     * The SMTP standard CRLF line break.
+     * If you want to change line break format, change static::$LE, not this.
+     */
+    const CRLF = "\r\n";
+
+    /**
+     * "Folding White Space" a white space string used for line folding.
+     */
+    const FWS = ' ';
+
+    /**
+     * SMTP RFC standard line ending; Carriage Return, Line Feed.
      *
      * @var string
      */
-    protected static $LE = "\r\n";
+    protected static $LE = self::CRLF;
 
     /**
      * The maximum line length supported by mail().
@@ -1446,7 +1457,7 @@ class PHPMailer
         ) {
             //SMTP mandates RFC-compliant line endings
             //and it's also used with mail() on Windows
-            static::setLE("\r\n");
+            static::setLE(self::CRLF);
         } else {
             //Maintain backward compatibility with legacy Linux command line mailers
             static::setLE(PHP_EOL);
@@ -1553,7 +1564,7 @@ class PHPMailer
                     $this->encodeHeader($this->secureHeader($this->Subject)),
                     $this->MIMEBody
                 );
-                $this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . static::$LE .
+                $this->MIMEHeader = static::stripTrailingWSP($this->MIMEHeader) . static::$LE .
                     static::normalizeBreaks($header_dkim) . static::$LE;
             }
 
@@ -1620,7 +1631,7 @@ class PHPMailer
      */
     protected function sendmailSend($header, $body)
     {
-        $header = rtrim($header, "\r\n ") . static::$LE . static::$LE;
+        $header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
 
         // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
         if (!empty($this->Sender) && self::isShellSafe($this->Sender)) {
@@ -1750,7 +1761,7 @@ class PHPMailer
      */
     protected function mailSend($header, $body)
     {
-        $header = rtrim($header, "\r\n ") . static::$LE . static::$LE;
+        $header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
 
         $toArr = [];
         foreach ($this->to as $toaddr) {
@@ -1839,7 +1850,7 @@ class PHPMailer
      */
     protected function smtpSend($header, $body)
     {
-        $header = rtrim($header, "\r\n ") . static::$LE . static::$LE;
+        $header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
         $bad_rcpt = [];
         if (!$this->smtpConnect($this->SMTPOptions)) {
             throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
@@ -2075,6 +2086,7 @@ class PHPMailer
             'se' => 'sv',
             'rs' => 'sr',
             'tg' => 'tl',
+            'am' => 'hy',
         ];
 
         if (isset($renamed_langcodes[$langcode])) {
@@ -2511,7 +2523,8 @@ class PHPMailer
      */
     public function getSentMIMEMessage()
     {
-        return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . static::$LE . static::$LE . $this->MIMEBody;
+        return static::stripTrailingWSP($this->MIMEHeader . $this->mailHeader) .
+            static::$LE . static::$LE . $this->MIMEBody;
     }
 
     /**
@@ -2594,7 +2607,7 @@ class PHPMailer
             $altBodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
         }
         //Use this as a preamble in all multipart message types
-        $mimepre = 'This is a multi-part message in MIME format.' . static::$LE;
+        $mimepre = 'This is a multi-part message in MIME format.' . static::$LE . static::$LE;
         switch ($this->message_type) {
             case 'inline':
                 $body .= $mimepre;
@@ -2949,7 +2962,7 @@ class PHPMailer
         $disposition = 'attachment'
     ) {
         try {
-            if (!static::isPermittedPath($path) || !@is_file($path)) {
+            if (!static::isPermittedPath($path) || !@is_file($path) || !is_readable($path)) {
                 throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);
             }
 
@@ -3051,9 +3064,9 @@ class PHPMailer
                 //Only include a filename property if we have one
                 if (!empty($name)) {
                     $mime[] = sprintf(
-                        'Content-Type: %s; name="%s"%s',
+                        'Content-Type: %s; name=%s%s',
                         $type,
-                        $this->encodeHeader($this->secureHeader($name)),
+                        static::quotedString($this->encodeHeader($this->secureHeader($name))),
                         static::$LE
                     );
                 } else {
@@ -3073,24 +3086,14 @@ class PHPMailer
                     $mime[] = 'Content-ID: <' . $this->encodeHeader($this->secureHeader($cid)) . '>' . static::$LE;
                 }
 
-                // If a filename contains any of these chars, it should be quoted,
-                // but not otherwise: RFC2183 & RFC2045 5.1
-                // Fixes a warning in IETF's msglint MIME checker
-                // Allow for bypassing the Content-Disposition header totally
+                // Allow for bypassing the Content-Disposition header
                 if (!empty($disposition)) {
                     $encoded_name = $this->encodeHeader($this->secureHeader($name));
-                    if (preg_match('/[ ()<>@,;:"\/\[\]?=]/', $encoded_name)) {
-                        $mime[] = sprintf(
-                            'Content-Disposition: %s; filename="%s"%s',
-                            $disposition,
-                            $encoded_name,
-                            static::$LE . static::$LE
-                        );
-                    } elseif (!empty($encoded_name)) {
+                    if (!empty($encoded_name)) {
                         $mime[] = sprintf(
                             'Content-Disposition: %s; filename=%s%s',
                             $disposition,
-                            $encoded_name,
+                            static::quotedString($encoded_name),
                             static::$LE . static::$LE
                         );
                     } else {
@@ -3134,7 +3137,7 @@ class PHPMailer
     protected function encodeFile($path, $encoding = self::ENCODING_BASE64)
     {
         try {
-            if (!static::isPermittedPath($path) || !file_exists($path)) {
+            if (!static::isPermittedPath($path) || !file_exists($path) || !is_readable($path)) {
                 throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
             }
             $file_buffer = file_get_contents($path);
@@ -3146,6 +3149,10 @@ class PHPMailer
             return $file_buffer;
         } catch (Exception $exc) {
             $this->setError($exc->getMessage());
+            $this->edebug($exc->getMessage());
+            if ($this->exceptions) {
+                throw $exc;
+            }
 
             return '';
         }
@@ -3516,7 +3523,7 @@ class PHPMailer
         $disposition = 'inline'
     ) {
         try {
-            if (!static::isPermittedPath($path) || !@is_file($path)) {
+            if (!static::isPermittedPath($path) || !@is_file($path) || !is_readable($path)) {
                 throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);
             }
 
@@ -3935,15 +3942,28 @@ class PHPMailer
      *
      * @param string      $name  Custom header name
      * @param string|null $value Header value
+     *
+     * @throws Exception
      */
     public function addCustomHeader($name, $value = null)
     {
-        if (null === $value) {
+        if (null === $value && strpos($name, ':') !== false) {
             // Value passed in as name:value
-            $this->CustomHeader[] = explode(':', $name, 2);
-        } else {
-            $this->CustomHeader[] = [$name, $value];
+            list($name, $value) = explode(':', $name, 2);
         }
+        $name = trim($name);
+        $value = trim($value);
+        //Ensure name is not empty, and that neither name nor value contain line breaks
+        if (empty($name) || strpbrk($name . $value, "\r\n") !== false) {
+            if ($this->exceptions) {
+                throw new Exception('Invalid header name or value');
+            }
+
+            return false;
+        }
+        $this->CustomHeader[] = [$name, $value];
+
+        return true;
     }
 
     /**
@@ -3987,6 +4007,7 @@ class PHPMailer
             foreach ($images[2] as $imgindex => $url) {
                 // Convert data URIs into embedded images
                 //e.g. "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
+                $match = [];
                 if (preg_match('#^data:(image/(?:jpe?g|gif|png));?(base64)?,(.+)#', $url, $match)) {
                     if (count($match) === 4 && static::ENCODING_BASE64 === $match[2]) {
                         $data = base64_decode($match[3]);
@@ -4355,7 +4376,7 @@ class PHPMailer
             $breaktype = static::$LE;
         }
         // Normalise to \n
-        $text = str_replace(["\r\n", "\r"], "\n", $text);
+        $text = str_replace([self::CRLF, "\r"], "\n", $text);
         // Now convert LE as needed
         if ("\n" !== $breaktype) {
             $text = str_replace("\n", $breaktype, $text);
@@ -4364,6 +4385,18 @@ class PHPMailer
         return $text;
     }
 
+    /**
+     * Remove trailing breaks from a string.
+     *
+     * @param string $text
+     *
+     * @return string The text to remove breaks from
+     */
+    public static function stripTrailingWSP($text)
+    {
+        return rtrim($text, " \r\n\t");
+    }
+
     /**
      * Return the current line break format string.
      *
@@ -4472,13 +4505,15 @@ class PHPMailer
      */
     public function DKIM_HeaderC($signHeader)
     {
+        //Normalize breaks to CRLF (regardless of the mailer)
+        $signHeader = static::normalizeBreaks($signHeader, self::CRLF);
+        //Unfold header lines
         //Note PCRE \s is too broad a definition of whitespace; RFC5322 defines it as `[ \t]`
         //@see https://tools.ietf.org/html/rfc5322#section-2.2
         //That means this may break if you do something daft like put vertical tabs in your headers.
-        //Unfold header lines
         $signHeader = preg_replace('/\r\n[ \t]+/', ' ', $signHeader);
         //Break headers out into an array
-        $lines = explode("\r\n", $signHeader);
+        $lines = explode(self::CRLF, $signHeader);
         foreach ($lines as $key => $line) {
             //If the header is missing a :, skip it as it's invalid
             //This is likely to happen because the explode() above will also split
@@ -4498,7 +4533,7 @@ class PHPMailer
             $lines[$key] = trim($heading, " \t") . ':' . trim($value, " \t");
         }
 
-        return implode("\r\n", $lines);
+        return implode(self::CRLF, $lines);
     }
 
     /**
@@ -4515,13 +4550,13 @@ class PHPMailer
     public function DKIM_BodyC($body)
     {
         if (empty($body)) {
-            return "\r\n";
+            return self::CRLF;
         }
         // Normalize line endings to CRLF
-        $body = static::normalizeBreaks($body, "\r\n");
+        $body = static::normalizeBreaks($body, self::CRLF);
 
         //Reduce multiple trailing line breaks to a single one
-        return rtrim($body, "\r\n") . "\r\n";
+        return static::stripTrailingWSP($body) . self::CRLF;
     }
 
     /**
@@ -4542,17 +4577,18 @@ class PHPMailer
         $DKIMquery = 'dns/txt'; // Query method
         $DKIMtime = time();
         //Always sign these headers without being asked
+        //Recommended list from https://tools.ietf.org/html/rfc6376#section-5.4.1
         $autoSignHeaders = [
-            'From',
-            'To',
-            'CC',
-            'Date',
-            'Subject',
-            'Reply-To',
-            'Message-ID',
-            'Content-Type',
-            'Mime-Version',
-            'X-Mailer',
+            'from',
+            'to',
+            'cc',
+            'date',
+            'subject',
+            'reply-to',
+            'message-id',
+            'content-type',
+            'mime-version',
+            'x-mailer',
         ];
         if (stripos($headers_line, 'Subject') === false) {
             $headers_line .= 'Subject: ' . $subject . static::$LE;
@@ -4587,7 +4623,7 @@ class PHPMailer
         $headersToSign = [];
         foreach ($parsedHeaders as $header) {
             //Is this header one that must be included in the DKIM signature?
-            if (in_array($header['label'], $autoSignHeaders, true)) {
+            if (in_array(strtolower($header['label']), $autoSignHeaders, true)) {
                 $headersToSignKeys[] = $header['label'];
                 $headersToSign[] = $header['label'] . ': ' . $header['value'];
                 if ($this->DKIM_copyHeaderFields) {
@@ -4625,9 +4661,9 @@ class PHPMailer
                 //Fold long values
                 if (strlen($copiedHeader) > self::STD_LINE_LENGTH - 3) {
                     $copiedHeaderFields .= substr(
-                        chunk_split($copiedHeader, self::STD_LINE_LENGTH - 3, static::$LE . ' '),
+                        chunk_split($copiedHeader, self::STD_LINE_LENGTH - 3, static::$LE . self::FWS),
                         0,
-                        -strlen(static::$LE . ' ')
+                        -strlen(static::$LE . self::FWS)
                     );
                 } else {
                     $copiedHeaderFields .= $copiedHeader;
@@ -4639,7 +4675,6 @@ class PHPMailer
         $headerKeys = ' h=' . implode(':', $headersToSignKeys) . ';' . static::$LE;
         $headerValues = implode(static::$LE, $headersToSign);
         $body = $this->DKIM_BodyC($body);
-        $DKIMlen = strlen($body); // Length of body
         $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body
         $ident = '';
         if ('' !== $this->DKIM_identity) {
@@ -4653,7 +4688,6 @@ class PHPMailer
             ' s=' . $this->DKIM_selector . ';' . static::$LE .
             ' a=' . $DKIMsignatureType . ';' .
             ' q=' . $DKIMquery . ';' .
-            ' l=' . $DKIMlen . ';' .
             ' t=' . $DKIMtime . ';' .
             ' c=' . $DKIMcanonicalization . ';' . static::$LE .
             $headerKeys .
@@ -4666,9 +4700,9 @@ class PHPMailer
             $headerValues . static::$LE . $dkimSignatureHeader
         );
         $signature = $this->DKIM_Sign($canonicalizedHeaders);
-        $signature = trim(chunk_split($signature, self::STD_LINE_LENGTH - 3, static::$LE . ' '));
+        $signature = trim(chunk_split($signature, self::STD_LINE_LENGTH - 3, static::$LE . self::FWS));
 
-        return static::normalizeBreaks($dkimSignatureHeader . $signature) . static::$LE;
+        return static::normalizeBreaks($dkimSignatureHeader . $signature);
     }
 
     /**
@@ -4684,6 +4718,28 @@ class PHPMailer
         return (bool) preg_match('/^(.{' . (self::MAX_LINE_LENGTH + strlen(static::$LE)) . ',})/m', $str);
     }
 
+    /**
+     * If a string contains any "special" characters, double-quote the name,
+     * and escape any double quotes with a backslash.
+     *
+     * @param string $str
+     *
+     * @return string
+     *
+     * @see RFC822 3.4.1
+     */
+    public static function quotedString($str)
+    {
+        if (preg_match('/[ ()<>@,;:"\/\[\]?=]/', $str)) {
+            //If the string contains any of these chars, it must be double-quoted
+            //and any double quotes must be escaped with a backslash
+            return '"' . str_replace('"', '\\"', $str) . '"';
+        }
+
+        //Return the string untouched, it doesn't need quoting
+        return $str;
+    }
+
     /**
      * Allows for public read access to 'to' property.
      * Before the send() call, queued addresses (i.e. with IDN) are not yet included.

+ 3 - 1
vendor/phpmailer/phpmailer/src/POP3.php

@@ -45,7 +45,7 @@ class POP3
      *
      * @var string
      */
-    const VERSION = '6.1.4';
+    const VERSION = '6.1.6';
 
     /**
      * Default POP3 port number.
@@ -230,6 +230,8 @@ class POP3
         }
 
         //  connect to the POP3 server
+        $errno = 0;
+        $errstr = '';
         $this->pop_conn = fsockopen(
             $host, //  POP3 Host
             $port, //  Port #

+ 4 - 3
vendor/phpmailer/phpmailer/src/SMTP.php

@@ -34,7 +34,7 @@ class SMTP
      *
      * @var string
      */
-    const VERSION = '6.1.4';
+    const VERSION = '6.1.6';
 
     /**
      * SMTP line break constant.
@@ -1168,7 +1168,7 @@ class SMTP
             //Must pass vars in here as params are by reference
             if (!stream_select($selR, $selW, $selW, $this->Timelimit)) {
                 $this->edebug(
-                    'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
+                    'SMTP -> get_lines(): select timed-out in (' . $this->Timelimit . ' sec)',
                     self::DEBUG_LOWLEVEL
                 );
                 break;
@@ -1187,7 +1187,7 @@ class SMTP
             $info = stream_get_meta_data($this->smtp_conn);
             if ($info['timed_out']) {
                 $this->edebug(
-                    'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
+                    'SMTP -> get_lines(): stream timed-out (' . $this->Timeout . ' sec)',
                     self::DEBUG_LOWLEVEL
                 );
                 break;
@@ -1344,6 +1344,7 @@ class SMTP
         } else {
             $this->last_smtp_transaction_id = false;
             foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
+                $matches = [];
                 if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
                     $this->last_smtp_transaction_id = trim($matches[1]);
                     break;