├── 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 | --------------------------------------------------------------------------------