Files
links/patches/application/common/library/scrm/spider/WhatshubSpider.php
T

130 lines
4.1 KiB
PHP
Executable File

<?php
declare(strict_types=1);
namespace app\common\library\scrm\spider;
use app\common\library\scrm\AbstractScrmSpider;
use app\common\library\scrm\AntiBotConfigBuilder;
use app\common\library\scrm\UnifiedScrmData;
/**
* Whatshub 云控蜘蛛(Real Browser + Turnstile 过盾 + UI 翻页)
*
* 时序:Cloudflare Turnstile → 密码弹窗 authActions → API 拦截 → UI 翻页
*/
class WhatshubSpider extends AbstractScrmSpider
{
/** 列表 API 路径 */
private const API_LIST = '/api/whatshub-counter/workShare/open/detail';
/** 详情/统计 API 路径 */
private const API_DETAILS = '/api/whatshub-counter/workShare/open/statistics';
/** 默认每页条数 */
private const DEFAULT_PER_PAGE_COUNT = 20;
private string $pageUrl;
private string $account;
private string $password;
private UnifiedScrmData $unifiedData;
public function __construct(
string $pageUrl,
string $account = '',
string $password = '',
string $nodeHost = 'http://127.0.0.1:3001'
) {
parent::__construct($nodeHost);
$this->pageUrl = $pageUrl;
$this->account = $account;
$this->password = $password;
$this->unifiedData = new UnifiedScrmData();
}
/** @return array<string, mixed> */
protected function getSpiderConfig(): array
{
return [
'pageUrl' => $this->pageUrl,
'listApi' => self::API_LIST,
'detailApi' => self::API_DETAILS,
'listMethod' => 'POST',
'paginationMode' => self::MODE_UI,
'authActions' => [
// Element Plus 密码弹窗:仅注入可见 input
['type' => 'vue_fill', 'selector' => '.el-input__inner', 'value' => $this->password],
['type' => 'wait', 'ms' => 500],
['type' => 'vue_click', 'selector' => '.vxe-button-group .theme--primary'],
['type' => 'wait', 'ms' => 2200],
],
'antiBot' => AntiBotConfigBuilder::build('whatshub', $this->pageUrl),
];
}
/**
* @param array<string, mixed>|null $listFirstPageData
* @param array<string, mixed>|null $countData
*/
protected function extractListTotalPages($listFirstPageData, $countData = null)
{
$defaultPerPage = self::DEFAULT_PER_PAGE_COUNT;
$this->unifiedData->total = (int) ($listFirstPageData['data']['total'] ?? 0);
if ($this->unifiedData->total <= $defaultPerPage) {
return 1;
}
return (int) ceil($this->unifiedData->total / $defaultPerPage);
}
/** @return array<string, mixed> */
protected function buildListPageParams(int $page): array
{
return ['pageNum' => $page, 'pageSize' => self::DEFAULT_PER_PAGE_COUNT];
}
/** @return array<string, mixed> */
protected function getUiPaginationConfig(): array
{
return [
'nextBtnSelector' => '.vxe-pager--next-btn',
'waitMs' => 1500,
];
}
protected function parseToUnifiedData($detailData, array $allListPagesData): UnifiedScrmData
{
$unifiedData = $this->unifiedData;
if (is_array($detailData)) {
$unifiedData->todayNewCount = (int) ($detailData['data']['dayNewFans'] ?? 0);
}
foreach ($allListPagesData as $pageRaw) {
if (!is_array($pageRaw)) {
continue;
}
$records = $pageRaw['data']['rows'] ?? [];
if (!is_array($records)) {
continue;
}
foreach ($records as $item) {
if (!is_array($item) || empty($item['account'])) {
continue;
}
$number = (string) $item['account'];
$isOnline = isset($item['isOnline']) && (int) $item['isOnline'] === 1;
$unifiedData->addNumber($number, $isOnline, (int) ($item['dayNewFans'] ?? 0));
}
}
$unifiedData->total = count($unifiedData->numbers);
return $unifiedData;
}
}