mirror of
https://github.com/netcccyun/dnsmgr.git
synced 2026-07-04 23:25:35 +08:00
新增HE DNS
This commit is contained in:
@@ -442,6 +442,41 @@ class DnsHelper
|
||||
'page' => true,
|
||||
'add' => false,
|
||||
],
|
||||
'henet' => [
|
||||
'name' => 'HE DNS',
|
||||
'icon' => 'he.ico',
|
||||
'note' => '',
|
||||
'config' => [
|
||||
'username' => [
|
||||
'name' => '用户名/邮箱',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
],
|
||||
'password' => [
|
||||
'name' => '密码',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
],
|
||||
'proxy' => [
|
||||
'name' => '使用代理服务器',
|
||||
'type' => 'radio',
|
||||
'options' => [
|
||||
'0' => '否',
|
||||
'1' => '是',
|
||||
],
|
||||
'value' => '0'
|
||||
],
|
||||
],
|
||||
'remark' => 0,
|
||||
'status' => false,
|
||||
'redirect' => false,
|
||||
'log' => false,
|
||||
'weight' => false,
|
||||
'page' => true,
|
||||
'add' => false,
|
||||
],
|
||||
'spaceship' => [
|
||||
'name' => 'Spaceship',
|
||||
'icon' => 'spaceship.ico',
|
||||
@@ -699,6 +734,7 @@ class DnsHelper
|
||||
'qingcloud' => ['DEF' => '0', 'CT' => '2', 'CU' => '3', 'CM' => '4', 'AB' => '8'],
|
||||
'cloudflare' => ['DEF' => '0'],
|
||||
'namesilo' => ['DEF' => 'default'],
|
||||
'henet' => ['DEF' => 'default'],
|
||||
'powerdns' => ['DEF' => 'default'],
|
||||
'spaceship' => ['DEF' => 'default'],
|
||||
'aliyunesa' => ['DEF' => '0'],
|
||||
|
||||
@@ -0,0 +1,562 @@
|
||||
<?php
|
||||
|
||||
namespace app\lib\dns;
|
||||
|
||||
use app\lib\DnsInterface;
|
||||
use Exception;
|
||||
use think\facade\Cache;
|
||||
|
||||
class henet implements DnsInterface
|
||||
{
|
||||
private $username;
|
||||
private $password;
|
||||
private $baseUrl = 'https://dns.he.net/';
|
||||
private $error;
|
||||
private $domain;
|
||||
private $domainid;
|
||||
private $proxy;
|
||||
private $cookie = '';
|
||||
private $loggedIn = false;
|
||||
private $cacheKey;
|
||||
|
||||
public function __construct($config)
|
||||
{
|
||||
$this->username = $config['username'];
|
||||
$this->password = $config['password'];
|
||||
$this->domain = $config['domain'];
|
||||
$this->domainid = $config['domainid'];
|
||||
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
|
||||
$this->cacheKey = 'henet_cookie_' . md5($this->username . '|' . $this->password);
|
||||
$this->loadCachedSession();
|
||||
}
|
||||
|
||||
public function getError()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
public function check()
|
||||
{
|
||||
return $this->getDomainList() !== false;
|
||||
}
|
||||
|
||||
public function getDomainList($KeyWord = null, $PageNumber = 1, $PageSize = 20)
|
||||
{
|
||||
$html = $this->login();
|
||||
if ($html === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$list = $this->parseDomains($html);
|
||||
if (!isNullOrEmpty($KeyWord)) {
|
||||
$list = array_values(array_filter($list, function ($row) use ($KeyWord) {
|
||||
return stripos($row['Domain'], $KeyWord) !== false;
|
||||
}));
|
||||
}
|
||||
|
||||
$total = count($list);
|
||||
$offset = max(0, ($PageNumber - 1) * $PageSize);
|
||||
$list = array_slice($list, $offset, $PageSize);
|
||||
return ['total' => $total, 'list' => $list];
|
||||
}
|
||||
|
||||
public function getDomainRecords($PageNumber = 1, $PageSize = 20, $KeyWord = null, $SubDomain = null, $Value = null, $Type = null, $Line = null, $Status = null)
|
||||
{
|
||||
$zoneid = $this->getZoneId();
|
||||
if ($zoneid === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$html = $this->request('GET', 'index.cgi?' . http_build_query([
|
||||
'hosted_dns_zoneid' => $zoneid,
|
||||
'menu' => 'edit_zone',
|
||||
'hosted_dns_editzone' => '',
|
||||
]));
|
||||
if ($html === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$list = $this->parseRecords($html);
|
||||
if (!isNullOrEmpty($SubDomain)) {
|
||||
$list = array_values(array_filter($list, function ($row) use ($SubDomain) {
|
||||
return strcasecmp($row['Name'], $SubDomain) === 0;
|
||||
}));
|
||||
} else {
|
||||
if (!isNullOrEmpty($KeyWord)) {
|
||||
$list = array_values(array_filter($list, function ($row) use ($KeyWord) {
|
||||
return stripos($row['Name'], $KeyWord) !== false || stripos($row['Value'], $KeyWord) !== false;
|
||||
}));
|
||||
}
|
||||
if (!isNullOrEmpty($Value)) {
|
||||
$list = array_values(array_filter($list, function ($row) use ($Value) {
|
||||
return $row['Value'] == $Value;
|
||||
}));
|
||||
}
|
||||
if (!isNullOrEmpty($Type)) {
|
||||
$list = array_values(array_filter($list, function ($row) use ($Type) {
|
||||
return strcasecmp($row['Type'], $Type) === 0;
|
||||
}));
|
||||
}
|
||||
if (!isNullOrEmpty($Status)) {
|
||||
$list = array_values(array_filter($list, function ($row) use ($Status) {
|
||||
return $row['Status'] == $Status;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
$total = count($list);
|
||||
$offset = max(0, ($PageNumber - 1) * $PageSize);
|
||||
$list = array_slice($list, $offset, $PageSize);
|
||||
return ['total' => $total, 'list' => $list];
|
||||
}
|
||||
|
||||
public function getSubDomainRecords($SubDomain, $PageNumber = 1, $PageSize = 20, $Type = null, $Line = null)
|
||||
{
|
||||
if ($SubDomain == '') $SubDomain = '@';
|
||||
return $this->getDomainRecords($PageNumber, $PageSize, null, $SubDomain, null, $Type, $Line);
|
||||
}
|
||||
|
||||
public function getDomainRecordInfo($RecordId)
|
||||
{
|
||||
$records = $this->getDomainRecords(1, 1000);
|
||||
if ($records === false) {
|
||||
return false;
|
||||
}
|
||||
foreach ($records['list'] as $row) {
|
||||
if ($row['RecordId'] == $RecordId) {
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
$this->setError('解析记录不存在');
|
||||
return false;
|
||||
}
|
||||
|
||||
public function addDomainRecord($Name, $Type, $Value, $Line = 'default', $TTL = 600, $MX = 1, $Weight = null, $Remark = null)
|
||||
{
|
||||
$zoneid = $this->getZoneId();
|
||||
if ($zoneid === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$params = $this->buildRecordParams($zoneid, '', $Name, $Type, $Value, $TTL, $MX);
|
||||
$params['hosted_dns_editrecord'] = 'Submit';
|
||||
|
||||
$html = $this->request('POST', 'index.cgi', $params);
|
||||
if ($html !== false && $this->isSuccess($html, 'Successfully added new record')) {
|
||||
$recordid = $this->findRecordInHtml($html, $Name, $Type, $Value);
|
||||
return $recordid ? $recordid : true;
|
||||
}
|
||||
|
||||
$this->setError($this->extractMessage($html) ?: '添加解析记录失败');
|
||||
return false;
|
||||
}
|
||||
|
||||
public function updateDomainRecord($RecordId, $Name, $Type, $Value, $Line = 'default', $TTL = 600, $MX = 1, $Weight = null, $Remark = null)
|
||||
{
|
||||
$zoneid = $this->getZoneId();
|
||||
if ($zoneid === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$params = $this->buildRecordParams($zoneid, $RecordId, $Name, $Type, $Value, $TTL, $MX);
|
||||
$params['hosted_dns_editrecord'] = 'Update';
|
||||
|
||||
$html = $this->request('POST', 'index.cgi', $params);
|
||||
if ($html !== false && $this->isSuccess($html, 'Successfully updated record')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->setError($this->extractMessage($html) ?: '修改解析记录失败');
|
||||
return false;
|
||||
}
|
||||
|
||||
public function updateDomainRecordRemark($RecordId, $Remark)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function deleteDomainRecord($RecordId)
|
||||
{
|
||||
$zoneid = $this->getZoneId();
|
||||
if ($zoneid === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$html = $this->request('POST', 'index.cgi', [
|
||||
'menu' => 'edit_zone',
|
||||
'hosted_dns_zoneid' => $zoneid,
|
||||
'hosted_dns_recordid' => $RecordId,
|
||||
'hosted_dns_editzone' => '1',
|
||||
'hosted_dns_delrecord' => '1',
|
||||
'hosted_dns_delconfirm' => 'delete',
|
||||
]);
|
||||
if ($html !== false && $this->isSuccess($html, 'Successfully removed record')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->setError($this->extractMessage($html) ?: '删除解析记录失败');
|
||||
return false;
|
||||
}
|
||||
|
||||
public function setDomainRecordStatus($RecordId, $Status)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getDomainRecordLog($PageNumber = 1, $PageSize = 20, $KeyWord = null, $StartDate = null, $endDate = null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getRecordLine()
|
||||
{
|
||||
return ['default' => ['name' => '默认', 'parent' => null]];
|
||||
}
|
||||
|
||||
public function getMinTTL()
|
||||
{
|
||||
return 300;
|
||||
}
|
||||
|
||||
public function addDomain($Domain)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private function login()
|
||||
{
|
||||
if ($this->loggedIn) {
|
||||
return $this->request('GET', '');
|
||||
}
|
||||
|
||||
$this->request('GET', '', null, false);
|
||||
$html = $this->request('POST', '', [
|
||||
'email' => $this->username,
|
||||
'pass' => $this->password,
|
||||
], false);
|
||||
if ($html === false) {
|
||||
return false;
|
||||
}
|
||||
if (stripos($html, 'incorrect') !== false || stripos($html, 'Invalid') !== false) {
|
||||
$this->setError($this->extractMessage($html) ?: '登录失败,请检查用户名和密码');
|
||||
return false;
|
||||
}
|
||||
|
||||
$domains = $this->parseDomains($html);
|
||||
if (empty($domains) && stripos($html, 'hosted_dns_zoneid') === false) {
|
||||
$this->setError($this->extractMessage($html) ?: '登录失败,未找到域名列表');
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->loggedIn = true;
|
||||
$this->saveCachedSession();
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function getZoneId()
|
||||
{
|
||||
if (!isNullOrEmpty($this->domainid)) {
|
||||
return $this->domainid;
|
||||
}
|
||||
if (isNullOrEmpty($this->domain)) {
|
||||
$this->setError('未指定域名');
|
||||
return false;
|
||||
}
|
||||
|
||||
$domains = $this->getDomainList($this->domain, 1, 1000);
|
||||
if ($domains === false) {
|
||||
return false;
|
||||
}
|
||||
foreach ($domains['list'] as $row) {
|
||||
if (strcasecmp($row['Domain'], $this->domain) === 0) {
|
||||
$this->domainid = $row['DomainId'];
|
||||
return $this->domainid;
|
||||
}
|
||||
}
|
||||
|
||||
$this->setError('域名不存在或无权限访问');
|
||||
return false;
|
||||
}
|
||||
|
||||
private function buildRecordParams($zoneid, $recordid, $Name, $Type, $Value, $TTL, $MX)
|
||||
{
|
||||
return [
|
||||
'account' => '',
|
||||
'menu' => 'edit_zone',
|
||||
'Type' => strtoupper($Type),
|
||||
'hosted_dns_zoneid' => $zoneid,
|
||||
'hosted_dns_recordid' => $recordid,
|
||||
'hosted_dns_editzone' => '1',
|
||||
'Priority' => strtoupper($Type) == 'MX' ? intval($MX) : '',
|
||||
'Name' => $this->toFullName($Name),
|
||||
'Content' => $this->convertValue($Value, $Type),
|
||||
'TTL' => intval($TTL),
|
||||
];
|
||||
}
|
||||
|
||||
private function parseDomains($html)
|
||||
{
|
||||
$list = [];
|
||||
$patterns = [
|
||||
'/onclick="delete_dom\(this\);"[^>]*name="([^"]+)"[^>]*value="(\d+)"/i',
|
||||
'/name="([^"]+)"[^>]*value="(\d+)"[^>]*onclick="delete_dom\(this\);"/i',
|
||||
];
|
||||
foreach ($patterns as $pattern) {
|
||||
if (!preg_match_all($pattern, $html, $matches, PREG_SET_ORDER)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($matches as $match) {
|
||||
$domain = html_entity_decode($match[1], ENT_QUOTES);
|
||||
$zoneid = $match[2];
|
||||
$list[$zoneid] = [
|
||||
'DomainId' => $zoneid,
|
||||
'Domain' => $domain,
|
||||
'RecordCount' => 0,
|
||||
];
|
||||
}
|
||||
}
|
||||
if (preg_match_all('/hosted_dns_zoneid=(\d+)[^"\']*["\'][^>]*>\s*([^<]+)\s*</i', $html, $matches, PREG_SET_ORDER)) {
|
||||
foreach ($matches as $match) {
|
||||
$domain = trim(html_entity_decode($match[2], ENT_QUOTES));
|
||||
if ($domain !== '' && !isset($list[$match[1]])) {
|
||||
$list[$match[1]] = [
|
||||
'DomainId' => $match[1],
|
||||
'Domain' => $domain,
|
||||
'RecordCount' => 0,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
return array_values($list);
|
||||
}
|
||||
|
||||
private function parseRecords($html)
|
||||
{
|
||||
$list = [];
|
||||
if (!preg_match_all('/<tr class="dns_tr" [^>]*>.*?<\/tr>/is', $html, $rows, PREG_SET_ORDER)) {
|
||||
return $list;
|
||||
}
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$cells = $this->extractCells($row[0]);
|
||||
if (count($cells) < 4) continue;
|
||||
$ttl = is_numeric($cells[4]) ? intval($cells[4]) : 0;
|
||||
$name = $cells[2];
|
||||
$type = strtoupper($cells[3]);
|
||||
$value = $cells[6];
|
||||
$priority = is_numeric($cells[5]) ? intval($cells[5]) : null;
|
||||
|
||||
$list[] = [
|
||||
'RecordId' => $cells[1],
|
||||
'Domain' => $this->domain,
|
||||
'Name' => $this->fromFullName($name),
|
||||
'Type' => $type,
|
||||
'Value' => $this->normalizeValue($value, $type),
|
||||
'Line' => 'default',
|
||||
'TTL' => $ttl,
|
||||
'MX' => $priority,
|
||||
'Status' => stripos($row[0], 'disabled') !== false ? '0' : '1',
|
||||
'Weight' => null,
|
||||
'Remark' => null,
|
||||
'UpdateTime' => null,
|
||||
];
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
private function extractCells($html)
|
||||
{
|
||||
$cells = [];
|
||||
if (preg_match_all('/<td[^>]*>(.*?)<\/td>/is', $html, $matches)) {
|
||||
foreach ($matches[1] as $cell) {
|
||||
$cell = preg_replace('/<script\b[^>]*>.*?<\/script>/is', '', $cell);
|
||||
$cell = preg_replace('/<style\b[^>]*>.*?<\/style>/is', '', $cell);
|
||||
$cell = trim(html_entity_decode(strip_tags($cell), ENT_QUOTES));
|
||||
$cell = preg_replace('/\s+/', ' ', $cell);
|
||||
if ($cell !== '') {
|
||||
$cells[] = $cell;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $cells;
|
||||
}
|
||||
|
||||
private function findRecordInHtml($html, $Name, $Type, $Value)
|
||||
{
|
||||
$records = $this->parseRecords($html);
|
||||
$name = $this->fromFullName($this->toFullName($Name));
|
||||
$value = $this->normalizeValue($this->convertValue($Value, $Type), $Type);
|
||||
foreach ($records as $record) {
|
||||
if (
|
||||
strcasecmp($record['Name'], $name) === 0 &&
|
||||
strcasecmp($record['Type'], $Type) === 0 &&
|
||||
$record['Value'] == $value
|
||||
) {
|
||||
return $record;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function toFullName($name)
|
||||
{
|
||||
if ($name == '@' || $name == '') {
|
||||
return $this->domain;
|
||||
}
|
||||
if (str_ends_with($name, '.' . $this->domain) || strcasecmp($name, $this->domain) === 0) {
|
||||
return $name;
|
||||
}
|
||||
return $name . '.' . $this->domain;
|
||||
}
|
||||
|
||||
private function fromFullName($name)
|
||||
{
|
||||
$name = rtrim($name, '.');
|
||||
if (strcasecmp($name, $this->domain) === 0) {
|
||||
return '@';
|
||||
}
|
||||
if (str_ends_with(strtolower($name), '.' . strtolower($this->domain))) {
|
||||
return substr($name, 0, -(strlen($this->domain) + 1));
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
private function convertValue($value, $type)
|
||||
{
|
||||
if (strtoupper($type) == 'TXT') {
|
||||
return trim($value, '"');
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function normalizeValue($value, $type)
|
||||
{
|
||||
$value = trim($value);
|
||||
if (strtoupper($type) == 'TXT') {
|
||||
return trim($value, '"');
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function isSuccess($html, $message)
|
||||
{
|
||||
return stripos($html, $message) !== false;
|
||||
}
|
||||
|
||||
private function extractMessage($html)
|
||||
{
|
||||
if (!$html) {
|
||||
return null;
|
||||
}
|
||||
if (preg_match('/<(?:div|span)[^>]*class="[^"]*(?:error|warn|success|message)[^"]*"[^>]*>(.*?)<\/(?:div|span)>/is', $html, $match)) {
|
||||
return trim(html_entity_decode(strip_tags($match[1]), ENT_QUOTES));
|
||||
}
|
||||
if (preg_match('/(Successfully [^<]+|Error:[^<]+|Invalid [^<]+)/i', $html, $match)) {
|
||||
return trim(html_entity_decode($match[1], ENT_QUOTES));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function request($method, $path, $params = null, $requireLogin = true, $allowRelogin = true)
|
||||
{
|
||||
if ($requireLogin && !$this->loggedIn && $this->login() === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$url = str_starts_with($path, 'http') ? $path : $this->baseUrl . $path;
|
||||
try {
|
||||
$response = http_request($url, $params ? http_build_query($params) : null, $this->baseUrl, $this->cookie, null, $this->proxy, $method, 20);
|
||||
} catch (Exception $e) {
|
||||
$this->setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->storeCookie($response['headers']);
|
||||
if ($response['code'] >= 400) {
|
||||
$this->setError('HTTP请求失败:' . $response['code']);
|
||||
return false;
|
||||
}
|
||||
if ($requireLogin && $allowRelogin && $this->isLoginExpired($response['body'])) {
|
||||
$this->clearCachedSession();
|
||||
if ($this->login() === false) {
|
||||
return false;
|
||||
}
|
||||
return $this->request($method, $path, $params, true, false);
|
||||
}
|
||||
return $response['body'];
|
||||
}
|
||||
|
||||
private function storeCookie($headers)
|
||||
{
|
||||
$cookies = [];
|
||||
if ($this->cookie !== '') {
|
||||
foreach (explode('; ', $this->cookie) as $cookie) {
|
||||
$parts = explode('=', $cookie, 2);
|
||||
if (count($parts) == 2) {
|
||||
$cookies[$parts[0]] = $parts[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($headers as $name => $values) {
|
||||
if (strtolower($name) !== 'set-cookie') {
|
||||
continue;
|
||||
}
|
||||
foreach ((array)$values as $value) {
|
||||
$cookie = explode(';', $value, 2)[0];
|
||||
$parts = explode('=', $cookie, 2);
|
||||
if (count($parts) == 2) {
|
||||
$cookies[$parts[0]] = $parts[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pairs = [];
|
||||
foreach ($cookies as $name => $value) {
|
||||
$pairs[] = $name . '=' . $value;
|
||||
}
|
||||
$this->cookie = implode('; ', $pairs);
|
||||
if ($this->loggedIn && $this->cookie !== '') {
|
||||
$this->saveCachedSession();
|
||||
}
|
||||
}
|
||||
|
||||
private function loadCachedSession()
|
||||
{
|
||||
$cookie = Cache::get($this->cacheKey);
|
||||
if (is_string($cookie) && $cookie !== '') {
|
||||
$this->cookie = $cookie;
|
||||
$this->loggedIn = true;
|
||||
}
|
||||
}
|
||||
|
||||
private function saveCachedSession()
|
||||
{
|
||||
if ($this->cookie !== '') {
|
||||
Cache::set($this->cacheKey, $this->cookie, 3600);
|
||||
}
|
||||
}
|
||||
|
||||
private function clearCachedSession()
|
||||
{
|
||||
$this->cookie = '';
|
||||
$this->loggedIn = false;
|
||||
Cache::delete($this->cacheKey);
|
||||
}
|
||||
|
||||
private function isLoginExpired($html)
|
||||
{
|
||||
if (!$html) {
|
||||
return false;
|
||||
}
|
||||
return stripos($html, 'name="email"') !== false
|
||||
&& stripos($html, 'name="pass"') !== false
|
||||
&& stripos($html, 'hosted_dns_zoneid') === false;
|
||||
}
|
||||
|
||||
private function setError($message)
|
||||
{
|
||||
$this->error = $message;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Reference in New Issue
Block a user