Passed
Pull Request — master (#19)
by
unknown
01:51
created

FreeNom::getAccounts()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 34
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
eloc 20
c 2
b 1
f 0
dl 0
loc 34
rs 8.6666
cc 7
nc 8
nop 0
1
<?php
2
/**
3
 * FreeNom域名自动续期
4
 *
5
 * @author mybsdc <[email protected]>
6
 * @date 2020/1/19
7
 * @time 17:29
8
 * @link https://github.com/luolongfei/freenom
9
 */
10
11
namespace Luolongfei\App\Console;
12
13
use Luolongfei\App\Exceptions\LlfException;
14
use GuzzleHttp\Client;
15
use GuzzleHttp\Cookie\CookieJar;
16
use Luolongfei\Lib\Log;
17
use Luolongfei\Lib\Mail;
18
use Luolongfei\Lib\TelegramBot;
19
20
class FreeNom
21
{
22
    const VERSION = 'v0.2.2';
23
24
    const TIMEOUT = 32.52;
25
26
    // FreeNom登录地址
27
    const LOGIN_URL = 'https://my.freenom.com/dologin.php';
28
29
    // 域名状态地址
30
    const DOMAIN_STATUS_URL = 'https://my.freenom.com/domains.php?a=renewals';
31
32
    // 域名续期地址
33
    const RENEW_DOMAIN_URL = 'https://my.freenom.com/domains.php?submitrenewals=true';
34
35
    // 匹配token的正则
36
    const TOKEN_REGEX = '/name="token"\svalue="(?P<token>[^"]+)"/i';
37
38
    // 匹配域名信息的正则
39
    const DOMAIN_INFO_REGEX = '/<tr><td>(?P<domain>[^<]+)<\/td><td>[^<]+<\/td><td>[^<]+<span class="[^"]+">(?P<days>\d+)[^&]+&domain=(?P<id>\d+)"/i';
40
41
    /**
42
     * @var FreeNom
43
     */
44
    protected static $instance;
45
46
    /**
47
     * @var Client
48
     */
49
    protected $client;
50
51
    /**
52
     * @var CookieJar | bool
53
     */
54
    protected $jar = true;
55
56
    /**
57
     * @var string freenom账户
58
     */
59
    protected $username;
60
61
    /**
62
     * @var string freenom密码
63
     */
64
    protected $password;
65
66
    public function __construct()
67
    {
68
        $this->client = new Client([
69
            'headers' => [
70
                'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
71
                'Accept-Encoding' => 'gzip, deflate, br',
72
                'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36',
73
            ],
74
            'timeout' => self::TIMEOUT,
75
            CURLOPT_FOLLOWLOCATION => true,
76
            CURLOPT_AUTOREFERER => true,
77
            'debug' => config('debug')
78
        ]);
79
    }
80
81
    /**
82
     * @return FreeNom
83
     */
84
    public static function instance()
85
    {
86
        if (!self::$instance instanceof self) {
0 ignored issues
show
introduced by
self::instance is always a sub-type of self.
Loading history...
87
            self::$instance = new self();
88
        }
89
90
        return self::$instance;
91
    }
92
93
    /**
94
     * 登录
95
     */
96
    protected function login()
97
    {
98
        $this->client->post(self::LOGIN_URL, [
99
            'headers' => [
100
                'Content-Type' => 'application/x-www-form-urlencoded',
101
                'Referer' => 'https://my.freenom.com/clientarea.php'
102
            ],
103
            'form_params' => [
104
                'username' => $this->username,
105
                'password' => $this->password
106
            ],
107
            'cookies' => $this->jar
108
        ]);
109
    }
110
111
    /**
112
     * 续期
113
     *
114
     * @throws \Exception
115
     * @throws LlfException
116
     */
117
    public function renewDomains()
118
    {
119
        // 所有请求共用一个CookieJar实例
120
        $this->jar = new CookieJar();
121
122
        $this->login();
123
        $authCookie = $this->jar->getCookieByName('WHMCSZH5eHTGhfvzP')->getValue();
124
        if (empty($authCookie)) {
125
            throw new LlfException(32520002);
126
        }
127
128
        // 检查域名状态
129
        $response = $this->client->get(self::DOMAIN_STATUS_URL, [
130
            'headers' => [
131
                'Referer' => 'https://my.freenom.com/clientarea.php'
132
            ],
133
            'cookies' => $this->jar
134
        ]);
135
        $body = (string)$response->getBody();
136
137
        // 域名数据
138
        if (!preg_match_all(self::DOMAIN_INFO_REGEX, $body, $domains, PREG_SET_ORDER)) {
139
            throw new LlfException(32520003);
140
        }
141
142
        // 页面token
143
        if (!preg_match(self::TOKEN_REGEX, $body, $matches)) {
144
            throw new LlfException(32520004);
145
        }
146
        $token = $matches['token'];
147
148
        // 续期
149
        $result = '';
150
        $renewed = $renewedTG = ''; // 续期成功的域名
151
        $notRenewed = $notRenewedTG = ''; // 记录续期出错的域名,用于邮件通知内容
152
        $domainInfo = $domainInfoTG = ''; // 域名状态信息,用于邮件通知内容
153
        foreach ($domains as $d) {
154
            $domain = $d['domain'];
155
            $days = intval($d['days']);
156
            $id = $d['id'];
157
158
            // 免费域名只允许在到期前14天内续期
159
            if ($days <= 14) {
160
                try {
161
                    $response = $this->client->post(self::RENEW_DOMAIN_URL, [
162
                        'headers' => [
163
                            'Referer' => sprintf('https://my.freenom.com/domains.php?a=renewdomain&domain=%s', $id),
164
                            'Content-Type' => 'application/x-www-form-urlencoded'
165
                        ],
166
                        'form_params' => [
167
                            'token' => $token,
168
                            'renewalid' => $id,
169
                            sprintf('renewalperiod[%s]', $id) => '12M', // 续期一年
170
                            'paymentmethod' => 'credit', // 支付方式:信用卡
171
                        ],
172
                        'cookies' => $this->jar
173
                    ]);
174
                } catch (\Exception $e) {
175
                    system_log(sprintf('%s:续期请求出错:%s', $this->username, $e->getMessage()));
176
                    continue;
177
                }
178
179
                $body = (string)$response->getBody();
180
                sleep(1);
181
182
                if (stripos($body, 'Order Confirmation') === false) { // 续期失败
183
                    $result .= sprintf("%s续期失败\n", $domain);
184
                    $notRenewed .= sprintf('<a href="http://%s" rel="noopener" target="_blank">%s</a>', $domain, $domain);
185
                    $notRenewedTG .= sprintf('[%s](http://%s)  ', $domain, $domain);
186
                } else {
187
                    $result .= sprintf("%s续期成功\n", $domain);
188
                    $renewed .= sprintf('<a href="http://%s" rel="noopener" target="_blank">%s</a>', $domain, $domain);
189
                    $renewedTG .= sprintf('[%s](http://%s)  ', $domain, $domain);
190
                    continue;
191
                }
192
            }
193
194
            $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);
195
            $domainInfoTG .= sprintf('[%s](http://%s)还有*%d*天到期,', $domain, $domain, $days);
196
        }
197
        $domainInfoTG .= "更多信息可以参考[Freenom官网](https://my.freenom.com/domains.php?a=renewals)哦~\n\n(如果你不想每次执行都收到推送,请将config.php中noticeFreq的值设为0,使程序只在有续期操作时才推送)";
198
199
        if ($notRenewed || $renewed) {
200
            Mail::send(
201
                '主人,我刚刚帮你续期域名啦~',
202
                [
203
                    $this->username,
204
                    $renewed ? sprintf('续期成功:%s<br>', $renewed) : '',
205
                    $notRenewed ? sprintf('续期出错:%s<br>', $notRenewed) : '',
206
                    $domainInfo ?: '哦豁,没看到其它域名。'
207
                ]
208
            );
209
            TelegramBot::send(sprintf(
210
                "主人,我刚刚帮你续期域名啦~\n\n%s%s\n另外,%s",
211
                $renewedTG ? sprintf("续期成功:%s\n", $renewedTG) : '',
212
                $notRenewedTG ? sprintf("续期失败:%s\n", $notRenewedTG) : '',
213
                $domainInfoTG
214
            ));
215
            system_log(sprintf("%s:续期结果如下:\n%s", $this->username, $result));
216
        } else {
217
            if (config('noticeFreq') === 1) {
218
                Mail::send(
219
                    '报告,今天没有域名需要续期',
220
                    [
221
                        $this->username,
222
                        $domainInfo
223
                    ],
224
                    '',
225
                    'notice'
226
                );
227
                TelegramBot::send("报告,今天没有域名需要续期,所有域名情况如下:\n\n" . $domainInfoTG);
228
            }
229
            system_log(sprintf('%s:<green>执行成功,今次没有需要续期的域名。</green>', $this->username));
230
        }
231
    }
232
233
    /**
234
     * 二维数组去重
235
     *
236
     * @param array $array 原始数组
237
     * @param array $keys 可指定对应的键联合
238
     *
239
     * @return bool
240
     */
241
    public function arrayUnique(array &$array, array $keys = [])
242
    {
243
        if (!isset($array[0]) || !is_array($array[0])) {
244
            return false;
245
        }
246
247
        if (empty($keys)) {
248
            $keys = array_keys($array[0]);
249
        }
250
251
        $tmp = [];
252
        foreach ($array as $k => $items) {
253
            $combinedKey = '';
254
            foreach ($keys as $key) {
255
                $combinedKey .= $items[$key];
256
            }
257
258
            if (isset($tmp[$combinedKey])) {
259
                unset($array[$k]);
260
            } else {
261
                $tmp[$combinedKey] = $k;
262
            }
263
        }
264
        unset($tmp);
265
266
        return true;
267
    }
268
269
    /**
270
     * 获取freenom账户信息
271
     *
272
     * @return array
273
     * @throws LlfException
274
     */
275
    protected function getAccounts()
276
    {
277
        $accounts = [];
278
        $multipleAccounts = preg_replace('/\s/', '', env('MULTIPLE_ACCOUNTS'));
279
        if ($multipleAccounts) {
280
            if (preg_match_all('/<(?P<u>.*?)>@<(?P<p>.*?)>/i', $multipleAccounts, $matches, PREG_SET_ORDER)) {
281
                foreach ($matches as $m) {
282
                    system_log($m);
283
                    $accounts[] = [
284
                        'username' => $m['u'],
285
                        'password' => $m['p']
286
                    ];
287
                }
288
            }
289
        } 
290
        else {
291
            $username = env('FREENOM_USERNAME');
292
            $password = env('FREENOM_PASSWORD');
293
            if ($username && $password) {
294
                $accounts[] = [
295
                    'username' => $username,
296
                    'password' => $password
297
                ];
298
            }
299
        }
300
301
        if (empty($accounts)) {
302
            throw new LlfException(32520001);
303
        }
304
305
        // 去重
306
        $this->arrayUnique($accounts);
307
308
        return $accounts;
309
    }
310
311
    /**
312
     * @throws LlfException
313
     * @throws \Exception
314
     */
315
    public function handle()
316
    {
317
        $accounts = $this->getAccounts();
318
        foreach ($accounts as $account) {
319
            try {
320
                $this->username = $account['username'];
321
                $this->password = $account['password'];
322
                $this->renewDomains();
323
            } catch (LlfException $e) {
324
                Mail::send(
325
                    '主人,' . $e->getMessage(),
326
                    [
327
                        $this->username,
328
                        sprintf('具体是在%s文件的第%d行,抛出了一个异常。异常的内容是%s,快去看看吧。', $e->getFile(), $e->getLine(), $e->getMessage()),
329
                    ],
330
                    '',
331
                    'LlfException'
332
                );
333
                TelegramBot::send(sprintf(
334
                    '主人,出错了。具体是在%s文件的第%d行,抛出了一个异常。异常的内容是%s,快去看看吧。(账户:%s)',
335
                    $e->getFile(),
336
                    $e->getLine(),
337
                    $e->getMessage(),
338
                    $this->username
339
                ));
340
            } catch (\Exception $e) {
341
                system_log(sprintf('出错:<red>%s</red>', $e->getMessage()), $e->getTrace());
342
            }
343
        }
344
    }
345
}
346