feat: 后端全量更新 - 含所有本次需求

- Contract.php: 返回合约账户余额(balance_contract)
- My.php: 地址管理增加BTC/ETH
- AppContract.php: 一键平仓(closeall)
- AppProxy.php: 代理专属注册链接 + 分级权限(L1/L2)
- site.php: 手续费减半(0.018→0.009)
- agent_permission_setup.sql: 代理权限SQL
- crypto_news_crawler.py: 新闻自动采集脚本
This commit is contained in:
li
2026-03-30 20:16:32 +08:00
commit 1b24994e74
6721 changed files with 1308571 additions and 0 deletions
@@ -0,0 +1,341 @@
<?php
namespace Stichoza\GoogleTranslate;
use ErrorException;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Stichoza\GoogleTranslate\Tokens\GoogleTokenGenerator;
use Stichoza\GoogleTranslate\Tokens\TokenProviderInterface;
use UnexpectedValueException;
/**
* Free Google Translate API PHP Package.
*
* @author Levan Velijanashvili <me@stichoza.com>
* @link http://stichoza.com/
* @license MIT
*/
class GoogleTranslate
{
/**
* @var \GuzzleHttp\Client HTTP Client
*/
protected $client;
/**
* @var string|null Source language - from where the string should be translated
*/
protected $source;
/**
* @var string Target language - to which language string should be translated
*/
protected $target;
/**
* @var string|null Last detected source language
*/
protected $lastDetectedSource;
/**
* @var string Google Translate URL base
*/
protected $usUrl = 'https://translate.google.com/translate_a/single';
protected $url = 'https://translate.google.cn/translate_a/single';
/**
* @var array Dynamic GuzzleHttp client options
*/
protected $options = [];
/**
* @var array URL Parameters
*/
protected $urlParams = [
'client' => 'webapp',
'hl' => 'en',
'dt' => [
't', // Translate
'bd', // Full translate with synonym ($bodyArray[1])
'at', // Other translate ($bodyArray[5] - in google translate page this shows when click on translated word)
'ex', // Example part ($bodyArray[13])
'ld', // I don't know ($bodyArray[8])
'md', // Definition part with example ($bodyArray[12])
'qca', // I don't know ($bodyArray[8])
'rw', // Read also part ($bodyArray[14])
'rm', // I don't know
'ss' // Full synonym ($bodyArray[11])
],
'sl' => null, // Source language
'tl' => null, // Target language
'q' => null, // String to translate
'ie' => 'UTF-8', // Input encoding
'oe' => 'UTF-8', // Output encoding
'multires' => 1,
'otf' => 0,
'pc' => 1,
'trs' => 1,
'ssel' => 0,
'tsel' => 0,
'kc' => 1,
'tk' => null,
];
/**
* @var array Regex key-value patterns to replace on response data
*/
protected $resultRegexes = [
'/,+/' => ',',
'/\[,/' => '[',
];
/**
* @var TokenProviderInterface Token provider
*/
protected $tokenProvider;
/**
* Class constructor.
*
* For more information about HTTP client configuration options, see "Request Options" in
* GuzzleHttp docs: http://docs.guzzlephp.org/en/stable/request-options.html
*
* @param string $target Target language
* @param string|null $source Source language
* @param array|null $options Associative array of http client configuration options
* @param TokenProviderInterface|null $tokenProvider
*/
public function __construct(string $target = 'en', string $source = null, array $options = null, TokenProviderInterface $tokenProvider = null)
{
$this->client = new Client();
$this->setTokenProvider($tokenProvider ?? new GoogleTokenGenerator)
->setOptions($options) // Options are already set in client constructor tho.
->setSource($source)
->setTarget($target);
}
/**
* Set target language for translation.
*
* @param string $target Language code
* @return GoogleTranslate
*/
public function setTarget(string $target): self
{
$this->target = $target;
return $this;
}
/**
* Set source language for translation.
*
* @param string|null $source Language code
* @return GoogleTranslate
*/
public function setSource(string $source = null): self
{
$this->source = $source ?? 'auto';
return $this;
}
/**
* Set Google Translate URL base
*
* @param string $url Google Translate URL base
* @return GoogleTranslate
*/
public function setUrl(string $url): self
{
$this->url = $url;
return $this;
}
/**
* Set GuzzleHttp client options.
*
* @param array $options guzzleHttp client options.
* @return GoogleTranslate
*/
public function setOptions(array $options = null): self
{
$this->options = $options ?? [];
return $this;
}
/**
* Set token provider.
*
* @param TokenProviderInterface $tokenProvider
* @return GoogleTranslate
*/
public function setTokenProvider(TokenProviderInterface $tokenProvider): self
{
$this->tokenProvider = $tokenProvider;
return $this;
}
/**
* Get last detected source language
*
* @return string|null Last detected source language
*/
public function getLastDetectedSource(): ?string
{
return $this->lastDetectedSource;
}
/**
* Override translate method for static call.
*
* @param string $string
* @param string $target
* @param string|null $source
* @param array $options
* @param TokenProviderInterface|null $tokenProvider
* @return null|string
* @throws ErrorException If the HTTP request fails
* @throws UnexpectedValueException If received data cannot be decoded
*/
public static function trans(string $string, string $target = 'en', string $source = null, array $options = [], TokenProviderInterface $tokenProvider = null): ?string
{
return (new self)
->setTokenProvider($tokenProvider ?? new GoogleTokenGenerator)
->setOptions($options) // Options are already set in client constructor tho.
->setSource($source)
->setTarget($target)
->translate($string);
}
/**
* Translate text.
*
* This can be called from instance method translate() using __call() magic method.
* Use $instance->translate($string) instead.
*
* @param string $string String to translate
* @return string|null
* @throws ErrorException If the HTTP request fails
* @throws UnexpectedValueException If received data cannot be decoded
*/
public function translate(string $string): ?string
{
/*
* if source lang and target lang are the same
* just return the string without any request to google
*/
if ($this->source == $this->target) return $string;
$responseArray = $this->getResponse($string);
/*
* if response in text and the content has zero the empty returns true, lets check
* if response is string and not empty and create array for further logic
*/
if (is_string($responseArray) && $responseArray != '') {
$responseArray = [$responseArray];
}
// Check if translation exists
if (!isset($responseArray[0]) || empty($responseArray[0])) {
return null;
}
// Detect languages
$detectedLanguages = [];
// the response contains only single translation, don't create loop that will end with
// invalid foreach and warning
if (!is_string($responseArray)) {
foreach ($responseArray as $item) {
if (is_string($item)) {
$detectedLanguages[] = $item;
}
}
}
// Another case of detected language
if (isset($responseArray[count($responseArray) - 2][0][0])) {
$detectedLanguages[] = $responseArray[count($responseArray) - 2][0][0];
}
// Set initial detected language to null
$this->lastDetectedSource = null;
// Iterate and set last detected language
foreach ($detectedLanguages as $lang) {
if ($this->isValidLocale($lang)) {
$this->lastDetectedSource = $lang;
break;
}
}
// the response can be sometimes an translated string.
if (is_string($responseArray)) {
return $responseArray;
} else {
if (is_array($responseArray[0])) {
return (string) array_reduce($responseArray[0], function ($carry, $item) {
$carry .= $item[0];
return $carry;
});
} else {
return (string) $responseArray[0];
}
}
}
/**
* Get response array.
*
* @param string $string String to translate
* @throws ErrorException If the HTTP request fails
* @throws UnexpectedValueException If received data cannot be decoded
* @return array|string Response
*/
public function getResponse(string $string): array
{
$queryArray = array_merge($this->urlParams, [
'sl' => $this->source,
'tl' => $this->target,
'tk' => $this->tokenProvider->generateToken($this->source, $this->target, $string),
'q' => $string
]);
$queryUrl = preg_replace('/%5B(?:[0-9]|[1-9][0-9]+)%5D=/', '=', http_build_query($queryArray));
try {
$response = $this->client->get($this->url, [
'query' => $queryUrl,
] + $this->options);
} catch (RequestException $e) {
throw new ErrorException($e->getMessage());
}
$body = $response->getBody(); // Get response body
// Modify body to avoid json errors
$bodyJson = preg_replace(array_keys($this->resultRegexes), array_values($this->resultRegexes), $body);
// Decode JSON data
if (($bodyArray = json_decode($bodyJson, true)) === null) {
throw new UnexpectedValueException('Data cannot be decoded or it is deeper than the recursion limit');
}
return $bodyArray;
}
/**
* Check if given locale is valid.
*
* @param string $lang Langauge code to verify
* @return bool
*/
protected function isValidLocale(string $lang): bool
{
return (bool) preg_match('/^([a-z]{2})(-[A-Z]{2})?$/', $lang);
}
}
@@ -0,0 +1,165 @@
<?php
namespace Stichoza\GoogleTranslate\Tokens;
/**
* Google Token Generator.
*
* Thanks to @helen5106 and @tehmaestro and few other cool guys
* at https://github.com/Stichoza/google-translate-php/issues/32
*/
class GoogleTokenGenerator implements TokenProviderInterface
{
/**
* Generate and return a token.
*
* @param string $source Source language
* @param string $target Target language
* @param string $text Text to translate
* @return string Token
*/
public function generateToken(string $source, string $target, string $text): string
{
return $this->TL($text);
}
/**
* Generate a valid Google Translate request token.
*
* @param string $a text to translate
*
* @return string
*/
private function TL($a)
{
$tkk = $this->TKK();
$b = $tkk[0];
for ($d = [], $e = 0, $f = 0; $f < $this->JS_length($a); $f++) {
$g = $this->JS_charCodeAt($a, $f);
if (128 > $g) {
$d[$e++] = $g;
} else {
if (2048 > $g) {
$d[$e++] = $g >> 6 | 192;
} else {
if (55296 == ($g & 64512) && $f + 1 < $this->JS_length($a) && 56320 == ($this->JS_charCodeAt($a, $f + 1) & 64512)) {
$g = 65536 + (($g & 1023) << 10) + ($this->JS_charCodeAt($a, ++$f) & 1023);
$d[$e++] = $g >> 18 | 240;
$d[$e++] = $g >> 12 & 63 | 128;
} else {
$d[$e++] = $g >> 12 | 224;
}
$d[$e++] = $g >> 6 & 63 | 128;
}
$d[$e++] = $g & 63 | 128;
}
}
$a = $b;
for ($e = 0; $e < count($d); $e++) {
$a += $d[$e];
$a = $this->RL($a, '+-a^+6');
}
$a = $this->RL($a, '+-3^+b+-f');
$a ^= $tkk[1] ? $tkk[1] + 0 : 0;
if (0 > $a) {
$a = ($a & 2147483647) + 2147483648;
}
$a = fmod($a, pow(10, 6));
return $a.'.'.($a ^ $b);
}
/**
* @return array
*/
private function TKK()
{
return ['406398', (561666268 + 1526272306)];
}
/**
* Process token data by applying multiple operations.
* (Params are safe, no need for multibyte functions)
*
* @param int $a
* @param string $b
*
* @return int
*/
private function RL($a, $b)
{
for ($c = 0; $c < strlen($b) - 2; $c += 3) {
$d = $b[$c + 2];
$d = 'a' <= $d ? ord($d[0]) - 87 : intval($d);
$d = '+' == $b[$c + 1] ? $this->unsignedRightShift($a, $d) : $a << $d;
$a = '+' == $b[$c] ? ($a + $d & 4294967295) : $a ^ $d;
}
return $a;
}
/**
* Unsigned right shift implementation
* https://msdn.microsoft.com/en-us/library/342xfs5s(v=vs.94).aspx
* http://stackoverflow.com/a/43359819/2953830
*
* @param $a
* @param $b
*
* @return number
*/
private function unsignedRightShift($a, $b)
{
if ($b >= 32 || $b < -32) {
$m = (int)($b / 32);
$b = $b - ($m * 32);
}
if ($b < 0) {
$b = 32 + $b;
}
if ($b == 0) {
return (($a >> 1) & 0x7fffffff) * 2 + (($a >> $b) & 1);
}
if ($a < 0) {
$a = ($a >> 1);
$a &= 2147483647;
$a |= 0x40000000;
$a = ($a >> ($b - 1));
} else {
$a = ($a >> $b);
}
return $a;
}
/**
* Get JS charCodeAt equivalent result with UTF-16 encoding
*
* @param string $str
* @param int $index
*
* @return number
*/
private function JS_charCodeAt($str, $index)
{
$utf16 = mb_convert_encoding($str, 'UTF-16LE', 'UTF-8');
return ord($utf16[$index*2]) + (ord($utf16[$index*2+1]) << 8);
}
/**
* Get JS equivalent string length with UTF-16 encoding
*
* @param string $str
*
* @return number
*/
private function JS_length($str)
{
$utf16 = mb_convert_encoding($str, 'UTF-16LE', 'UTF-8');
return strlen($utf16)/2;
}
}
@@ -0,0 +1,22 @@
<?php
namespace Stichoza\GoogleTranslate\Tokens;
/**
* A nice interface for providing tokens.
*/
class SampleTokenGenerator implements TokenProviderInterface
{
/**
* Generate a fake token just as an example.
*
* @param string $source Source language
* @param string $target Target langiage
* @param string $text Text to translate
* @return string Token
*/
public function generateToken(string $source, string $target, string $text): string
{
return sprintf('%d.%d', rand(10000, 99999), rand(10000, 99999));
}
}
@@ -0,0 +1,19 @@
<?php
namespace Stichoza\GoogleTranslate\Tokens;
/**
* A nice interface for providing tokens.
*/
interface TokenProviderInterface
{
/**
* Generate and return a token.
*
* @param string $source Source language
* @param string $target Target langiage
* @param string $text Text to translate
* @return string Token
*/
public function generateToken(string $source, string $target, string $text): string;
}
@@ -0,0 +1,222 @@
<?php
use think\Exception;
use Stichoza\GoogleTranslate\GoogleTranslate;
use app\admin\model\Proxy as ProxyIp;
/**
* @param $text 需要翻译的内容-可以是任意语种文本-不超过5000字符
* @param $toLang 要翻译成目标语种
* @param string $fromLang 原始文本的语种-不填写可以自动识别-填写错误会导致翻译无效
* @param bool $useProxy 是否使用代理-可以传入代理 true 自动随机代理 false 不使用代理
* @param int $timout 超时时间
* @param int $retryTime 失败重试次数
* @return array ['status' => boolean 'data' => result, 'msg' => 'errormsg']
* @throws ErrorException
*/
function doGoogleTranslate($text, $toLang, $fromLang = '', $useProxy = false, $timout = 5, $retryTime = 5)
{
$result = [
'data' => '',
'status' => false,
'msg' => '',
];
$text = filterHtmlEntryChar($text);
do{
try {
$tr = new GoogleTranslate();
if ($fromLang) {
$tr->setSource($fromLang);
}
$tr->setTarget($toLang);
if ($useProxy !== false) {
$options = [];
if ($useProxy === true) {
//从配置文件中取出代理
$proxyIps = get_addon_fullconfig('translate');
$proxyIps = $proxyIps[0]['value'];
if ($proxyIps) {
$proxyIps = array_unique(explode(PHP_EOL, $proxyIps));
$proxyIndex = array_rand($proxyIps, 1);
$proxy = trim($proxyIps[$proxyIndex]);
} else {
$proxy = false;
}
if ($proxy) {
$options = [
'proxy' => [$proxy],
'timeout' => $timout,
];
}
} else {
$options = [
'proxy' => [$useProxy],
'timeout' => $timout,
];
}
if ($options) {
$tr->setOptions($options);
}
}
$result['data'] = $tr->translate($text);
$result['status'] = true;
} catch (Exception $e) {
$result['data'] = '';
$result['status'] = false;
$result['msg'] = $e->getMessage();
}
} while($retryTime-- > 0 && $result['status'] === false);
return $result;
}
function filterHtmlEntryChar($str)
{
$str = htmlspecialchars_decode($str);//过滤掉html实体 避免谷歌翻译失败 https://www.cnblogs.com/mengmengi/p/10137167.html
//过滤替换掉会导致谷歌翻译的失败的标签
$seachAndReplace = [
"&lt;" => "<",
"&gt;" => ">",
"&amp;" => "&",
"&quot;" => "“",
"&reg;" => "®",
"&copy;" => "©",
"&trade;" => "™",
"&ensp;" => "",
"&emsp;" => "",
"&nbsp;" => "",
"&acute;" => "´",
"&amp;" => "&",
"&brvbar;" => "¦",
"&bull;" => "•",
"&cedil;" => "¸",
"&cent;" => "¢",
"&euro;" => "€",
"&bdquo;" => "„",
"&circ;" => "ˆ",
"&dagger;" => "†",
"&Dagger;" => "‡",
"&asymp;" => "≈",
"&cap;" => "∩",
"&clubs;" => "♣",
"&darr;" => "↓",
"&diams;" => "♦",
"&equiv;" => "≡",
"&alpha;" => "α",
"&beta;" => "β",
"&chi;" => "χ",
"&delta;" => "δ",
"&epsilon;" => "ε",
"&Alpha;" => "Α",
"&Beta;" => "Β",
"&Chi;" => "Χ",
"&Delta;" => "Δ",
"&Epsilon;" => "Ε",
"&copy;" => "©",
"&deg;" => "°",
"&divide;" => "÷",
"&frac12;" => "½",
"&frac14;" => "¼",
"&frac34;" => "¾",
"&pound;" => "£",
"&hellip;" => "…",
"&ldquo;" => "“",
"&lsaquo;" => "‹",
"&lsquo;" => "‘",
"&frasl;" => "⁄",
"&ge;" => "≥",
"&harr;" => "↔",
"&hearts;" => "♥",
"&infin;" => "∞",
"&int;" => "∫",
"&eta;" => "η",
"&gamma;" => "γ",
"&iota;" => "ι",
"&kappa;" => "κ",
"&lambda;" => "λ",
"&Eta;" => "Η",
"&Gamma;" => "Γ",
"&Iota;" => "Ι",
"&Kappa;" => "Κ",
"&Lambda;" => "Λ",
"&gt;" => ">",
"&iexcl;" => "¡",
"&iquest;" => "¿",
"&laquo;" => "«",
"&lt;" => "<",
"&macr;" => "¯",
"&yen;" => "¥",
"&middot;" => "·",
"&mdash;" => "—",
"&ndash;" => "–",
"&permil;" => "‰",
"&larr;" => "←",
"&le;" => "≤",
"&loz;" => "◊",
"&minus;" => "−",
"&ne;" => "≠",
"&oline;" => "‾",
"&mu;" => "μ",
"&nu;" => "ν",
"&omega;" => "ω",
"&omicron;" => "ο",
"&phi;" => "φ",
"&Mu;" => "Μ",
"&Nu;" => "Ν",
"&Omega;" => "Ω",
"&Omicron;" => "Ο",
"&Phi;" => "Φ",
"&micro;" => "µ",
"&not;" => "¬",
"&para;" => "",
"&plusmn;" => "±",
"&quot;" => "“",
"&rsaquo;" => "›",
"&rsquo;" => "’",
"&sbquo;" => "‚",
"&shy;" => "",
"&part;" => "∂",
"&Prime;" => "″",
"&prime;" => "′",
"&prod;" => "∏",
"&radic;" => "√",
"&rarr;" => "→",
"&pi;" => "π",
"&psi;" => "ψ",
"&rho;" => "ρ",
"&sigma;" => "σ",
"&tau;" => "τ",
"&Pi;" => "Π",
"&Psi;" => "Ψ",
"&Rho;" => "Ρ",
"&Sigma;" => "Σ",
"&Tau;" => "Τ",
"&reg;" => "®",
"&raquo;" => "»",
"&sect;" => "§",
"&uml;" => "¨",
"&times;" => "×",
"&trade;" => "™",
"&ordf;" => "ª",
"&ordm;" => "º",
"&rdquo;" => "”",
"&tilde;" => "˜",
"&spades;" => "♠",
"&sum;" => "∑",
"&uarr;" => "↑",
"&zwj;" => "‍ ",
"&zwnj;" => "‌ ",
"&theta;" => "θ",
"&upsilon;" => "υ",
"&xi;" => "ξ",
"&zeta;" => "ζ",
"&Theta;" => "Θ",
"&Upsilon;" => "Υ",
"&Xi;" => "Ξ",
"&Zeta;" => "Ζ",
"&sigmaf;" => "ς",
];
if (strpos($str, '&') !== false) {
$str = str_replace(array_keys($seachAndReplace), array_values($seachAndReplace), $str);
}
return $str;
}