葫芦侠一键签到落地 PHP 源码(基于 /api/hlxa) [复制链接]

管理员组

葫芦侠板块签到 · 落地 PHP 源码(v3 UI 优化版)

基于 api.hk0.cc 网关实现葫芦侠(三楼)板块一键签到,单文件即可运行。

本版本增强

  • 支持手动选择板块签到(可多选)——板块列表从 /api/hlx?action=categories 实时拉取,支持动态板块数量
  • 账号密码在页面输入并保存在【本地浏览器 localStorage】,不会写死到源码
  • 全新玻璃拟态 UI,适配移动端:板块搜索过滤、密码显示/隐藏、结果分色明细 + 成功/失败/经验汇总卡片
  • 单个 / 多个 / 一键全部签到,实时回显每条结果与经验值

运行环境

PHP7.0 及以上,推荐 7.4 / 8.x
扩展需开启 cUrl(宝塔面板默认已开启)
数据库无需 MySQL
框架无需任何框架,单文件即可运行

部署步骤

  1. hlxa_signin.php 上传到任意可用 PHP 站点的网站目录
  2. 浏览器访问该文件
  3. 输入葫芦侠账号(手机号)与密码(自动保存在本地浏览器,下次免输)
  4. 勾选要签到的板块,或点 签到全部板块

命令行自动签到(可配宝塔计划任务 / crontab)

php hlxa_signin.php --auto --key=你的key cat_id1 cat_id2   # 签指定板块
php hlxa_signin.php --auto --all                           # 签全部板块

不传 --key 时,可把 {"account":"手机号","password":"密码"} 写入同目录 .hlxa_auther 自动登录,避免源码写死账号密码。

接口来源(api.hk0.cc 网关)

  • 板块列表 https://api.hk0.cc/api/hlx?action=categories
  • 登录 https://api.hk0.cc/api/hlxa?action=login&account=&password=
  • 签到 https://api.hk0.cc/api/hlxa?action=signin&_key=&cat_id=

附件下载

完整源码与使用说明见附件:hlxa_signin_v3.zip(内含 hlxa_signin.php + README.txt,共 8986 字节)

v3 相对 v2 的改进

  • 全新玻璃拟态 UI,适配移动端
  • 板块搜索过滤、密码可见切换
  • 签到结果分色明细 + 成功/失败/经验汇总卡片
  • 本地记忆账号密码与上次勾选的板块

源码(完整版见附件)

<?php
/**
 * =============================================================================
 * 葫芦侠(三楼)板块签到 · 落地 PHP 源码(UI 优化版 v3)
 * =============================================================================
 * 基于 api.hk0.cc 网关接口实现签到:
 *   - 板块列表 GET /api/hlx?action=categories   (全部板块,前端勾选)
 *   - 登录     GET /api/hlxa?action=login       (拿 _key)
 *   - 签到     GET /api/hlxa?action=signin      (单板块 cat_id 或全部)
 *
 * 账号密码在页面输入并保存在本地浏览器(localStorage),不会写死到源码。
 *
 * 【运行环境】
 *   PHP 7.0+,开启 cUrl 扩展(宝塔面板默认已开启)。
 *   无需 MySQL、无需任何框架,单文件即可运行。
 *
 * 【部署】
 *  1. 本文件另存为 hlxa_signin.php,上传到任意 PHP 站点目录。
 *  2. 浏览器访问该文件即可。
 *  3. 命令行/计划任务自动签到时:
 *     php hlxa_signin.php --auto --key=你的key cat_id1 cat_id2
 *     php hlxa_signin.php --auto --all
 *     未配 --key 时,请将 {"account":"手机号","password":"密码"} 写入
 *     本文件同目录的 .hlxa_auther(不写死到源码)。
 * =============================================================================
 */

// ---------------- 接口地址(可改) ----------------
$API = array(
  'categories' => 'https://api.hk0.cc/api/hlx?action=categories',
  'login'      => 'https://api.hk0.cc/api/hlxa?action=login',
  'signin'     => 'https://api.hk0.cc/api/hlxa?action=signin',
);

// ---------------- 工具:转发请求 ----------------
function sk_proxy($url, $timeout = 20) {
  $ch = curl_init();
  curl_setopt_array($ch, array(
    CURLOPT_URL            => $url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => $timeout,
    CURLOPT_CONNECTTIMEOUT => min(10, $timeout),
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_SSL_VERIFYPEER => false,
    CURLOPT_SSL_VERIFYHOST => false,
    CURLOPT_USERAGENT      => 'Mozilla/5.0 (hlxa-signin)',
  ));
  $res = curl_exec($ch);
  $err = curl_error($ch);
  curl_close($ch);
  if ($res === false) {
    return array('code' => 502, 'success' => false, 'message' => 'cUrl 错误: ' . $err);
  }
  $j = json_decode($res, true);
  return is_array($j) ? $j : array('code' => 503, 'success' => false, 'message' => $res);
}

header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
if (($_SERVER['REQUEST_METHOD'] ?? '') === 'OPTIONS') { exit; }

// ---------------- CLI / 定时自动签到 ----------------
if ((PHP_SAPI === 'cli') && array_key_exists('argv', $_SERVER)) {
  $args = $_SERVER['argv'];
  $mode = 'auto';
  $opt  = array('key' => '', 'cats' => array(), 'all' => false);
  foreach (array_slice($args, 1) as $a) {
    if ($a === '--auto')                      { $mode = 'auto'; }
    elseif (strpos($a, '--key=') === 0)       { $opt['key'] = substr($a, 6); }
    elseif ($a === '--all')                   { $opt['all'] = true; }
    elseif (ctype_digit($a))                  { $opt['cats'][] = (int)$a; }
  }
  if ($mode !== 'auto') { exit("用法: php 本文件.php [cat_id] [--key=xxx] [--all]\n"); }
  exit(sk_cli_auto($opt));
}

// ---------------- HTTP 接口(前端调用) ----------------
$action = isset($_REQUEST['action']) ? $_REQUEST['action'] : 'page';

if ($action === 'categories') {
  echo json_encode(sk_proxy($API['categories']), JSON_UNESCAPED_UNICODE);
  exit;
}

if ($action === 'login') {
  $account  = isset($_POST['account'])  ? trim((string)$_POST['account'])  : '';
  $password = isset($_POST['password']) ? trim((string)$_POST['password']) : '';
  if ($account === '' || $password === '') {
    echo json_encode(array('code' => 400, 'success' => false, 'message' => '请输入账号与密码'));
    exit;
  }
  $url = $API['login'] . '&account=' . rawurlencode($account) . '&password=' . rawurlencode($password);
  echo json_encode(sk_proxy($url), JSON_UNESCAPED_UNICODE);
  exit;
}

if ($action === 'signin') {
  $key    = isset($_POST['key']) ? trim((string)$_POST['key']) : '';
  $all    = (isset($_POST['all']) && (string)$_POST['all'] === '1');
  $catsRaw = isset($_POST['cat_ids']) ? (array)$_POST['cat_ids'] : array();
  if ($key === '') {
    echo json_encode(array('code' => 400, 'success' => false, 'message' => '请先登录获取 _key'));
    exit;
  }
  $cats = array();
  foreach ($catsRaw as $c) { if (ctype_digit((string)$c) && (int)$c > 0) $cats[] = (int)$c; }
  $cats = array_values(array_unique($cats));

  $result = array('code' => 200, 'success' => true, 'total' => count($cats), 'results' => array());
  if ($all) {
    $url = $API['signin'] . '&_key=' . rawurlencode($key);
    $r = sk_proxy($url);
    if (empty($r['success'])) {
      $result['success'] = false;
      $result['message'] = isset($r['message']) ? $r['message'] : ('错误 code=' . $r['code']);
    } else {
      $d = isset($r['data']) ? $r['data'] : array();
      $result['total']           = isset($d['total'])           ? $d['total']           : 0;
      $result['success_count']   = isset($d['success_count'])   ? $d['success_count']   : 0;
      $result['fail_count']      = isset($d['fail_count'])      ? $d['fail_count']      : 0;
      $result['total_experience']= isset($d['total_experience']) ? $d['total_experience'] : 0;
      if (!empty($d['details'])) $result['results'] = $d['details'];
    }
  } else {
    $ok = 0; $fail = 0; $exp = 0;
    foreach ($cats as $cid) {
      $url = $API['signin'] . '&_key=' . rawurlencode($key) . '&cat_id=' . $cid;
      $r = sk_proxy($url);
      $item = array('cat_id' => $cid, 'success' => false, 'experience_val' => 0, 'message' => '');
      if (!empty($r['success'])) {
        $dd = isset($r['data']) ? $r['data'] : array();
        $item['success'] = true;
        $item['experience_val']  = isset($dd['experience_val'])      ? $dd['experience_val']      : 0;
        $item['continue_days']   = isset($dd['continue_days'])       ? $dd['continue_days']       : 0;
        $item['is_first_sign']   = isset($dd['is_first_sign_today']) ? $dd['is_first_sign_today'] : 0;
        $exp += $item['experience_val']; $ok++;
      } else {
        $item['message'] = isset($r['message']) ? $r['message'] : 'error';
        $fail++;
      }
      $result['results'][] = $item;
    }
    $result['success_count']    = $ok;
    $result['fail_count']       = $fail;
    $result['total_experience'] = $exp;
  }
  echo json_encode($result, JSON_UNESCAPED_UNICODE);
  exit;
}

// ---------------- CLI 自动签到实现 ----------------
function sk_cli_auto($opt) {
  global $API;
  $key = $opt['key'];
  $lines = array();
  $lines[] = '========== 葫芦侠自动签到 ' . date('Y-m-d H:i:s') . ' ==========';
  $log = function ($s) use (&$lines) { $lines[] = $s; };

  if ($key === '') {
    $confFile = __DIR__ . '/.hlxa_auther';
    if (is_file($confFile)) {
      $c = @json_decode(trim(file_get_contents($confFile)), true);
      if (is_array($c) && !empty($c['account']) && !empty($c['password'])) {
        $url = $API['login'] . '&account=' . rawurlencode($c['account']) . '&password=' . rawurlencode($c['password']);
        $r = sk_proxy($url);
        if (!empty($r['success']) && !empty($r['data']['_key'])) {
          $key = $r['data']['_key'];
          $log('[登录成功] ' . (isset($r['data']['nick']) ? $r['data']['nick'] : ''));
        } else {
          $log('[登录失败] ' . (isset($r['message']) ? $r['message'] : ''));
        }
      }
    }
    if ($key === '') {
      $log('[错误] 未找到 .hlxa_auther 配置文件,或账号密码有误。');
      $log('[提示] 首次 CLI 自动签到:将 {"account":"手机号","password":"密码"} 写入本文件同目录 .hlxa_auther');
      return implode("\n", $lines) . "\n";
    }
  }

  if ($opt['all'] || empty($opt['cats'])) {
    $url = $API['signin'] . '&_key=' . rawurlencode($key);
    $r = sk_proxy($url);
    if (empty($r['success'])) {
      $log('[签到失败] ' . (isset($r['message']) ? $r['message'] : ''));
    } else {
      $d = isset($r['data']) ? $r['data'] : array();
      $log('[全部板块] 共 ' . $d['total'] . ' 板块, 成功 ' . $d['success_count'] . ', 失败 ' . $d['fail_count'] . ', 经验 +' . $d['total_experience']);
      if (!empty($d['details'])) {
        foreach ($d['details'] as $it) {
          $log(sprintf(" %-4s %-12s %s +%d", $it['cat_id'], isset($it['title']) ? $it['title'] : '', $it['success'] ? '成功' : '失败', isset($it['experience_val']) ? $it['experience_val'] : 0));
        }
      }
    }
  } else {
    foreach ($opt['cats'] as $cid) {
      $url = $API['signin'] . '&_key=' . rawurlencode($key) . '&cat_id=' . $cid;
      $r = sk_proxy($url);
      if (!empty($r['success'])) {
        $d = isset($r['data']) ? $r['data'] : array();
        $log('[板块 ' . $cid . '] 成功, +' . $d['experience_val'] . ' 经验, 连续 ' . $d['continue_days'] . ' 天');
      } else {
        $log('[板块 ' . $cid . '] 失败, ' . (isset($r['message']) ? $r['message'] : ''));
      }
    }
  }
  $log('========== 完成 ==========');
  return implode("\n", $lines) . "\n";
}

// ---------------- 页面(HTML) ----------------
if ($action === 'page') {
  header('Content-Type: text/html; charset=utf-8');
  echo <<<'HTML'
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#0f2027">
<title>葫芦侠板块签到</title>
<style>
:root{--bg1:#0f2027;--bg2:#203a43;--bg3:#2c5364;--card:rgba(255,255,255,.07);--line:rgba(255,255,255,.12);--txt:#e8eef5;--mut:#93a1b4;--acc:#4da3ff;--acc2:#9b6bff;--ok:#41d17a;--bad:#ff6b6b}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif;background:linear-gradient(135deg,var(--bg1),var(--bg2),var(--bg3));min-height:100vh;color:var(--txt);padding:clamp(12px,3vw,28px)}
.wrap{max-width:760px;margin:0 auto}
.card{background:var(--card);border:1px solid var(--line);border-radius:18px;padding:clamp(16px,4vw,26px);margin-bottom:16px;backdrop-filter:blur(8px);box-shadow:0 12px 40px rgba(0,0,0,.25)}
.head{display:flex;align-items:center;gap:10px;margin-bottom:6px}
.head .logo{width:40px;height:40px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:22px;background:linear-gradient(135deg,var(--acc),var(--acc2))}
h1{font-size:20px;line-height:1.2}
h1 small{display:block;font-size:12px;font-weight:400;color:var(--mut);margin-top:2px}
.sub{color:var(--mut);font-size:13px;margin-bottom:4px}
label{display:block;font-size:13px;color:#b8c4d2;margin:12px 0 6px}
input[type=text],input[type=password]{width:100%;background:rgba(0,0,0,.28);border:1px solid var(--line);border-radius:10px;padding:11px 12px;color:var(--txt);font-size:15px;outline:none;transition:.15s}
input:focus{border-color:var(--acc);box-shadow:0 0 0 3px rgba(77,163,255,.15)}
.pwdwrap{position:relative}
.pwdwrap .eye{position:absolute;right:10px;top:50%;transform:translateY(-50%);background:none;border:none;color:var(--mut);cursor:pointer;font-size:16px;padding:4px}
.toolbar{display:flex;justify-content:space-between;align-items:center;gap:8px;margin:14px 0 8px}
.toolbar label{margin:0}
.search{flex:1;max-width:220px;position:relative}
.search input{width:100%;padding:8px 10px;padding-right:28px;font-size:13px}
.search .sicon{position:absolute;right:10px;top:50%;transform:translateY(-50%);color:var(--mut);font-size:13px;pointer-events:none}
.cats{max-height:340px;overflow-y:auto;border:1px solid var(--line);border-radius:12px;padding:4px}
.cat{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:10px;cursor:pointer;transition:background .12s}
.cat:hover{background:rgba(255,255,255,.06)}
.cat.off{opacity:.45}
.cat img{width:34px;height:34px;border-radius:9px;object-fit:cover;background:rgba(255,255,255,.08);flex:0 0 auto}
.cat .t{flex:1;min-width:0}
.cat .t b{display:block;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cat .t small{color:var(--mut);font-size:12px}
.cat input{transform:scale(1.25);accent-color:var(--acc);flex:0 0 auto}
.selbar{display:flex;justify-content:space-between;align-items:center;margin:6px 4px 0}
.selbar .info{color:var(--mut);font-size:12px}
.summary{display:flex;gap:10px;margin-top:14px}
.summary .s{flex:1;text-align:center;background:rgba(0,0,0,.22);border:1px solid var(--line);border-radius:12px;padding:10px 6px}
.summary .s b{display:block;font-size:20px}
.summary .s span{color:var(--mut);font-size:11px}
.summary .s.ok b{color:var(--ok)}
.summary .s.bad b{color:var(--bad)}
.summary .s.exp b{color:var(--acc2)}
.btn{background:linear-gradient(135deg,var(--acc),var(--acc2));border:none;color:#fff;border-radius:12px;padding:13px 20px;font-size:15px;cursor:pointer;width:100%;font-weight:600;transition:opacity .15s,transform .08s;margin-top:14px}
.btn:hover{opacity:.9}
.btn:active{transform:scale(.99)}
.btn:disabled{opacity:.5;cursor:not-allowed}
.btn.ghost{background:rgba(255,255,255,.08);border:1px solid var(--line);width:auto;padding:7px 12px;font-size:13px;font-weight:400}
.btn.ghost:disabled{opacity:.4}
.msg{margin-top:14px;border-radius:12px;padding:14px;font-size:13px;line-height:1.7;background:rgba(0,0,0,.3);display:none;max-height:320px;overflow-y:auto}
.msg.open{display:block}
.msg.ok{border:1px solid rgba(65,209,122,.35)}
.msg.err{border:1px solid rgba(255,107,107,.4)}
.msg .line{display:flex;justify-content:space-between;gap:8px;padding:4px 2px;border-bottom:1px dashed rgba(255,255,255,.07)}
.msg .line:last-child{border-bottom:none}
.msg .line .st{flex:0 0 auto}
.msg .line.ok .st{color:var(--ok)}
.msg .line.bad .st{color:var(--bad)}
.msg .blank{color:var(--mut);text-align:center;padding:12px 0}
.foot{color:#5b6b79;font-size:12px;text-align:center;margin-top:2px}
@media(max-width:520px){.summary{gap:6px}.summary .s b{font-size:17px}.search{max-width:150px}}
</style>
</head>
<body>
<div class="wrap">

  <div class="card">
    <div class="head">
      <div class="logo">🌿</div>
      <h1>葫芦侠板块签到<small>落地版 v3 · PHP 7.0+ · 单文件</small></h1>
    </div>
    <div class="sub">账号密码仅保存在当前浏览器(localStorage),不会写入服务器文件。</div>

    <label>葫芦侠账号(手机号)</label>
    <input type="text" id="account" placeholder="请输入葫芦侠账号" autocomplete="off">

    <label>密码</label>
    <div class="pwdwrap">
      <input type="password" id="password" placeholder="请输入密码">
      <button class="eye" id="eye" type="button" title="显示/隐藏密码">👁</button>
    </div>

    <div class="toolbar">
      <label>选择签到板块(可多选)</label>
      <div class="search">
        <input type="text" id="kw" placeholder="搜索板块…">
        <span class="sicon">⌕</span>
      </div>
    </div>
    <div class="cats" id="cats"><div class="sub" style="padding:12px">正在加载板块…</div></div>

    <div class="selbar">
      <span class="info" id="selinfo">已选 0 / 0 个板块</span>
      <button class="btn ghost" id="selAll">全选</button>
    </div>

    <button class="btn" id="signBtn">一键签到选中板块</button>
    <button class="btn ghost" id="signAllBtn" style="width:100%">签到全部板块</button>

    <div class="summary" id="summary" style="display:none">
      <div class="s ok"><b id="sOk">0</b><span>签到成功</span></div>
      <div class="s bad"><b id="sBad">0</b><span>失败</span></div>
      <div class="s exp"><b id="sExp">+0</b><span>经验</span></div>
    </div>

    <div class="msg" id="msg"></div>
  </div>

  <div class="foot">接口由 api.hk0.cc 提供 · GUI 模式浏览器访问 · CLI 模式支持宝塔计划任务 / crontab</div>
</div>

<script>
(function(){
  var $=function(id){return document.getElementById(id)};
  var KA="sk_hlxa_account", KP="sk_hlxa_pass", KS="sk_hlxa_cats";
  var cats=[], sel={}, kw="";

  if(localStorage.getItem(KA)) $("account").value=localStorage.getItem(KA);
  if(localStorage.getItem(KP)) $("password").value=localStorage.getItem(KP);
  try{ sel=JSON.parse(localStorage.getItem(KS)||"{}"); }catch(e){ sel={}; }

  function save(){
    localStorage.setItem(KA,$("account").value);
    localStorage.setItem(KP,$("password").value);
    localStorage.setItem(KS,JSON.stringify(sel));
  }
  function esc(s){ var d=document.createElement("div"); d.textContent=s==null?"":String(s); return d.innerHTML; }

  $("eye").onclick=function(){
    var p=$("password");
    var show=p.type==="password";
    p.type=show?"text":"password";
    this.textContent=show?"🙈":"👁";
  };

  $("kw").addEventListener("input", function(){ kw=this.value.trim().toLowerCase(); renderCats(); });

  function filtered(){
    if(!kw) return cats;
    return cats.filter(function(c){ return (c.title||"").toLowerCase().indexOf(kw)>=0; });
  }

  function renderCats(){
    var box=$("cats"), list=filtered();
    if(!cats.length){ box.innerHTML='<div class="sub" style="padding:12px">暂无板块数据</div>'; return; }
    var h="";
    list.forEach(function(c){
      var on=!!sel[c.id];
      h+='<div class="cat"'+(kw ? ' data-k="'+esc(c.title)+'"' : '')+' onclick="toggle('+c.id+')">'
        +'<input type="checkbox"'+(on ? ' checked' : '')+' onclick="event.stopPropagation();toggle('+c.id+')">'
        +'<img src="'+esc(c.icon)+'" onerror="this.style.display=\'none\'">'
        +'<div class="t"><b>'+esc(c.title)+'</b><small>帖子 '+(c.post_size>=0?c.post_size:(c.post_count||0))+'</small></div>'
        +'</div>';
    });
    box.innerHTML=h||'<div class="sub" style="padding:12px">未找到匹配板块</div>';
    updSel();
  }
  window.toggle=function(id){
    sel[id]=sel[id]?undefined:true;
    save(); renderCats();
  };
  function countSel(){ var n=0; for(var k in sel) if(sel[k]) n++; return n; }
  function updSel(){
    var n=countSel();
    $("selinfo").textContent="已选 "+n+" / "+cats.length+" 个板块";
    $("selAll").textContent=(n&&n===cats.length)?"取消全选":"全选";
  }
  $("selAll").onclick=function(){
    if(countSel()===cats.length){ sel={}; }
    else { cats.forEach(function(c){ sel[c.id]=true; }); }
    save(); renderCats();
  };

  function show(msg, ok){
    var m=$("msg");
    m.className="msg open "+(ok?"ok":"err");
    m.textContent=msg;
  }
  function showLines(lines, ok){
    var m=$("msg");
    m.className="msg open "+(ok?"ok":"err");
    if(!lines.length){ m.innerHTML='<div class="blank">暂无明细</div>'; return; }
    var h="";
    lines.forEach(function(it){
      if(it.kind==="blank"){ h+='<div class="blank">'+esc(it.text)+'</div>'; return; }
      h+='<div class="line '+(it.ok?'ok':'bad')+'"><span>'+esc(it.text)+'</span><span class="st">'+esc(it.st)+'</span></div>';
    });
    m.innerHTML=h;
  }
  function setSummary(ok, bad, exp){
    var s=$("summary");
    s.style.display=ok||bad?"flex":"none";
    $("sOk").textContent=ok; $("sBad").textContent=bad; $("sExp").textContent=exp>=0?("+"+exp):"-";
  }
  function setBusy(btn, busy, label){
    btn.disabled=busy;
    btn.textContent=busy?("签到中…"):label;
  }

  function api(action, data, cb, errCb){
    var fd=new FormData();
    for(var k in data) fd.append(k,data[k]);
    fetch("?action="+action,{method:"POST",body:fd})
      .then(function(r){return r.json()})
      .then(cb)
      .catch(function(e){ if(errCb) errCb(e); });
  }

  function buildLines(res, items){
    var lines=[];
    if(items && items.length){
      items.forEach(function(r){
        var d=r;
        var ok1=!!r.success;
        var exp1=r.experience_val||0;
        lines.push({ok:ok1, text:"板块 "+r.cat_id+" · "+(r.title||""), st:ok1?("成功 +"+exp1+" 经验"):("失败 "+(r.message||"未知"))});
      });
    }
    if(res.total_experience!=null){
      lines.unshift({ok:true, text:"签到完成", st:"共"+res.total+" / 成功"+res.success_count+" / 失败"+res.fail_count});
    }
    setSummary(res.success_count||0, res.fail_count||0, res.total_experience);
    return lines;
  }

  function sign(kind, _key){
    var account=$("account").value.trim(), password=$("password").value;
    if(!account||!password){ show("请先输入账号和密码",false); return; }
    save();
    var btn=(kind==="all")?$("signAllBtn"):$("signBtn");
    setBusy(btn,true,btn.textContent);
    show("正在登录…",true);
    api("login",{account:account,password:password},function(login){
      if(!login.success){
        setBusy(btn,false,kind==="all"?"签到全部板块":"一键签到选中板块");
        show("登录失败:"+(login.message||"未知错误"),false);
        return;
      }
      var key=login.data._key;
      var data={key:key};
      if(kind==="all"){ data.all="1"; }
      else{
        var ids=[]; for(var k in sel) if(sel[k]) ids.push(k);
        if(!ids.length){
          setBusy(btn,false,"一键签到选中板块");
          show("未选择任何板块",false); return;
        }
        data.cat_ids=ids;
      }
      setBusy(btn,true,"签到中…");
      show("正在签到…",true);
      api("signin",data,function(res){
        setBusy(btn,false,kind==="all"?"签到全部板块":"一键签到选中板块");
        if(res.success){
          var lines=buildLines(res, res.results||[]);
          showLines(lines, true);
        } else {
          setSummary(0,0,-1);
          show(res.message||"签到失败",false);
        }
      },function(e){ setBusy(btn,false,kind==="all"?"签到全部板块":"一键签到选中板块"); show("网络错误:"+e,false); });
    },function(e){ setBusy(btn,false,kind==="all"?"签到全部板块":"一键签到选中板块"); show("网络错误:"+e,false); });
  }
  $("signBtn").onclick=function(){ sign("sel"); };
  $("signAllBtn").onclick=function(){ sign("all"); };

  fetch("?action=categories").then(function(r){return r.json()}).then(function(d){
    if(d.success){
      cats=d.data||[];
      for(var i=0;i<cats.length;i++){ var c=cats[i]; if(c.post_size==null&&c.post_count!=null) c.post_size=c.post_count; }
      // 默认勾选上次选择;若从未选过则全不选
    } else {
      $("cats").innerHTML='<div class="sub" style="padding:12px">加载板块失败:'+esc(d.message||d.code)+'</div>';
    }
    renderCats();
  }).catch(function(){
    $("cats").innerHTML='<div class="sub" style="padding:12px">加载板块失败(网络错误)</div>';
    renderCats();
  });
})();
</script>
</body>
</html>
HTML;
}

完整 500 行源码(含全新 UI 与 CLI 自动签到)见附件 hlxa_signin_v3.zip。

hlxa_signin_v3.zip 8.8KB
最新回复

请先登录后再回复 登录

uid:1 管理员组
关注
错过了不该错过的人,那就是遗憾!
发帖 1418
评论 93
粉丝 1
关注 0
发新帖
目录
葫芦侠一键签到落地 PHP 源码(基于 /api/hlxa)