├── README.md
└── daily.php
/README.md:
--------------------------------------------------------------------------------
1 | # V2EX 自动领取每日奖励
2 |
3 | 一段基于 `curl_share_*` 系列函数的 PHP 模拟登录 V2EX 代码,并自动领取每日奖励金币,无需生成临时 cookie 文件。
4 |
5 | ### 运行要求:
6 |
7 | + PHP >= 5.5
8 | + 支持 `curl_share_init` 等函数
9 |
10 |
11 | ### 使用方式:
12 |
13 | 实例化一个对象:`$v2ex = new v2ex('username', 'password');` 即可。
14 |
15 |
16 | ### 吐槽:
17 |
18 | 新浪的 SAE 虽然已经提供了 PHP 5.6 版本,但却不支持 `curl_share_*` 系列函数,真是日了狗了。
19 |
--------------------------------------------------------------------------------
/daily.php:
--------------------------------------------------------------------------------
1 | = 5.5
6 | class v2ex
7 | {
8 | # 网站主域名
9 | private $domain = 'https://www.v2ex.com';
10 |
11 | # 模拟 UserAgent
12 | private $useragent = 'Mozilla/5 (Macintosh; Intel Mac OS X 10) AppleWebKit/537 (KHTML, like Gecko) Chrome/41 Safari/537';
13 |
14 | # 共享 CURL 载体
15 | private $curls = null;
16 |
17 | # IP 地址
18 | private $ip = null;
19 |
20 |
21 | # 构造函数
22 | public function __construct($username, $password)
23 | {
24 | // 为本次请求生成一个随机 IP 地址,避免同 IP 上的猪队友
25 | $this->ip = join('.', [220, mt_rand(50, 250), mt_rand(50, 250), mt_rand(50, 250)]);
26 |
27 | // 初始化 CURL 共享载体
28 | if (is_null($this->curls))
29 | {
30 | $this->curls = curl_share_init();
31 | curl_share_setopt($this->curls, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
32 | }
33 |
34 | // 第一步:获取登录页面代码,提取登录用的 ONCE 参数
35 | $html = $this->request('/signin', '/');
36 | preg_match('@@', $html, $match);
37 | $once = $match[1];
38 |
39 | // 第二步:请求登录,获取当前金币数量
40 | $data = [
41 | 'u' => $username,
42 | 'p' => $password,
43 | 'once' => $once,
44 | 'next' => '/',
45 | ];
46 | $html = $this->request('/signin', '/signin', $data);
47 | $balance['before'] = $this->balance($html) or die('登录失败!');
48 |
49 | // 第三步:进入每日任务页面,提取金币领取地址
50 | $html = $this->request('/mission/daily', '/');
51 | preg_match('@/mission/daily/redeem\?once=\d+@', $html, $match) or die('当日已经领取');
52 | $url = $match[0];
53 |
54 | // 第四步:领取每日奖励,获取新的金币数量
55 | $html = $this->request($url, '/mission/daily');
56 | $balance['after'] = $this->balance($html);
57 |
58 | // 判断领取是否成功
59 | stripos($html, '每日登录奖励已领取') or die('领取金币失败');
60 |
61 | // 连续登录天数
62 | preg_match('@已连续登录 \d+ 天@', $html, $match);
63 | $days = $match[0];
64 |
65 | // 输出结果
66 | echo $days.',领取 '.($balance['after'] - $balance['before']).' 金币,账户余额 '.$balance['after'].' 金币';
67 | }
68 |
69 |
70 | # 析构函数
71 | public function __destruct()
72 | {
73 | // 关闭 CURL 共享载体
74 | if ($this->curls) curl_share_close($this->curls);
75 | }
76 |
77 |
78 | # 获取页面上当前金币的数量
79 | private function balance($html)
80 | {
81 | if (preg_match_all('@(\d+) @', $html, $matches)) return join('', $matches[1]);
82 | else return false;
83 | }
84 |
85 |
86 | # 封装 HTTP GET/POST 请求操作
87 | private function request($url, $referer, $data = false)
88 | {
89 | // 初始化 CURL 对象
90 | $curl = curl_init($this->domain.$url);
91 | curl_setopt_array($curl, [
92 | CURLOPT_SHARE => $this->curls,
93 | CURLOPT_REFERER => $this->domain.$referer,
94 | CURLOPT_USERAGENT => $this->useragent,
95 | CURLOPT_HEADER => 0,
96 | CURLOPT_SSL_VERIFYPEER => 0,
97 | CURLOPT_RETURNTRANSFER => 1,
98 | CURLOPT_FOLLOWLOCATION => 1,
99 | CURLOPT_HTTPHEADER => ['X-FORWARDED-FOR:'.$this->ip, 'CLIENT-IP:'.$this->ip],
100 | ]);
101 |
102 | // 如果为 $data 有参数则为 POST 请求
103 | if ($data && is_array($data))
104 | {
105 | curl_setopt_array($curl, [
106 | CURLOPT_POST => 1,
107 | CURLOPT_POSTFIELDS => $data,
108 | ]);
109 | }
110 |
111 | // 执行请求
112 | $response = curl_exec($curl);
113 | $httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
114 |
115 | // 如果有错误则中断程序
116 | if ($httpcode != '200') die('请求失败:HTTP '.$httpcode);
117 | if ($response === false) die('请求失败:'.curl_error($curl));
118 |
119 | // 关闭 CURL 请求,返回响应内容
120 | curl_close($curl);
121 | return $response;
122 | }
123 | }
124 |
125 | ?>
126 |
--------------------------------------------------------------------------------