IssueService.php 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438
  1. <?php
  2. namespace App\Services;
  3. use App\Models\Cao;
  4. use App\Models\CaoHistory;
  5. use App\Models\PcIssue;
  6. use App\Models\Prediction;
  7. use App\Services\BaseService;
  8. use App\Models\Issue;
  9. use App\Models\Config;
  10. use Illuminate\Support\Facades\DB;
  11. use Illuminate\Support\Collection;
  12. use Illuminate\Support\Facades\Cache;
  13. use Illuminate\Support\Facades\Http;
  14. use Illuminate\Support\Facades\Log;
  15. use App\Services\GameplayRuleService;
  16. use App\Constants\GameplayRuleEnum;
  17. use App\Http\Controllers\admin\Lottery;
  18. use App\Services\KeyboardService;
  19. use App\Services\LotteryImageService;
  20. use Telegram\Bot\FileUpload\InputFile;
  21. use App\Jobs\SendTelegramMessageJob;
  22. use App\Jobs\SendTelegramGroupMessageJob;
  23. /**
  24. * 投注
  25. */
  26. class IssueService extends BaseService
  27. {
  28. const COUNTDOWN_TO_CLOSING_THE_MARKET = 60;//提前xx秒封盘
  29. const PLAYNOW_KENO_URL = 'https://www.playnow.com/services2/keno/nextdraw';
  30. const PLAYNOW_SOURCE_TIMEZONE = 'America/Vancouver';
  31. const BEIJING_TIMEZONE = 'Asia/Shanghai';
  32. const PLAYNOW_DRAW_INTERVAL_SECONDS = 210;
  33. public static function init($telegram, $data, $chatId, $firstName, $messageId): void
  34. {
  35. switch ($data) {
  36. default:
  37. //查看开奖历史图片
  38. $pattern = "/^showLotteryHistory@@\d+$/";
  39. if (preg_match($pattern, $data)) {
  40. $id = preg_replace('/^showLotteryHistory@@/', '', $data);
  41. IssueService::sendLotteryImage($chatId, $id);
  42. }
  43. break;
  44. }
  45. }
  46. /**
  47. * @description: 模型
  48. * @return {string}
  49. */
  50. public
  51. static function model(): string
  52. {
  53. return Issue::class;
  54. }
  55. /**
  56. * @description: 枚举
  57. * @return {*}
  58. */
  59. public
  60. static function enum(): string
  61. {
  62. return '';
  63. }
  64. /**
  65. * @description: 获取查询条件
  66. * @param {array} $search 查询内容
  67. * @return {array}
  68. */
  69. public
  70. static function getWhere(array $search = []): array
  71. {
  72. $where = [];
  73. if (isset($search['issue_no']) && !empty($search['issue_no'])) {
  74. $where[] = ['issue_no', '=', $search['issue_no']];
  75. }
  76. if (isset($search['id']) && !empty($search['id'])) {
  77. $where[] = ['id', '=', $search['id']];
  78. }
  79. if (isset($search['status']) && $search['status'] != '') {
  80. $where[] = ['status', '=', $search['status']];
  81. }
  82. if (isset($search['abnormal']) && !empty($search['abnormal'])) {
  83. $where[] = ['end_time', '<', date('Y-m-d H:i:s', time() - 1800)];
  84. $where[] = ['status', '!=', self::model()::STATUS_DRAW];
  85. }
  86. if (!empty($search['start_time'])) {
  87. $where[] = ['end_time', '>=', $search['start_time'].' 00:00:00'];
  88. }
  89. if (!empty($search['end_time'])) {
  90. $where[] = ['end_time', '<=', $search['end_time'].' 23:59:59'];
  91. }
  92. return $where;
  93. }
  94. /**
  95. * @description: 查询单条数据
  96. * @param array $search
  97. * @return \App\Models\Coin|null
  98. */
  99. public
  100. static function findOne(array $search): ?Issue
  101. {
  102. return self::model()::where(self::getWhere($search))->first();
  103. }
  104. /**
  105. * @description: 查询所有数据
  106. * @param array $search
  107. * @return \Illuminate\Database\Eloquent\Collection
  108. */
  109. public
  110. static function findAll(array $search = [])
  111. {
  112. return self::model()::where(self::getWhere($search))->get();
  113. }
  114. /**
  115. * @description: 分页查询
  116. * @param array $search
  117. * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
  118. */
  119. public
  120. static function paginate(array $search = [])
  121. {
  122. $limit = isset($search['limit']) ? $search['limit'] : 15;
  123. $paginator = self::model()::where(self::getWhere($search))->orderBy('issue_no', 'desc')->paginate($limit);
  124. return ['total' => $paginator->total(), 'data' => $paginator->items()];
  125. }
  126. /**
  127. * @description:
  128. * @param {*} $params
  129. * @return {*}
  130. */
  131. public
  132. static function submit($params = [])
  133. {
  134. $result = false;
  135. $msg['code'] = self::NOT;
  136. $msg['msg'] = '';
  137. // 2. 判断是否是更新
  138. if (!empty($params['id'])) {
  139. // 更新
  140. $info = self::findOne(['id' => $params['id']]);
  141. if (!$info) {
  142. $msg['msg'] = '期号不存在!';
  143. } else {
  144. $result = $info->update($params);
  145. $id = $params['id'];
  146. }
  147. } else {
  148. // 创建
  149. $result = $info = self::model()::create($params);
  150. $id = $result->id;
  151. }
  152. if ($result) {
  153. $msg['code'] = self::YES;
  154. $msg['msg'] = '设置成功';
  155. $msg['key'] = $id;
  156. } else {
  157. $msg['msg'] = empty($msg['msg']) ? '操作失败' : $msg['msg'];
  158. }
  159. return $msg;
  160. }
  161. /**
  162. * @description: 开始下注
  163. * @param {*} $id
  164. * @return {*}
  165. */
  166. public
  167. static function betting($id)
  168. {
  169. $info = self::findOne(['id' => $id]);
  170. if (!$info) {
  171. return ['code' => self::NOT, 'msg' => '期号不存在'];
  172. }
  173. if (!in_array($info->status, [self::model()::STATUS_DRAFT, self::model()::STATUS_BETTING])) {
  174. return ['code' => self::NOT, 'msg' => '期号状态不正确'];
  175. }
  176. $info->status = self::model()::STATUS_BETTING;
  177. $info->save();
  178. $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
  179. $wanFaGuiZeTime = Cache::get("玩法规则推送时间", 0);
  180. $now = time();
  181. $wanFaGuiZeTime = intval($wanFaGuiZeTime);
  182. if ($now - $wanFaGuiZeTime > (60 * 15)) {
  183. $replyInfo = KeyboardService::findOne(['button' => '玩法规则']);
  184. if ($replyInfo) {
  185. $text = $replyInfo->reply;
  186. $buttons = json_decode($replyInfo->buttons, true);
  187. $image = $replyInfo->image;
  188. if ($image) {
  189. $image = url($image);
  190. }
  191. if (empty($buttons)) {
  192. $buttons = self::getOperateButton();
  193. }
  194. Cache::put('玩法规则推送时间', time());
  195. if ($pc28Switch == 0) self::asyncBettingGroupNotice($text, $buttons, $image);
  196. }
  197. }
  198. $replyInfo = KeyboardService::findOne(['button' => '开始下注']);
  199. if ($replyInfo) {
  200. $text = $replyInfo->reply;
  201. $buttons = json_decode($replyInfo->buttons, true);
  202. $image = $replyInfo->image;
  203. if ($image) {
  204. $image = url($image);
  205. }
  206. if ($pc28Switch == 0) self::asyncBettingGroupNotice($text, $buttons, $image);
  207. }
  208. return ['code' => self::YES, 'msg' => '开始下注'];
  209. }
  210. /**
  211. * @description: 封盘
  212. * @param {*} $id
  213. * @return {*}
  214. */
  215. public
  216. static function closeBetting($id)
  217. {
  218. $info = self::findOne(['id' => $id]);
  219. if (!$info) {
  220. return ['code' => self::NOT, 'msg' => '期号不存在'];
  221. }
  222. if ($info->status != self::model()::STATUS_BETTING) {
  223. return ['code' => self::NOT, 'msg' => '期号状态不正确'];
  224. }
  225. $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
  226. $info->status = self::model()::STATUS_CLOSE;
  227. $info->save();
  228. if ($pc28Switch == 0) {
  229. $replyInfo = KeyboardService::findOne(['button' => '停止下注']);
  230. if ($replyInfo) {
  231. $text = $replyInfo->reply;
  232. $buttons = json_decode($replyInfo->buttons, true);
  233. $image = $replyInfo->image;
  234. if ($image) {
  235. $image = url($image);
  236. }
  237. //停止下注的信息不发了
  238. // self::asyncBettingGroupNotice($text, $buttons, $image);
  239. }
  240. // 投注情况通知 xxxx期投注统计
  241. BetService::statNotice($info->issue_no);
  242. $replyInfo = KeyboardService::findOne(['button' => '封盘开奖']);
  243. if ($replyInfo) {
  244. $text = $replyInfo->reply;
  245. $buttons = json_decode($replyInfo->buttons, true);
  246. $image = $replyInfo->image;
  247. if ($image) {
  248. $image = url($image);
  249. }
  250. // self::bettingGroupNotice($text, $buttons, $image);
  251. self::asyncBettingGroupNotice($text, $buttons, $image);
  252. }
  253. }
  254. return ['code' => self::YES, 'msg' => '封盘成功'];
  255. }
  256. /**
  257. * @description: 开奖失败
  258. * @param {*} $id
  259. * @return {*}
  260. */
  261. public
  262. static function lotteryDrawFail($id)
  263. {
  264. $result = false;
  265. $msg['code'] = self::NOT;
  266. $msg['msg'] = '';
  267. DB::beginTransaction();
  268. try {
  269. // 更新
  270. $info = self::findOne(['id' => $id]);
  271. if (!$info) {
  272. $msg['msg'] = '期号不存在!';
  273. } else {
  274. $params['status'] = self::model()::STATUS_FAIL;
  275. $result = $info->update($params);
  276. BetService::betFail($info->issue_no);
  277. }
  278. DB::commit();
  279. return ['code' => self::YES, 'msg' => '投注已退回'];
  280. } catch (\Exception $e) {
  281. DB::rollBack();
  282. return ['code' => self::NOT, 'msg' => '投注退回失败'];
  283. }
  284. if ($result) {
  285. $msg['code'] = self::YES;
  286. $msg['msg'] = '设置成功';
  287. } else {
  288. $msg['msg'] = empty($msg['msg']) ? '操作失败' : $msg['msg'];
  289. }
  290. return $msg;
  291. }
  292. /**
  293. * @description: 开奖
  294. * @param {*} $id
  295. * @param {*} $winning_numbers 开奖号码
  296. * @param {*} $combo 开奖组合
  297. * @param {*} $recordImage 开奖图片
  298. * @return {*}
  299. */
  300. public
  301. static function lotteryDraw($id, $winning_numbers, $combo, $recordImage)
  302. {
  303. $info = self::findOne(['id' => $id]);
  304. if (!$info) {
  305. return ['code' => self::NOT, 'msg' => '期号不存在'];
  306. }
  307. if ($info->status == self::model()::STATUS_DRAW) {
  308. return ['code' => self::NOT, 'msg' => '期号状态不正确'];
  309. }
  310. $winArr = array_map('intval', explode(',', $winning_numbers));
  311. // 计算中奖
  312. $awards = self::award(explode(',', $winning_numbers));
  313. DB::beginTransaction();
  314. try {
  315. $info->status = self::model()::STATUS_DRAW;
  316. $info->winning_numbers = $winning_numbers;
  317. $info->combo = $combo;
  318. $info->saveQuietly();
  319. $size = in_array("大", $awards);
  320. $size = $size ? "大" : "小";
  321. $oddOrEven = in_array("双", $awards);
  322. $oddOrEven = $oddOrEven ? "双" : "单";
  323. Prediction::result($info->issue_no, $size, $oddOrEven, $info->winning_numbers);
  324. Cao::updateData($awards);
  325. CaoHistory::updateData($awards);
  326. $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
  327. $replyInfo = KeyboardService::findOne(['button' => '本期开奖']);
  328. if ($replyInfo) {
  329. $text = $replyInfo->reply;
  330. $text .= "\n";
  331. $text .= $info->issue_no . ": " . implode('+', explode(',', $winning_numbers)) . "=" . array_sum($winArr) . " " . $combo;
  332. $buttons = json_decode($replyInfo->buttons, true);
  333. $image = $replyInfo->image;
  334. if ($image) {
  335. $image = url($image);
  336. }
  337. if (empty($buttons)) {
  338. $serviceAccount = Config::where('field', 'service_account')->first()->val;
  339. $buttons[] = [['text' => lang('✅唯一财务'), 'callback_data' => "", 'url' => "https://t.me/{$serviceAccount}"]];
  340. }
  341. // self::bettingGroupNotice($text, $buttons, $image, true);
  342. if ($pc28Switch == 0) SendTelegramGroupMessageJob::dispatch($text, $buttons, $image, true, "\n", [
  343. 'message_type' => 'draw_result',
  344. 'issue_no' => $info->issue_no,
  345. ])->afterCommit();
  346. }
  347. $recordImage = self::lotteryImage($info->issue_no);
  348. if ($recordImage) {
  349. // self::bettingGroupNotice('', [], url($recordImage));
  350. if ($pc28Switch == 0) SendTelegramGroupMessageJob::dispatch('', [], url($recordImage), false, "\n", [
  351. 'message_type' => 'draw_history_image',
  352. 'issue_no' => $info->issue_no,
  353. ])->afterCommit();
  354. }
  355. $info->image = $recordImage;
  356. $info->save();
  357. BetService::betSettled($info->issue_no, $awards);
  358. DB::commit();
  359. } catch (\Exception $e) {
  360. DB::rollBack();
  361. $message = "开奖失败:\n{$info->issue_no}\n";
  362. $message .= $e->getFile() . ':' . $e->getLine();
  363. $message .= "\n";
  364. $message .= $e->getMessage() . $winning_numbers;
  365. Log::error($message);
  366. return ['code' => self::NOT, 'msg' => '开奖失败', 'error' => $e->getMessage()];
  367. }
  368. return ['code' => self::YES, 'msg' => '开奖成功'];
  369. }
  370. // 虚拟开奖
  371. public
  372. static function fakeLotteryDraw($issue_no, $awards)
  373. {
  374. $fake_bet_list = Cache::get('fake_bet_' . $issue_no, []);
  375. $text = "";
  376. foreach ($fake_bet_list as $k => $v) {
  377. $lastStr = self::getLastChar($v['first_name'], 1);
  378. if (in_array($v['keywords'], $awards)) {
  379. $amount = (float)$v['amount'];
  380. $odds = (float)$v['odds'];
  381. $profit = $amount * $odds;
  382. if ($profit > 880000) {
  383. $profit = 880000; // 单注最高奖金880000
  384. }
  385. $item['profit'] = $profit;
  386. $yl = $profit - $amount;
  387. if ($k + 1 <= 30) {
  388. $text .= "私聊下注 【******" . $lastStr . "】 {$yl}\n";
  389. }
  390. } else {
  391. if ($k + 1 <= 30) {
  392. $text .= "私聊下注 【******" . $lastStr . "】 -{$v['amount']}\n";
  393. }
  394. }
  395. }
  396. return $text;
  397. }
  398. /**
  399. * @description: 获取中奖的奖项
  400. * @param {*} $winning_numbers
  401. * @return {*}
  402. */
  403. public
  404. static function award($winning_numbers)
  405. {
  406. $result = [];
  407. // 组合
  408. $sum = array_sum($winning_numbers);
  409. $section = self::getSection($sum); // 总和段位
  410. $result[] = $section;
  411. $sumOddEven = self::calculateOddEven($sum); // 总和单双
  412. $result[] = $sumOddEven;
  413. $sumSize = self::calculateSumSize($sum); // 总和大小
  414. $result[] = $sumSize;
  415. $sumExtremeSize = self::calculateSumExtremeSize($sum); // 总和极值
  416. if ($sumExtremeSize) {
  417. $result[] = $sumExtremeSize;
  418. }
  419. $sumCao = $sum . '操'; // 总和数字
  420. $result[] = $sumCao;
  421. $sumCombo = $sumSize . $sumOddEven; // 总和大小单双组合
  422. $result[] = $sumCombo;
  423. $sumBaoZi = self::isBaoZi($winning_numbers[0], $winning_numbers[1], $winning_numbers[2]); // 豹子
  424. if ($sumBaoZi) {
  425. $result[] = $sumBaoZi;
  426. }
  427. $sumPair = self::isPair($winning_numbers[0], $winning_numbers[1], $winning_numbers[2]); // 对子
  428. if ($sumPair) {
  429. $result[] = $sumPair;
  430. }
  431. $sumStraight = self::isStraight($winning_numbers[0], $winning_numbers[1], $winning_numbers[2]); // 顺子
  432. if ($sumStraight) {
  433. $result[] = $sumStraight;
  434. }
  435. $tail = self::getLastDigit($sum); // 总和尾数
  436. if (!in_array($tail, [0, 9])) {
  437. $result[] = $tail . '尾'; // 尾数
  438. $tailOddEven = self::calculateOddEven($tail); // 尾数单双
  439. $result[] = '尾' . $tailOddEven;
  440. $tailSize = self::calculateOneSize($tail); // 尾数大小
  441. $result[] = '尾' . $tailSize;
  442. $tailCombo = '尾' . $tailSize . $tailOddEven; // 尾数大小单双组合
  443. $result[] = $tailCombo;
  444. } else {
  445. $result[] = $tail . '尾'; // 尾数
  446. }
  447. $numA = $winning_numbers[0]; // A球
  448. $result[] = $numA . 'A';
  449. $numAOddEven = self::calculateOddEven($numA); // A球单双
  450. $result[] = 'A' . $numAOddEven;
  451. $numASize = self::calculateOneSize($numA); // A球大小
  452. $result[] = 'A' . $numASize;
  453. $result[] = 'A' . $numASize . $numAOddEven; // A球大小单双组合
  454. $numB = $winning_numbers[1]; // B球
  455. $result[] = $numB . 'B';
  456. $numBOddEven = self::calculateOddEven($numB); // B球
  457. $result[] = 'B' . $numBOddEven;
  458. $numBSize = self::calculateOneSize($numB); // B球大小
  459. $result[] = 'B' . $numBSize;
  460. $result[] = 'B' . $numBSize . $numBOddEven; // B球大小单双组合
  461. $numC = $winning_numbers[2];
  462. $result[] = $numC . 'C';
  463. $numCOddEven = self::calculateOddEven($numC); // C球单双
  464. $result[] = 'C' . $numCOddEven;
  465. $numCSize = self::calculateOneSize($numC); // C球大小
  466. $result[] = 'C' . $numCSize;
  467. $result[] = 'C' . $numCSize . $numCOddEven; // C球大小单双组合
  468. return $result;
  469. }
  470. /**
  471. * @description: 算单双
  472. * @param {*} $number
  473. * @return {*}
  474. */
  475. public
  476. static function calculateOddEven($number)
  477. {
  478. if ($number & 1) {
  479. return GameplayRuleEnum::SINGLE;
  480. } else {
  481. return GameplayRuleEnum::DOUBLE;
  482. }
  483. }
  484. /**
  485. * @description: 总和大小
  486. * @param {*} $number
  487. * @return {*}
  488. */
  489. public
  490. static function calculateSumSize($number)
  491. {
  492. if ($number >= GameplayRuleEnum::SUM_BIG) {
  493. return GameplayRuleEnum::BIG;
  494. }
  495. if ($number <= GameplayRuleEnum::SUM_SMALL) {
  496. return GameplayRuleEnum::SMALL;
  497. }
  498. }
  499. /**
  500. * @description: 总和极值
  501. * @param {*} $number
  502. * @return {*}
  503. */
  504. public
  505. static function calculateSumExtremeSize($number)
  506. {
  507. $result = '';
  508. if ($number >= GameplayRuleEnum::SUM_EXTREME_BIG) {
  509. $result = GameplayRuleEnum::EXTREME_BIG;
  510. }
  511. if ($number <= GameplayRuleEnum::SUM_EXTREME_SMALL) {
  512. $result = GameplayRuleEnum::EXTREME_SMALL;
  513. }
  514. return $result;
  515. }
  516. /**
  517. * @description: 豹子
  518. * @param {int} $a
  519. * @param {int} $b
  520. * @param {int} $c
  521. * @return {*}
  522. */
  523. public
  524. static function isBaoZi(int $a, int $b, int $c)
  525. {
  526. $result = '';
  527. if ($a === $b && $b === $c) {
  528. $result = GameplayRuleEnum::BAO_ZI;
  529. }
  530. return $result;
  531. }
  532. /**
  533. * @description: 对子
  534. * @param {int} $a
  535. * @param {int} $b
  536. * @param {int} $c
  537. * @return {*}
  538. */
  539. public
  540. static function isPair($a, $b, $c)
  541. {
  542. $result = '';
  543. // 确保输入都是个位数
  544. if (!is_numeric($a) || !is_numeric($b) || !is_numeric($c) ||
  545. $a < 0 || $a > 9 || $b < 0 || $b > 9 || $c < 0 || $c > 9) {
  546. return ''; // 或者抛出异常
  547. }
  548. if (($a == $b && $a != $c) ||
  549. ($a == $c && $a != $b) ||
  550. ($b == $c && $b != $a)) {
  551. $result = GameplayRuleEnum::PAIRS;
  552. }
  553. // 判断是否为对子情况
  554. return $result;
  555. }
  556. /**
  557. * @description: 顺子
  558. * @param {int} $a
  559. * @param {int} $b
  560. * @param {int} $c
  561. * @return {*}
  562. */
  563. public
  564. static function isStraight($a, $b, $c)
  565. {
  566. $result = '';
  567. // 确保输入都是个位数(0-9)
  568. if (!is_numeric($a) || !is_numeric($b) || !is_numeric($c) ||
  569. $a < 0 || $a > 9 || $b < 0 || $b > 9 || $c < 0 || $c > 9) {
  570. return '';
  571. }
  572. // 去重(顺子必须三个不同数字)
  573. if ($a == $b || $a == $c || $b == $c) {
  574. return '';
  575. }
  576. // 检查是否是完全升序或完全降序的连续数字
  577. $numbers = [$a, $b, $c];
  578. sort($numbers); // 排序后检查是否是 x, x+1, x+2
  579. list($x, $y, $z) = $numbers;
  580. // 情况1:升序连续(1,2,3)
  581. $isAscending = ($x + 1 == $y) && ($y + 1 == $z);
  582. // 情况2:降序连续(3,2,1)
  583. $isDescending = ($z + 1 == $y) && ($y + 1 == $x);
  584. if ($isAscending || $isDescending) {
  585. $result = GameplayRuleEnum::STRAIGHT;
  586. }
  587. return $result;
  588. }
  589. /**
  590. * 获取数字的尾数
  591. * @param int $number 输入数字
  592. * @return int 尾数
  593. */
  594. public
  595. static function getLastDigit($number)
  596. {
  597. // 确保输入是整数
  598. $number = (int)$number;
  599. // 取绝对值,处理负数情况
  600. $number = abs($number);
  601. // 取模10得到尾数
  602. return $number % 10;
  603. }
  604. /**
  605. * @description: 尾大小
  606. * @param {*} $number
  607. * @return {*}
  608. */
  609. public
  610. static function calculateOneSize($number)
  611. {
  612. if ($number >= GameplayRuleEnum::ONE_BIG) {
  613. return GameplayRuleEnum::BIG;
  614. }
  615. if ($number <= GameplayRuleEnum::ONE_SMALL) {
  616. return GameplayRuleEnum::SMALL;
  617. }
  618. }
  619. /**
  620. * @description: 获取段位
  621. * @param {*} $number
  622. * @return {*}
  623. */
  624. public
  625. static function getSection($number)
  626. {
  627. $result = '';
  628. if ($number >= GameplayRuleEnum::SECTION_1[0] && $number <= GameplayRuleEnum::SECTION_1[1]) {
  629. $result = GameplayRuleEnum::ONE;
  630. } elseif ($number >= GameplayRuleEnum::SECTION_2[0] && $number <= GameplayRuleEnum::SECTION_2[1]) {
  631. $result = GameplayRuleEnum::TWO;
  632. } elseif ($number >= GameplayRuleEnum::SECTION_3[0] && $number <= GameplayRuleEnum::SECTION_3[1]) {
  633. $result = GameplayRuleEnum::THREE;
  634. } elseif ($number >= GameplayRuleEnum::SECTION_4[0] && $number <= GameplayRuleEnum::SECTION_4[1]) {
  635. $result = GameplayRuleEnum::FOUR;
  636. }
  637. return $result; // 不在任何段中
  638. }
  639. /**
  640. * @description: 近期开奖记录
  641. * @return {*}
  642. */
  643. public
  644. static function currentLotteryResults($memberId)
  645. {
  646. // $result = self::model()::where('status', self::model()::STATUS_DRAW)->orderBy('id','desc')->take(16)->get();
  647. // $text = "📅 近期开奖记录\n";
  648. // $text .= "====================\n";
  649. // if($result){
  650. // foreach($result as $k => $v){
  651. // $winArr = explode(',',$v->winning_numbers);
  652. // // 组合
  653. // $sum = array_sum($winArr);
  654. // $combo = [];
  655. // $sumOddEven = self::calculateOddEven($sum); // 总和单双
  656. // $sumSize = self::calculateSumSize($sum); // 总和大小
  657. // $sumExtremeSize = self::calculateSumExtremeSize($sum); // 总和极值
  658. // if(empty($sumExtremeSize)){
  659. // $sumExtremeSize = "-";
  660. // }
  661. // $tail = self::getLastDigit($sum); // 总和尾数
  662. // if($tail == 0){
  663. // $tail = '-'; // 尾数
  664. // }else{
  665. // $tail = '尾'.$tail; // 尾数
  666. // }
  667. // $text .= "回合:{$v->issue_no}期 \n";
  668. // $text .= "结果:".implode('+',explode(',',$v->winning_numbers))."=".array_sum(explode(',',$v->winning_numbers))." \n";
  669. // $text .= "组合:{$sumSize} {$sumOddEven} \n";
  670. // $text .= "极值:{$sumExtremeSize} \n";
  671. // $text .= "尾数:{$tail} \n";
  672. // $text .= "---------------------------\n";
  673. // }
  674. // self::telegram()->sendMessage([
  675. // 'chat_id' => $memberId,
  676. // 'text' => $text,
  677. // ]);
  678. // }else{
  679. // self::telegram()->sendMessage([
  680. // 'chat_id' => $memberId,
  681. // 'text' => "暂无开奖记录",
  682. // ]);
  683. // }
  684. $result = self::model()::where('status', self::model()::STATUS_DRAW)->orderBy('id', 'desc')->first();
  685. if ($result) {
  686. if ($result->image) {
  687. // self::telegram()->sendPhoto([
  688. // 'chat_id' => $memberId,
  689. // 'photo' => InputFile::create(url($result->image)),
  690. // ]);
  691. return [
  692. 'chat_id' => $memberId,
  693. 'photo' => InputFile::create(url($result->image)),
  694. ];
  695. } else {
  696. // if($result->combo){
  697. // self::telegram()->sendMessage([
  698. // 'chat_id' => $memberId,
  699. // 'text' => "",
  700. // ]);
  701. // }else{
  702. // self::telegram()->sendMessage([
  703. // 'chat_id' => $memberId,
  704. // 'text' => lang("暂无开奖记录"),
  705. // ]);
  706. // }
  707. return
  708. [
  709. 'chat_id' => $memberId,
  710. 'text' => lang("暂无开奖记录"),
  711. ];
  712. }
  713. }
  714. }
  715. public
  716. static function getCombo($winArr)
  717. {
  718. // 组合
  719. $sum = array_sum($winArr);
  720. $combo = [];
  721. $sumSize = self::calculateSumSize($sum); // 总和大小
  722. $combo[] = $sumSize;
  723. $sumOddEven = self::calculateOddEven($sum); // 总和单双
  724. $combo[] = $sumOddEven;
  725. $sumExtremeSize = self::calculateSumExtremeSize($sum); // 总和极值
  726. if ($sumExtremeSize) {
  727. $combo[] = $sumExtremeSize;
  728. }
  729. $sumBaoZi = self::isBaoZi($winArr[0], $winArr[1], $winArr[2]); // 豹子
  730. if ($sumBaoZi) {
  731. $combo[] = $sumBaoZi;
  732. }
  733. $sumPair = self::isPair($winArr[0], $winArr[1], $winArr[2]); // 对子
  734. if ($sumPair) {
  735. $combo[] = $sumPair;
  736. }
  737. $sumStraight = self::isStraight($winArr[0], $winArr[1], $winArr[2]); // 顺子
  738. if ($sumStraight) {
  739. $combo[] = $sumStraight;
  740. }
  741. $tail = self::getLastDigit($sum); // 总和尾数
  742. if ($tail == 0 || $tail == 9) {
  743. } else {
  744. $combo[] = '尾' . $tail; // 尾数
  745. }
  746. return implode(' ', $combo);
  747. }
  748. private static function getPlayNowConfig($key, $default)
  749. {
  750. $value = config('services.playnow.' . $key, $default);
  751. return $value === null || $value === '' ? $default : $value;
  752. }
  753. private static function getPlayNowProxyUrl(): string
  754. {
  755. $scheme = self::getPlayNowConfig('proxy.scheme', 'http');
  756. $host = self::getPlayNowConfig('proxy.host', '155.138.141.119');
  757. $port = self::getPlayNowConfig('proxy.port', '3128');
  758. $username = (string)self::getPlayNowConfig('proxy.username', 'proxyuser');
  759. $password = (string)self::getPlayNowConfig('proxy.password', '');
  760. if ($username === '' && $password === '') {
  761. return "{$scheme}://{$host}:{$port}";
  762. }
  763. if ($password === '') {
  764. return "{$scheme}://" . rawurlencode($username) . "@{$host}:{$port}";
  765. }
  766. return "{$scheme}://" . rawurlencode($username) . ':' . rawurlencode($password) . "@{$host}:{$port}";
  767. }
  768. private static function getPlayNowProxyLogContext(): array
  769. {
  770. $host = self::getPlayNowConfig('proxy.host', '155.138.141.119');
  771. $password = self::getPlayNowConfig('proxy.password', '');
  772. return [
  773. 'proxy' => $host === '' ? 'missing' : 'configured',
  774. 'proxy_auth' => $password === '' ? 'missing' : 'configured',
  775. ];
  776. }
  777. private static function fetchPlayNowKenoResult()
  778. {
  779. $url = self::getPlayNowConfig('keno_url', self::PLAYNOW_KENO_URL);
  780. $response = Http::timeout(25)
  781. ->connectTimeout(10)
  782. ->withHeaders([
  783. 'Accept' => 'application/json, text/plain, */*',
  784. 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36',
  785. ])
  786. ->withOptions([
  787. 'proxy' => self::getPlayNowProxyUrl(),
  788. 'curl' => [
  789. CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  790. ],
  791. ])
  792. ->get($url);
  793. Log::channel('issue')->info('PlayNow接口响应', [
  794. 'status' => $response->status(),
  795. 'url' => $url,
  796. ] + self::getPlayNowProxyLogContext());
  797. if (!$response->successful()) {
  798. Log::channel('issue')->info('PlayNow接口请求失败', [
  799. 'status' => $response->status(),
  800. 'body' => substr($response->body(), 0, 500),
  801. ]);
  802. return null;
  803. }
  804. return $response->json();
  805. }
  806. private static function convertPlayNowDrawDateToBeijing($drawDate)
  807. {
  808. if (empty($drawDate)) {
  809. return null;
  810. }
  811. $sourceTimezone = new \DateTimeZone(self::getPlayNowConfig('timezone', self::PLAYNOW_SOURCE_TIMEZONE));
  812. $beijingTimezone = new \DateTimeZone(self::BEIJING_TIMEZONE);
  813. return (new \DateTimeImmutable($drawDate, $sourceTimezone))->setTimezone($beijingTimezone);
  814. }
  815. private static function getPlayNowDrawIntervalSeconds(array $result): int
  816. {
  817. $timeSinceDraw = isset($result['timeSinceDraw']) ? (int)$result['timeSinceDraw'] : 0;
  818. $nextDrawSeconds = isset($result['nextKenoDrawTime']) ? (int)$result['nextKenoDrawTime'] : 0;
  819. if ($timeSinceDraw > 0 && $nextDrawSeconds > 0) {
  820. return (int)round($timeSinceDraw / 1000) + $nextDrawSeconds;
  821. }
  822. return $nextDrawSeconds > 0 ? $nextDrawSeconds : self::PLAYNOW_DRAW_INTERVAL_SECONDS;
  823. }
  824. private static function calculatePlayNowPc28(array $numbers): array
  825. {
  826. $sortedNumbers = array_map('intval', $numbers);
  827. sort($sortedNumbers, SORT_NUMERIC);
  828. $sumByIndexes = function (array $indexes) use ($sortedNumbers) {
  829. $total = 0;
  830. foreach ($indexes as $index) {
  831. $total += $sortedNumbers[$index] ?? 0;
  832. }
  833. return $total % 10;
  834. };
  835. $firstNumber = $sumByIndexes([1, 4, 7, 10, 13, 16]);
  836. $secondNumber = $sumByIndexes([2, 5, 8, 11, 14, 17]);
  837. $thirdNumber = $sumByIndexes([3, 6, 9, 12, 15, 18]);
  838. return [
  839. 'numbers' => [$firstNumber, $secondNumber, $thirdNumber],
  840. 'total' => $firstNumber + $secondNumber + $thirdNumber,
  841. 'sorted_numbers' => $sortedNumbers,
  842. ];
  843. }
  844. private static function normalizePlayNowNumbers(array $numbers)
  845. {
  846. if (count($numbers) !== 20) {
  847. return null;
  848. }
  849. $normalized = [];
  850. foreach ($numbers as $number) {
  851. $value = filter_var($number, FILTER_VALIDATE_INT, [
  852. 'options' => [
  853. 'min_range' => 1,
  854. 'max_range' => 80,
  855. ],
  856. ]);
  857. if ($value === false) {
  858. return null;
  859. }
  860. $normalized[] = (int)$value;
  861. }
  862. if (count(array_unique($normalized)) !== 20) {
  863. return null;
  864. }
  865. return $normalized;
  866. }
  867. private static function isSamePlayNowIssueNo($issueNo, int $drawNo): bool
  868. {
  869. $issueNo = trim((string)$issueNo);
  870. return ctype_digit($issueNo) && (int)$issueNo === $drawNo;
  871. }
  872. private static function normalizePlayNowDrawNo($drawNo)
  873. {
  874. $value = filter_var($drawNo, FILTER_VALIDATE_INT, [
  875. 'options' => [
  876. 'min_range' => 1,
  877. ],
  878. ]);
  879. return $value === false ? null : (int)$value;
  880. }
  881. private static function parsePlayNowDrawInfo(array $result)
  882. {
  883. if (!isset($result['draw']) || empty($result['num']) || !is_array($result['num'])) {
  884. return null;
  885. }
  886. $drawNo = self::normalizePlayNowDrawNo($result['draw']);
  887. if (!$drawNo) {
  888. return null;
  889. }
  890. $sourceNumbers = self::normalizePlayNowNumbers($result['num']);
  891. if (!$sourceNumbers) {
  892. return null;
  893. }
  894. try {
  895. $drawDateBeijing = self::convertPlayNowDrawDateToBeijing($result['drawDate'] ?? null);
  896. } catch (\Throwable $exception) {
  897. Log::channel('issue')->info('PlayNow开奖时间解析失败', [
  898. 'draw_date_raw' => $result['drawDate'] ?? '',
  899. 'error' => $exception->getMessage(),
  900. ]);
  901. return null;
  902. }
  903. $nowBeijing = new \DateTimeImmutable('now', new \DateTimeZone(self::BEIJING_TIMEZONE));
  904. $drawIntervalSeconds = self::getPlayNowDrawIntervalSeconds($result);
  905. $startDateTime = $drawDateBeijing ?: $nowBeijing;
  906. $endDateTime = $startDateTime->modify('+' . $drawIntervalSeconds . ' seconds');
  907. $pc28 = self::calculatePlayNowPc28($sourceNumbers);
  908. return [
  909. 'draw_no' => $drawNo,
  910. 'draw_date_raw' => $result['drawDate'] ?? '',
  911. 'draw_date_beijing' => $drawDateBeijing ? $drawDateBeijing->format('Y-m-d H:i:s') : '',
  912. 'next_draw_seconds' => $result['nextKenoDrawTime'] ?? '',
  913. 'draw_interval_seconds' => $drawIntervalSeconds,
  914. 'source_numbers' => $sourceNumbers,
  915. 'sorted_numbers' => $pc28['sorted_numbers'],
  916. 'pc28' => $pc28,
  917. 'winning_numbers' => implode(',', $pc28['numbers']),
  918. 'combo' => self::getCombo($pc28['numbers']),
  919. 'start_time' => $startDateTime->format('Y-m-d H:i:s'),
  920. 'end_time' => $endDateTime->format('Y-m-d H:i:s'),
  921. ];
  922. }
  923. public static function getPlayNowWinningInfo($issueNo): array
  924. {
  925. Log::channel('issue')->info('后台手动开奖获取PlayNow数据', [
  926. 'issue_no' => $issueNo,
  927. ] + self::getPlayNowProxyLogContext());
  928. try {
  929. $result = self::fetchPlayNowKenoResult();
  930. } catch (\Throwable $exception) {
  931. Log::channel('issue')->info('后台手动开奖获取PlayNow数据异常', [
  932. 'issue_no' => $issueNo,
  933. 'error' => $exception->getMessage(),
  934. 'file' => $exception->getFile(),
  935. 'line' => $exception->getLine(),
  936. ]);
  937. return ['code' => self::NOT, 'msg' => '获取开奖信息失败'];
  938. }
  939. $drawInfo = self::parsePlayNowDrawInfo($result ?: []);
  940. if (!$drawInfo) {
  941. Log::channel('issue')->info('后台手动开奖PlayNow返回数据格式异常', [
  942. 'issue_no' => $issueNo,
  943. 'result' => $result,
  944. ]);
  945. return ['code' => self::NOT, 'msg' => '获取开奖信息失败'];
  946. }
  947. Log::channel('issue')->info('后台手动开奖PlayNow计算结果', [
  948. 'issue_no' => $issueNo,
  949. 'draw' => $drawInfo['draw_no'],
  950. 'draw_date_raw' => $drawInfo['draw_date_raw'],
  951. 'draw_date_beijing' => $drawInfo['draw_date_beijing'],
  952. 'source_numbers' => implode(',', $drawInfo['source_numbers']),
  953. 'sorted_numbers' => implode(',', $drawInfo['sorted_numbers']),
  954. 'pc28_numbers' => $drawInfo['winning_numbers'],
  955. 'pc28_total' => $drawInfo['pc28']['total'],
  956. 'combo' => $drawInfo['combo'],
  957. ]);
  958. if (!self::isSamePlayNowIssueNo($issueNo, $drawInfo['draw_no'])) {
  959. Log::channel('issue')->info('后台手动开奖期号不匹配', [
  960. 'issue_no' => $issueNo,
  961. 'playnow_draw' => $drawInfo['draw_no'],
  962. ]);
  963. return ['code' => self::NOT, 'msg' => '未查询到开奖信息,请手动开奖'];
  964. }
  965. return ['code' => self::YES, 'msg' => '获取成功'] + $drawInfo;
  966. }
  967. // 获取最新的开奖数据
  968. public static function getLatestIssue()
  969. {
  970. Log::channel('issue')->info('开始获取最新期号', [
  971. 'source' => 'playnow',
  972. ] + self::getPlayNowProxyLogContext());
  973. try {
  974. $result = self::fetchPlayNowKenoResult();
  975. } catch (\Throwable $exception) {
  976. Log::channel('issue')->info('获取PlayNow最新期号异常', [
  977. 'error' => $exception->getMessage(),
  978. 'file' => $exception->getFile(),
  979. 'line' => $exception->getLine(),
  980. ]);
  981. return ['code' => self::NOT, 'msg' => '获取最新期号失败'];
  982. }
  983. $drawInfo = self::parsePlayNowDrawInfo($result ?: []);
  984. if (!$drawInfo) {
  985. Log::channel('issue')->info('PlayNow返回数据格式异常', [
  986. 'result' => $result,
  987. ]);
  988. return ['code' => self::NOT, 'msg' => '获取最新期号失败'];
  989. }
  990. $drawNo = $drawInfo['draw_no'];
  991. $startTime = $drawInfo['start_time'];
  992. $endTime = $drawInfo['end_time'];
  993. $pc28 = $drawInfo['pc28'];
  994. Log::channel('issue')->info('PlayNow最新开奖数据', [
  995. 'draw' => $drawNo,
  996. 'draw_date_raw' => $drawInfo['draw_date_raw'],
  997. 'draw_date_beijing' => $drawInfo['draw_date_beijing'],
  998. 'next_draw_seconds' => $drawInfo['next_draw_seconds'],
  999. 'draw_interval_seconds' => $drawInfo['draw_interval_seconds'],
  1000. 'source_numbers' => implode(',', $drawInfo['source_numbers']),
  1001. 'sorted_numbers' => implode(',', $drawInfo['sorted_numbers']),
  1002. 'pc28_numbers' => $drawInfo['winning_numbers'],
  1003. 'pc28_total' => $pc28['total'],
  1004. 'next_issue_start_time' => $startTime,
  1005. 'next_issue_end_time' => $endTime,
  1006. ]);
  1007. $new = true;
  1008. $oldList = self::findAll(['status' => self::model()::STATUS_CLOSE]); // 获取所有封盘的期号
  1009. foreach ($oldList as $k => $v) {
  1010. if (self::isSamePlayNowIssueNo($v->issue_no, $drawNo)) {
  1011. $winning_numbers = $drawInfo['winning_numbers'];
  1012. $winArr = $pc28['numbers'];
  1013. $combo = $drawInfo['combo'];
  1014. $key = 'lottery_numbers_' . $v->issue_no;
  1015. if (Cache::add($key, $winning_numbers, 100)) {
  1016. Log::channel('issue')->info('开奖期号: ' . $v->issue_no . ' 开奖号码: ' . $winning_numbers, [
  1017. 'source' => 'playnow',
  1018. 'draw' => $drawNo,
  1019. 'combo' => $combo,
  1020. 'pc28_total' => $pc28['total'],
  1021. ]);
  1022. self::lotteryDraw($v->id, $winning_numbers, $combo, '');
  1023. $new = false;
  1024. } else {
  1025. Log::channel('issue')->info('开奖期号已处理,跳过重复开奖', [
  1026. 'issue_no' => $v->issue_no,
  1027. 'winning_numbers' => $winning_numbers,
  1028. ]);
  1029. }
  1030. $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
  1031. //更新游戏开关的切换
  1032. if ($pc28Switch == 0) Config::setPc28Switch();
  1033. }
  1034. }
  1035. // sleep(5); // 等待开奖完成
  1036. if ($new) {
  1037. $new_issue_no = $drawNo + 1; // 新期号
  1038. $newInfo = self::findOne(['issue_no' => $new_issue_no]); // 找新的期号
  1039. // 不存在
  1040. if (!$newInfo) {
  1041. Log::channel('issue')->info('新增期号: ' . $new_issue_no, [
  1042. 'source' => 'playnow',
  1043. 'start_time' => $startTime,
  1044. 'end_time' => $endTime,
  1045. ]);
  1046. $res = self::submit([
  1047. 'issue_no' => $new_issue_no,
  1048. 'status' => self::model()::STATUS_DRAFT,
  1049. 'start_time' => $startTime,
  1050. 'end_time' => $endTime,
  1051. ]);
  1052. Prediction::prediction($new_issue_no);
  1053. $id = $res['key'] ?? 0;
  1054. if ($id) {
  1055. self::betting($id); // 开始下注
  1056. }
  1057. Cache::set('new_issue_no', $new_issue_no, 10); // 缓存
  1058. } else {
  1059. Log::channel('issue')->info('期号已存在,无需新增', [
  1060. 'issue_no' => $new_issue_no,
  1061. 'status' => $newInfo->status,
  1062. 'start_time' => $newInfo->start_time,
  1063. 'end_time' => $newInfo->end_time,
  1064. ]);
  1065. }
  1066. }
  1067. return $result;
  1068. }
  1069. // 获取最新的开奖数据
  1070. public
  1071. static function getLatestIssue2()
  1072. {
  1073. $url = "https://ydpc28.co/api/pc28/list";
  1074. $result = file_get_contents($url);
  1075. $result = json_decode($result, true);
  1076. if ($result['errorCode'] != 0) {
  1077. return ['code' => self::NOT, 'msg' => '获取最新期号失败'];
  1078. }
  1079. $nextDrawInfo = $result['data']['nextDrawInfo'];
  1080. $startTime = $nextDrawInfo['currentBJTime'];
  1081. // if($nextDrawInfo['nextDrawTime'] >= date('H:i:s')) {
  1082. // $endTime = date('Y-m-d').' '.$nextDrawInfo['nextDrawTime']; // 下一期的截止时间
  1083. // }else{
  1084. // $endTime = date('Y-m-d',strtotime('+1 day')).' '.$nextDrawInfo['nextDrawTime']; // 下一期的截止时间
  1085. // }
  1086. $endTime = date('Y-m-d H:i:s', strtotime($startTime) + 210);
  1087. $new = true;
  1088. $list = $result['data']['list'];
  1089. $listKey = [];
  1090. foreach ($list as $k => $v) {
  1091. $listKey[$v['lotNumber']] = $v;
  1092. }
  1093. $oldList = self::findAll(['status' => self::model()::STATUS_CLOSE]); // 获取所有封盘的期号
  1094. foreach ($oldList as $k => $v) {
  1095. if (isset($listKey[$v->issue_no])) {
  1096. $issue = $listKey[$v->issue_no];
  1097. $winning_numbers = implode(',', str_split((string)$issue['openCode']));
  1098. $winArr = array_map('intval', explode(',', $winning_numbers));
  1099. // 组合
  1100. $sum = array_sum($winArr);
  1101. $combo = [];
  1102. $sumOddEven = self::calculateOddEven($sum); // 总和单双
  1103. $combo[] = $sumOddEven;
  1104. $sumSize = self::calculateSumSize($sum); // 总和大小
  1105. $combo[] = $sumSize;
  1106. $sumExtremeSize = self::calculateSumExtremeSize($sum); // 总和极值
  1107. if ($sumExtremeSize) {
  1108. $combo[] = $sumExtremeSize;
  1109. }
  1110. $sumBaoZi = self::isBaoZi($winArr[0], $winArr[1], $winArr[2]); // 豹子
  1111. if ($sumBaoZi) {
  1112. $combo[] = $sumBaoZi;
  1113. }
  1114. $sumPair = self::isPair($winArr[0], $winArr[1], $winArr[2]); // 对子
  1115. if ($sumPair) {
  1116. $combo[] = $sumPair;
  1117. }
  1118. $sumStraight = self::isStraight($winArr[0], $winArr[1], $winArr[2]); // 顺子
  1119. if ($sumStraight) {
  1120. $combo[] = $sumStraight;
  1121. }
  1122. $tail = self::getLastDigit($sum); // 总和尾数
  1123. if ($tail == 0 || $tail == 9) {
  1124. } else {
  1125. $combo[] = '尾' . $tail; // 尾数
  1126. }
  1127. $combo = implode(' ', $combo);
  1128. self::lotteryDraw($v->id, $winning_numbers, $combo, '');
  1129. }
  1130. }
  1131. return $result;
  1132. }
  1133. // 封盘倒数
  1134. public
  1135. static function syncCountdownIssue()
  1136. {
  1137. $info = self::model()::where('status', self::model()::STATUS_BETTING)->orderBy('end_time', 'asc')->first();
  1138. if ($info) {
  1139. $now_date = date('Y-m-d H:i:s', time() + IssueService::COUNTDOWN_TO_CLOSING_THE_MARKET);
  1140. if ($info['end_time'] < $now_date) {
  1141. $replyInfo = KeyboardService::findOne(['button' => '封盘倒数']);
  1142. if ($replyInfo) {
  1143. $text = $replyInfo->reply;
  1144. $buttons = json_decode($replyInfo->buttons, true);
  1145. $image = $replyInfo->image;
  1146. if ($image) {
  1147. $image = url($image);
  1148. }
  1149. if (Cache::has('issue_countdown_' . $info->issue_no)) {
  1150. } else {
  1151. $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
  1152. if ($pc28Switch == 0) {
  1153. self::asyncBettingGroupNotice($text, $buttons, $image);
  1154. Cache::put('issue_countdown_' . $info->issue_no, true, 60); // 缓存50秒,防止多次发送
  1155. }
  1156. }
  1157. }
  1158. }
  1159. }
  1160. }
  1161. // 停止下注
  1162. public
  1163. static function syncCloseIssue()
  1164. {
  1165. $now_date = date('Y-m-d H:i:s', time() + 30); // 提前30秒
  1166. $list = self::findAll(['status' => self::model()::STATUS_BETTING]);
  1167. foreach ($list as $k => $v) {
  1168. if ($v['end_time'] < $now_date) {
  1169. self::closeBetting($v->id);
  1170. }
  1171. }
  1172. }
  1173. // 生成开奖图片
  1174. public
  1175. static function lotteryImage($issue_no)
  1176. {
  1177. $list = self::model()::where('issue_no', '<=', $issue_no)->where(self::getWhere(['status' => self::model()::STATUS_DRAW]))->orderBy('issue_no', 'desc')->take(20)->get();
  1178. $records = $list->toArray();
  1179. foreach ($records as $k => $v) {
  1180. $winning_numbers = explode(',', $v['winning_numbers']);
  1181. $v['winning_numbers'] = $winning_numbers;
  1182. // 组合
  1183. $sum = array_sum($winning_numbers);
  1184. $v['sum'] = $sum;
  1185. $sumOddEven = self::calculateOddEven($sum); // 总和单双
  1186. $sumSize = self::calculateSumSize($sum); // 总和大小
  1187. $v['combo'] = $sumSize . ' ' . $sumOddEven;
  1188. $sumExtremeSize = self::calculateSumExtremeSize($sum); // 总和极值
  1189. if (!$sumExtremeSize) {
  1190. $sumExtremeSize = '-';
  1191. }
  1192. $v['extreme'] = $sumExtremeSize;
  1193. $tail = self::getLastDigit($sum); // 总和尾数
  1194. if ($tail === 0 || $tail === 9) {
  1195. $tailStr = '-';
  1196. } else {
  1197. $tailStr = '尾' . $tail;
  1198. }
  1199. $v['tail'] = $tailStr;
  1200. $records[$k] = $v;
  1201. }
  1202. $service = new LotteryImageService();
  1203. $url = $service->generate($records);
  1204. self::model()::where('issue_no', $issue_no)->update(['image' => $url]);
  1205. return $url;
  1206. }
  1207. // 发送开奖图片
  1208. public
  1209. static function sendLotteryImage($chatId, $issueNo)
  1210. {
  1211. $recordImage = self::lotteryImage($issueNo);
  1212. self::sendMessage($chatId, '', [], url($recordImage));
  1213. // dispatch(new SendTelegramMessageJob('', [], url($recordImage)));
  1214. }
  1215. }