LinkController.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843
  1. <?php
  2. declare(strict_types=1);
  3. //Thanks to http://blog.csdn.net/jollyjumper/article/details/9823047
  4. namespace App\Controllers;
  5. use Link;
  6. use Psr\Http\Message\ResponseInterface;
  7. use Request;
  8. use URL;
  9. use voku\helper\AntiXSS;
  10. /**
  11. * LinkController
  12. */
  13. class LinkController extends BaseController
  14. {
  15. public static function GenerateRandomLink()
  16. {
  17. for ($i = 0; $i < 10; $i++) {
  18. $token = Tools::genRandomChar(16);
  19. $Elink = Link::where('token', $token)->first();
  20. if ($Elink === null) {
  21. return $token;
  22. }
  23. }
  24. return "couldn't alloc token";
  25. }
  26. public static function GenerateSSRSubCode(int $userid): string
  27. {
  28. $Elink = Link::where('userid', $userid)->first();
  29. if ($Elink !== null) {
  30. return $Elink->token;
  31. }
  32. $NLink = new Link();
  33. $NLink->userid = $userid;
  34. $NLink->token = self::GenerateRandomLink();
  35. $NLink->save();
  36. return $NLink->token;
  37. }
  38. /**
  39. * @param array $args
  40. */
  41. public static function GetContent(Request $request, Response $response, array $args)
  42. {
  43. if (! $_ENV['Subscribe']) {
  44. return null;
  45. }
  46. $token = $args['token'];
  47. $Elink = Link::where('token', $token)->first();
  48. if ($Elink === null) {
  49. return null;
  50. }
  51. $user = $Elink->getUser();
  52. if ($user === null) {
  53. return null;
  54. }
  55. $opts = $request->getQueryParams();
  56. // 筛选节点部分
  57. $Rule['type'] = (isset($opts['type']) ? trim($opts['type']) : 'all');
  58. $Rule['is_mu'] = ($_ENV['mergeSub'] === true ? 1 : 0);
  59. if (isset($opts['mu'])) {
  60. $Rule['is_mu'] = (int) $opts['mu'];
  61. }
  62. if (isset($opts['class'])) {
  63. $class = trim(urldecode($opts['class']));
  64. $Rule['content']['class'] = array_map(
  65. function ($item) {
  66. return (int) $item;
  67. },
  68. explode('-', $class)
  69. );
  70. }
  71. if (isset($opts['noclass'])) {
  72. $noclass = trim(urldecode($opts['noclass']));
  73. $Rule['content']['noclass'] = array_map(
  74. function ($item) {
  75. return (int) $item;
  76. },
  77. explode('-', $noclass)
  78. );
  79. }
  80. if (isset($opts['regex'])) {
  81. $Rule['content']['regex'] = trim(urldecode($opts['regex']));
  82. }
  83. // Emoji
  84. $Rule['emoji'] = $_ENV['add_emoji_to_node_name'];
  85. if (isset($opts['emoji'])) {
  86. $Rule['emoji'] = (bool) $opts['emoji'];
  87. }
  88. // 显示流量以及到期时间等
  89. $Rule['extend'] = $_ENV['enable_sub_extend'];
  90. if (isset($opts['extend'])) {
  91. $Rule['extend'] = (bool) $opts['extend'];
  92. }
  93. // 兼容原版
  94. if (isset($opts['mu'])) {
  95. $mu = (int) $opts['mu'];
  96. switch ($mu) {
  97. case 0:
  98. $opts['sub'] = 1;
  99. break;
  100. case 1:
  101. $opts['sub'] = 1;
  102. break;
  103. case 2:
  104. $opts['sub'] = 3;
  105. break;
  106. case 3:
  107. $opts['ssd'] = 1; //deprecated
  108. break;
  109. case 4:
  110. $opts['clash'] = 1;
  111. break;
  112. }
  113. }
  114. // 订阅类型
  115. $subscribe_type = '';
  116. $getBody = '';
  117. $sub_type_array = ['list', 'clash', 'surge', 'surfboard','anxray', 'quantumult', 'quantumultx', 'sub'];
  118. foreach ($sub_type_array as $key) {
  119. if (isset($opts[$key])) {
  120. $query_value = $opts[$key];
  121. if ($query_value !== '0' && $query_value !== '') {
  122. // 兼容代码开始
  123. if ($key === 'sub' && $query_value > 4) {
  124. $query_value = 1;
  125. }
  126. // 兼容代码结束
  127. if ($key === 'list') {
  128. $SubscribeExtend = self::getSubscribeExtend($query_value);
  129. } else {
  130. $SubscribeExtend = self::getSubscribeExtend($key, $query_value);
  131. }
  132. $filename = $SubscribeExtend['filename'] . '_' . time() . '.' . $SubscribeExtend['suffix'];
  133. $subscribe_type = $SubscribeExtend['filename'];
  134. $class = 'get' . $SubscribeExtend['class'];
  135. $content = self::$class($user, $query_value, $opts, $Rule);
  136. $getBody = self::getBody(
  137. $user,
  138. $response,
  139. $content,
  140. $filename
  141. );
  142. break;
  143. }
  144. continue;
  145. }
  146. }
  147. // 记录订阅日志
  148. if ($_ENV['subscribeLog'] === true) {
  149. self::Subscribe_log($user, $subscribe_type, $request->getHeaderLine('User-Agent'));
  150. }
  151. return $getBody;
  152. }
  153. /**
  154. * 获取订阅类型的文件名
  155. *
  156. * @param string $type 订阅类型
  157. * @param string|null $value 值
  158. *
  159. * @return array
  160. */
  161. public static function getSubscribeExtend(string $type, ?string $value = null): array
  162. {
  163. switch ($type) {
  164. case 'ss':
  165. $return = [
  166. 'filename' => 'SS',
  167. 'suffix' => 'txt',
  168. 'class' => 'Sub',
  169. ];
  170. break;
  171. case 'ssa':
  172. $return = [
  173. 'filename' => 'SSA',
  174. 'suffix' => 'json',
  175. 'class' => 'Lists',
  176. ];
  177. break;
  178. case 'ssr':
  179. $return = [
  180. 'filename' => 'SSR',
  181. 'suffix' => 'txt',
  182. 'class' => 'Sub',
  183. ];
  184. break;
  185. case 'sub':
  186. $strArray = [
  187. 1 => 'ssr',
  188. 2 => 'ss',
  189. 3 => 'v2rayn',
  190. 4 => 'trojan',
  191. ];
  192. $str = (! in_array($value, $strArray) ? $strArray[$value] : $strArray[1]);
  193. $return = self::getSubscribeExtend($str);
  194. break;
  195. case 'clash':
  196. if ($value !== null) {
  197. $return = self::getSubscribeExtend('clash');
  198. $return['class'] = 'Clash';
  199. } else {
  200. $return = [
  201. 'filename' => 'Clash',
  202. 'suffix' => 'yaml',
  203. 'class' => 'Lists',
  204. ];
  205. }
  206. break;
  207. case 'surge':
  208. if ($value !== null) {
  209. $return = [
  210. 'filename' => 'Surge',
  211. 'suffix' => 'conf',
  212. 'class' => 'Surge',
  213. ];
  214. $return['filename'] .= $value;
  215. } else {
  216. $return = [
  217. 'filename' => 'SurgeList',
  218. 'suffix' => 'list',
  219. 'class' => 'Lists',
  220. ];
  221. }
  222. break;
  223. case 'v2rayn':
  224. $return = [
  225. 'filename' => 'V2RayN',
  226. 'suffix' => 'txt',
  227. 'class' => 'Sub',
  228. ];
  229. break;
  230. case 'trojan':
  231. $return = [
  232. 'filename' => 'Trojan',
  233. 'suffix' => 'txt',
  234. 'class' => 'Sub',
  235. ];
  236. break;
  237. case 'kitsunebi':
  238. $return = [
  239. 'filename' => 'Kitsunebi',
  240. 'suffix' => 'txt',
  241. 'class' => 'Lists',
  242. ];
  243. break;
  244. case 'anxray':
  245. $return = [
  246. 'filename' => 'AnXray',
  247. 'suffix' => 'txt',
  248. 'class' => 'AnXray',
  249. ];
  250. break;
  251. case 'surfboard':
  252. $return = [
  253. 'filename' => 'Surfboard',
  254. 'suffix' => 'conf',
  255. 'class' => 'Surfboard',
  256. ];
  257. break;
  258. case 'quantumult':
  259. if ($value !== null) {
  260. if ((int) $value === 2) {
  261. $return = self::getSubscribeExtend('quantumult_sub');
  262. } else {
  263. $return = self::getSubscribeExtend('quantumult_conf');
  264. }
  265. } else {
  266. $return = [
  267. 'filename' => 'Quantumult',
  268. 'suffix' => 'conf',
  269. 'class' => 'Lists',
  270. ];
  271. }
  272. break;
  273. case 'quantumultx':
  274. $return = [
  275. 'filename' => 'QuantumultX',
  276. 'suffix' => 'txt',
  277. 'class' => 'Lists',
  278. ];
  279. if ($value !== null) {
  280. $return['class'] = 'QuantumultX';
  281. }
  282. break;
  283. case 'shadowrocket':
  284. $return = [
  285. 'filename' => 'Shadowrocket',
  286. 'suffix' => 'txt',
  287. 'class' => 'Lists',
  288. ];
  289. break;
  290. case 'clash_provider':
  291. $return = [
  292. 'filename' => 'ClashProvider',
  293. 'suffix' => 'yaml',
  294. 'class' => 'Lists',
  295. ];
  296. break;
  297. case 'quantumult_sub':
  298. $return = [
  299. 'filename' => 'QuantumultSub',
  300. 'suffix' => 'conf',
  301. 'class' => 'Quantumult',
  302. ];
  303. break;
  304. case 'quantumult_conf':
  305. $return = [
  306. 'filename' => 'QuantumultConf',
  307. 'suffix' => 'conf',
  308. 'class' => 'Quantumult',
  309. ];
  310. break;
  311. default:
  312. $return = [
  313. 'filename' => 'UndefinedNode',
  314. 'suffix' => 'txt',
  315. 'class' => 'Sub',
  316. ];
  317. break;
  318. }
  319. return $return;
  320. }
  321. /**
  322. * 响应内容
  323. *
  324. * @param string $content 订阅内容
  325. * @param string $filename 文件名
  326. */
  327. public static function getBody(User $user, object $response, string $content, string $filename): ResponseInterface
  328. {
  329. $response = $response
  330. ->withHeader(
  331. 'Content-type',
  332. ' application/octet-stream; charset=utf-8'
  333. )
  334. ->withHeader(
  335. 'Cache-Control',
  336. 'no-store, no-cache, must-revalidate'
  337. )
  338. ->withHeader(
  339. 'Content-Disposition',
  340. ' attachment; filename=' . $filename
  341. )
  342. ->withHeader(
  343. 'Subscription-Userinfo',
  344. (' upload=' . $user->u
  345. . '; download=' . $user->d
  346. . '; total=' . $user->transfer_enable
  347. . '; expire=' . strtotime($user->class_expire))
  348. );
  349. return $response->write($content);
  350. }
  351. /**
  352. * 订阅链接汇总
  353. *
  354. * @param User $user 用户
  355. * @param int $int 当前用户访问的订阅类型
  356. *
  357. * @return array
  358. */
  359. public static function getSubinfo(User $user, int $int = 0): array
  360. {
  361. if ($int === 0) {
  362. $int = '';
  363. }
  364. $userapiUrl = $_ENV['subUrl'] . self::GenerateSSRSubCode($user->id);
  365. $return_info = [
  366. 'link' => '',
  367. // sub
  368. 'ss' => '?sub=2',
  369. 'ssr' => '?sub=1',
  370. 'v2ray' => '?sub=3',
  371. 'trojan' => '?sub=4',
  372. // apps
  373. 'ssa' => '?list=ssa',
  374. 'anxray' => '?anxray=1',
  375. 'clash' => '?clash=1',
  376. 'clash_provider' => '?list=clash',
  377. 'surge' => '?surge=' . $int,
  378. 'surge_node' => '?list=surge',
  379. 'surge2' => '?surge=2',
  380. 'surge3' => '?surge=3',
  381. 'surge4' => '?surge=4',
  382. 'surfboard' => '?surfboard=1',
  383. 'quantumult' => '?quantumult=' . $int,
  384. 'quantumult_v2' => '?list=quantumult',
  385. 'quantumult_sub' => '?quantumult=2',
  386. 'quantumult_conf' => '?quantumult=3',
  387. 'quantumultx' => '?list=quantumultx',
  388. 'shadowrocket' => '?list=shadowrocket',
  389. 'kitsunebi' => '?list=kitsunebi',
  390. ];
  391. return array_map(
  392. function ($item) use ($userapiUrl) {
  393. return $userapiUrl . $item;
  394. },
  395. $return_info
  396. );
  397. }
  398. public static function getListItem($item, $list)
  399. {
  400. $return = null;
  401. switch ($list) {
  402. case 'ss':
  403. $return = AppURI::getItemUrl($item, 1);
  404. break;
  405. case 'ssr':
  406. $return = AppURI::getItemUrl($item, 0);
  407. break;
  408. case 'ssa':
  409. $return = AppURI::getSSJSON($item);
  410. break;
  411. case 'anxray':
  412. $return = AppURI::getAnXrayURI($item);
  413. break;
  414. case 'surge':
  415. $return = AppURI::getSurgeURI($item, 3);
  416. break;
  417. case 'clash':
  418. $return = AppURI::getClashURI($item);
  419. break;
  420. case 'v2rayn':
  421. $return = AppURI::getV2RayNURI($item);
  422. break;
  423. case 'trojan':
  424. $return = AppURI::getTrojanURI($item);
  425. break;
  426. case 'kitsunebi':
  427. $return = AppURI::getKitsunebiURI($item);
  428. break;
  429. case 'quantumult':
  430. $return = AppURI::getQuantumultURI($item, true);
  431. break;
  432. case 'quantumultx':
  433. $return = AppURI::getQuantumultXURI($item);
  434. break;
  435. case 'shadowrocket':
  436. $return = AppURI::getShadowrocketURI($item);
  437. break;
  438. }
  439. return $return;
  440. }
  441. public static function getLists($user, $list, $opts, $Rule)
  442. {
  443. $list = strtolower($list);
  444. if ($list === 'ssa') {
  445. $Rule['type'] = 'ss';
  446. }
  447. if ($list === 'quantumult') {
  448. $Rule['type'] = 'vmess';
  449. }
  450. if ($list === 'shadowrocket') {
  451. // Shadowrocket 自带 emoji
  452. $Rule['emoji'] = false;
  453. }
  454. $items = URL::getNew_AllItems($user, $Rule);
  455. $return = [];
  456. if ($Rule['extend'] === true) {
  457. switch ($list) {
  458. case 'ssa':
  459. case 'clash':
  460. $return = array_merge($return, self::getListExtend($user, $list));
  461. break;
  462. default:
  463. $return[] = implode(PHP_EOL, self::getListExtend($user, $list));
  464. break;
  465. }
  466. }
  467. foreach ($items as $item) {
  468. $out = self::getListItem($item, $list);
  469. if ($out !== null) {
  470. $return[] = $out;
  471. }
  472. }
  473. switch ($list) {
  474. case 'ssa':
  475. return json_encode($return, 320);
  476. break;
  477. case 'clash':
  478. return \Symfony\Component\Yaml\Yaml::dump(['proxies' => $return], 4, 2);
  479. case 'kitsunebi':
  480. case 'quantumult':
  481. case 'shadowrocket':
  482. return base64_encode(implode(PHP_EOL, $return));
  483. default:
  484. return implode(PHP_EOL, $return);
  485. }
  486. }
  487. public static function getListExtend($user, $list)
  488. {
  489. $return = [];
  490. $info_array = (count($_ENV['sub_message']) !== 0 ? (array) $_ENV['sub_message'] : []);
  491. if (strtotime($user->expire_in) > time()) {
  492. if ($user->transfer_enable === 0) {
  493. $unusedTraffic = '剩余流量:0';
  494. } else {
  495. $unusedTraffic = '剩余流量:' . $user->unusedTraffic();
  496. }
  497. $expire_in = '过期时间:';
  498. if ($user->class_expire !== '1989-06-04 00:05:00') {
  499. $userClassExpire = explode(' ', $user->class_expire);
  500. $expire_in .= $userClassExpire[0];
  501. } else {
  502. $expire_in .= '无限期';
  503. }
  504. } else {
  505. $unusedTraffic = '账户已过期,请续费后使用';
  506. $expire_in = '账户已过期,请续费后使用';
  507. }
  508. if (! in_array($list, ['quantumult', 'quantumultx', 'shadowrocket'])) {
  509. $info_array[] = $unusedTraffic;
  510. $info_array[] = $expire_in;
  511. }
  512. $baseUrl = explode('//', $_ENV['baseUrl'])[1];
  513. $baseUrl = explode('/', $baseUrl)[0];
  514. $Extend = [
  515. 'remark' => '',
  516. 'type' => '',
  517. 'add' => $baseUrl,
  518. 'address' => $baseUrl,
  519. 'port' => 10086,
  520. 'method' => 'chacha20-ietf',
  521. 'passwd' => $user->passwd,
  522. 'id' => $user->uuid,
  523. 'aid' => 0,
  524. 'net' => 'tcp',
  525. 'headerType' => 'none',
  526. 'host' => '',
  527. 'path' => '/',
  528. 'tls' => '',
  529. 'protocol' => 'origin',
  530. 'protocol_param' => '',
  531. 'obfs' => 'plain',
  532. 'obfs_param' => '',
  533. 'group' => $_ENV['appName'],
  534. ];
  535. if ($list === 'shadowrocket') {
  536. $return[] = 'STATUS=' . $unusedTraffic . '.♥.' . $expire_in . PHP_EOL . 'REMARKS=' . $_ENV['appName'];
  537. }
  538. foreach ($info_array as $remark) {
  539. $Extend['remark'] = $remark;
  540. if (in_array($list, ['kitsunebi', 'quantumult', 'v2rayn'])) {
  541. $Extend['type'] = 'vmess';
  542. $out = self::getListItem($Extend, $list);
  543. } elseif ($list === 'trojan') {
  544. $Extend['type'] = 'trojan';
  545. $out = self::getListItem($Extend, $list);
  546. } elseif ($list === 'ssr') {
  547. $Extend['type'] = 'ssr';
  548. $out = self::getListItem($Extend, $list);
  549. } else {
  550. $Extend['type'] = 'ss';
  551. $out = self::getListItem($Extend, $list);
  552. }
  553. if ($out !== null) {
  554. $return[] = $out;
  555. }
  556. }
  557. return $return;
  558. }
  559. /**
  560. * Surge 配置
  561. *
  562. * @param User $user 用户
  563. * @param int $surge 订阅类型
  564. * @param array $opts request
  565. * @param array $Rule 节点筛选规则
  566. */
  567. public static function getSurge(User $user, int $surge, array $opts, array $Rule): string
  568. {
  569. $subInfo = self::getSubinfo($user, $surge);
  570. $userapiUrl = $subInfo['surge'];
  571. if ($surge !== 4) {
  572. $Rule['type'] = 'ss';
  573. }
  574. $items = URL::getNew_AllItems($user, $Rule);
  575. $Nodes = [];
  576. $All_Proxy = '';
  577. foreach ($items as $item) {
  578. $out = AppURI::getSurgeURI($item, $surge);
  579. if ($out !== null) {
  580. $Nodes[] = $item;
  581. $All_Proxy .= $out . PHP_EOL;
  582. }
  583. }
  584. $variable = ($surge === 2 ? 'Surge2_Profiles' : 'Surge_Profiles');
  585. if (isset($opts['profiles']) && in_array($opts['profiles'], array_keys($_ENV[$variable]))) {
  586. $Profiles = $opts['profiles'];
  587. $userapiUrl .= '&profiles=' . $Profiles;
  588. } else {
  589. $Profiles = ($surge === 2 ? $_ENV['Surge2_DefaultProfiles'] : $_ENV['Surge_DefaultProfiles']);
  590. }
  591. return ConfGenerate::getSurgeConfs($user, $All_Proxy, $Nodes, $_ENV[$variable][$Profiles]);
  592. }
  593. /**
  594. * Quantumult 配置
  595. *
  596. * @param User $user 用户
  597. * @param int $quantumult 订阅类型
  598. * @param array $opts request
  599. * @param array $Rule 节点筛选规则
  600. */
  601. public static function getQuantumult(User $user, int $quantumult, array $opts, array $Rule): string
  602. {
  603. switch ($quantumult) {
  604. case 2:
  605. $subUrl = self::getSubinfo($user, 0);
  606. $str = [
  607. '[SERVER]',
  608. '',
  609. '[SOURCE]',
  610. $_ENV['appName'] . ', server ,' . $subUrl['ssr'] . ', false, true, false',
  611. $_ENV['appName'] . '_ss, server ,' . $subUrl['ss'] . ', false, true, false',
  612. $_ENV['appName'] . '_VMess, server ,' . $subUrl['quantumult_v2'] . ', false, true, false',
  613. 'Hackl0us Rules, filter, https://raw.githubusercontent.com/Hackl0us/Surge-Rule-Snippets/master/LAZY_RULES/Quantumult.conf, true',
  614. '',
  615. '[DNS]',
  616. 'system, 119.29.29.29, 223.6.6.6, 114.114.114.114',
  617. '',
  618. '[STATE]',
  619. 'STATE,AUTO',
  620. ];
  621. return implode(PHP_EOL, $str);
  622. break;
  623. case 3:
  624. $items = URL::getNew_AllItems($user, $Rule);
  625. break;
  626. default:
  627. return self::getLists($user, 'quantumult', $opts, $Rule);
  628. break;
  629. }
  630. $All_Proxy = '';
  631. $All_Proxy_name = '';
  632. $BackChina_name = '';
  633. foreach ($items as $item) {
  634. $out = AppURI::getQuantumultURI($item);
  635. if ($out !== null) {
  636. $All_Proxy .= $out . PHP_EOL;
  637. if (strpos($item['remark'], '回国') || strpos($item['remark'], 'China')) {
  638. $BackChina_name .= "\n" . $item['remark'];
  639. } else {
  640. $All_Proxy_name .= "\n" . $item['remark'];
  641. }
  642. }
  643. }
  644. $ProxyGroups = [
  645. 'proxy_group' => base64_encode("🍃 Proxy : static, 🏃 Auto\n🏃 Auto\n🚀 Direct\n" . $All_Proxy_name),
  646. 'domestic_group' => base64_encode("🍂 Domestic : static, 🚀 Direct\n🚀 Direct\n🍃 Proxy\n" . $BackChina_name),
  647. 'others_group' => base64_encode("☁️ Others : static, 🍃 Proxy\n🚀 Direct\n🍃 Proxy"),
  648. 'direct_group' => base64_encode("🚀 Direct : static, DIRECT\nDIRECT"),
  649. 'apple_group' => base64_encode("🍎 Only : static, 🚀 Direct\n🚀 Direct\n🍃 Proxy"),
  650. 'auto_group' => base64_encode("🏃 Auto : auto\n" . $All_Proxy_name),
  651. ];
  652. $render = ConfRender::getTemplateRender();
  653. $render->assign('All_Proxy', $All_Proxy)->assign('ProxyGroups', $ProxyGroups);
  654. return $render->fetch('quantumult/quantumult.tpl');
  655. }
  656. /**
  657. * QuantumultX 配置
  658. *
  659. * @param User $user 用户
  660. * @param int $quantumultx 订阅类型
  661. * @param array $opts request
  662. * @param array $Rule 节点筛选规则
  663. */
  664. public static function getQuantumultX(User $user, int $quantumultx, array $opts, array $Rule): string
  665. {
  666. return '';
  667. }
  668. /**
  669. * Surfboard 配置
  670. *
  671. * @param User $user 用户
  672. * @param int $surfboard 订阅类型
  673. * @param array $opts request
  674. * @param array $Rule 节点筛选规则
  675. */
  676. public static function getSurfboard(User $user, int $surfboard, array $opts, array $Rule): string
  677. {
  678. $subInfo = self::getSubinfo($user, 0);
  679. $userapiUrl = $subInfo['surfboard'];
  680. $Nodes = [];
  681. $All_Proxy = '';
  682. $items = URL::getNew_AllItems($user, $Rule);
  683. foreach ($items as $item) {
  684. $out = AppURI::getSurfboardURI($item);
  685. if ($out !== null) {
  686. $Nodes[] = $item;
  687. $All_Proxy .= $out . PHP_EOL;
  688. }
  689. }
  690. if (isset($opts['profiles']) && in_array($opts['profiles'], array_keys($_ENV['Surfboard_Profiles']))) {
  691. $Profiles = $opts['profiles'];
  692. $userapiUrl .= '&profiles=' . $Profiles;
  693. } else {
  694. $Profiles = $_ENV['Surfboard_DefaultProfiles']; // 默认策略组
  695. }
  696. return ConfGenerate::getSurgeConfs($user, $All_Proxy, $Nodes, $_ENV['Surfboard_Profiles'][$Profiles]);
  697. }
  698. /**
  699. * Clash 配置
  700. *
  701. * @param User $user 用户
  702. * @param int $clash 订阅类型
  703. * @param array $opts request
  704. * @param array $Rule 节点筛选规则
  705. */
  706. public static function getClash(User $user, int $clash, array $opts, array $Rule): string
  707. {
  708. $subInfo = self::getSubinfo($user, $clash);
  709. $userapiUrl = $subInfo['clash'];
  710. $items = URL::getNew_AllItems($user, $Rule);
  711. $Proxys = [];
  712. foreach ($items as $item) {
  713. $Proxy = AppURI::getClashURI($item);
  714. if ($Proxy !== null) {
  715. $Proxys[] = $Proxy;
  716. }
  717. }
  718. if (isset($opts['profiles']) && in_array($opts['profiles'], array_keys($_ENV['Clash_Profiles']))) {
  719. $Profiles = $opts['profiles'];
  720. $userapiUrl .= '&profiles=' . $Profiles;
  721. } else {
  722. $Profiles = $_ENV['Clash_DefaultProfiles']; // 默认策略组
  723. }
  724. return ConfGenerate::getClashConfs($user, $Proxys, $_ENV['Clash_Profiles'][$Profiles]);
  725. }
  726. public static function getAnXray($user, $anxray, $opts, $Rule)
  727. {
  728. $subInfo = self::getSubinfo($user, $anxray);
  729. $All_Proxy = '';
  730. $userapiUrl = $subInfo['anxray'];
  731. $items = URL::getNew_AllItems($user, $Rule);
  732. foreach ($items as $item) {
  733. $out = AppURI::getAnXrayURI($item);
  734. if ($out !== null) {
  735. $All_Proxy .= $out . PHP_EOL;
  736. }
  737. }
  738. return base64_encode($All_Proxy);
  739. }
  740. /**
  741. * 通用订阅,ssr & v2rayn
  742. *
  743. * @param User $user 用户
  744. * @param int $sub 订阅类型
  745. * @param array $opts request
  746. * @param array $Rule 节点筛选规则
  747. */
  748. public static function getSub(User $user, int $sub, array $opts, array $Rule): string
  749. {
  750. $return_url = '';
  751. switch ($sub) {
  752. case 2: // SS
  753. $Rule['type'] = 'ss';
  754. $getListExtend = $Rule['extend'] ? self::getListExtend($user, 'ss') : [];
  755. break;
  756. case 3: // V2
  757. $Rule['type'] = 'vmess';
  758. $getListExtend = $Rule['extend'] ? self::getListExtend($user, 'v2rayn') : [];
  759. break;
  760. case 4: // Trojan
  761. $Rule['type'] = 'trojan';
  762. $getListExtend = $Rule['extend'] ? self::getListExtend($user, 'trojan') : [];
  763. break;
  764. default: // SSR
  765. $Rule['type'] = 'ssr';
  766. $getListExtend = $Rule['extend'] ? self::getListExtend($user, 'ssr') : [];
  767. break;
  768. }
  769. if ($Rule['extend']) {
  770. $return_url .= implode(PHP_EOL, $getListExtend) . PHP_EOL;
  771. }
  772. $return_url .= URL::get_NewAllUrl($user, $Rule);
  773. return base64_encode($return_url);
  774. }
  775. /**
  776. * 记录订阅日志
  777. *
  778. * @param User $user 用户
  779. * @param string $type 订阅类型
  780. * @param string $ua UA
  781. */
  782. private static function Subscribe_log(User $user, string $type, string $ua): void
  783. {
  784. $log = new UserSubscribeLog();
  785. $log->user_name = $user->user_name;
  786. $log->user_id = $user->id;
  787. $log->email = $user->email;
  788. $log->subscribe_type = $type;
  789. $log->request_ip = $_SERVER['REMOTE_ADDR'];
  790. $log->request_time = date('Y-m-d H:i:s');
  791. $antiXss = new AntiXSS();
  792. $log->request_user_agent = $antiXss->xss_clean($ua);
  793. $log->save();
  794. }
  795. }