fn_b/app/Services/AppleIdArticleLayout.php

80 lines
4.3 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Services;
use DOMDocument;
use DOMElement;
use DOMXPath;
/** Presentation-only adaptation: keep every original account/password pair intact. */
final class AppleIdArticleLayout
{
public static function prepare(string $html): ?array
{
if (!preg_match('/免费苹果(?:账号|ID)/u', $html)) return null;
$dom = new DOMDocument('1.0', 'UTF-8');
$before = libxml_use_internal_errors(true);
try {
$dom->loadHTML('<?xml encoding="UTF-8"><html><body><div id="aid-root">'.str_replace(ArticleRecommendations::SLOT, '', $html).'</div></body></html>', LIBXML_NONET);
$root = $dom->getElementById('aid-root');
if (!$root) return null;
$xpath = new DOMXPath($dom);
$copies = $xpath->query('.//*[@data-clipboard-text]', $root);
if (!$copies || $copies->length % 2) return null;
// Empty historical updates are valid, but plaintext account rows must not disappear.
if (!$copies->length && (preg_match('/账号[::]|密码[::]/u', $root->textContent)
|| !$xpath->query('.//*[self::h1 or self::h2 or self::h3 or self::h4 or self::h5 or self::h6][starts-with(normalize-space(.), "免费苹果")]', $root)->length)) return null;
$result = ['accounts' => [], 'regions' => [], 'warnings' => [], 'intro' => '', 'outro' => ''];
$containers = [];
for ($i = 0; $i < $copies->length; $i += 2) {
$account = $copies->item($i);
$password = $copies->item($i + 1);
$container = $account->parentNode;
while ($container && $container !== $root && $container->parentNode !== $root) $container = $container->parentNode;
// Unknown nesting, labels or additional copy controls retain the legacy rendering.
if (!$container instanceof DOMElement || $container->parentNode !== $root || $container->tagName !== 'p') return null;
$fields = $xpath->query('.//*[@data-clipboard-text]', $container);
if ($fields->length !== 2 || !$fields->item(0)->isSameNode($account) || !$fields->item(1)->isSameNode($password)) return null;
$text = self::text($container->textContent);
if (!preg_match('/^账号[::]\s*.+[,,]\s*密码[::]\s*.+[,,]\s*地区[::]\s*(.*?)[,,]\s*检测时间[::]\s*(.*?)\s*$/u', $text, $match)) return null;
$username = $account->getAttribute('data-clipboard-text');
$secret = $password->getAttribute('data-clipboard-text');
if ($username === '' || $secret === '') return null;
$region = trim($match[1]);
if ($region === '') $region = '未注明';
$result['accounts'][] = ['account' => $username, 'password' => $secret, 'region' => $region, 'checked_at' => trim($match[2])];
$result['regions'][$region] = ($result['regions'][$region] ?? 0) + 1;
$containers[] = $container;
}
$seenAccount = false;
foreach (iterator_to_array($root->childNodes) as $node) {
$isAccount = false;
foreach ($containers as $container) if ($node->isSameNode($container)) { $isAccount = true; break; }
if ($isAccount) { $seenAccount = true; continue; }
$text = self::text($node->textContent);
if ($text === '') continue;
if ($node instanceof DOMElement && preg_match('/^免费苹果(?:账号|ID)/u', $text) && preg_match('/^h[1-6]$/', $node->tagName)) continue;
if (str_contains($text, '必须从App Store登录') && str_contains($text, 'iCloud')) {
$warning = preg_replace('/^[\x{26A0}\x{FE0F}\s]+/u', '', $text);
if (!in_array($warning, $result['warnings'], true)) $result['warnings'][] = $warning;
continue;
}
$result[$seenAccount ? 'outro' : 'intro'] .= $dom->saveHTML($node);
}
return $result;
} catch (\Throwable) {
return null;
} finally {
libxml_clear_errors();
libxml_use_internal_errors($before);
}
}
private static function text(string $text): string
{
return trim(preg_replace('/\s+/u', ' ', $text));
}
}