Telegram.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. <?php
  2. /**
  3. * Created by IntelliJ IDEA.
  4. * User: solu
  5. * Date: 2019/3/26
  6. * Time: 5:38 PM
  7. */
  8. class Telegram {
  9. const REDIS_PREFIX_TG_CSRF = 'globals:tg_csrf:';
  10. const REDIS_TG_CSRF_TTL = 60;
  11. const REDIS_TG_GROUP_TO_GROUP_HASH = 'globals:tg_group_to_group_hash';
  12. const REDIS_TG_USER_TO_USER_HASH = 'globals:tg_user_to_user_hash';
  13. const REDIS_TG_MESSAGE_LIST = 'globals:tg_message_list';
  14. private static $messageTypes = [
  15. 'sticker',
  16. 'photo',
  17. 'audio',
  18. 'video_note',
  19. 'video'
  20. ];
  21. /**
  22. * @param $message
  23. * @throws Exception
  24. */
  25. public static function processMessage($message) {
  26. // process incoming message
  27. // $message_id = $message['message_id'];
  28. $chat_id = $message['chat']['id'];
  29. $chatType = $message['chat']['type'];
  30. $from = $message['from']['id'];
  31. // 处理登录,绑定
  32. if (strpos($message['text'], '/start') !== false) {
  33. list($_, $token) = explode(' ', $message['text']);
  34. $user_id = $from;
  35. if (strpos($token, 'login') !== false) {
  36. $name = $message['chat']['first_name'];
  37. $message['chat']['last_name'] && $name .= "_{$message['chat']['last_name']}";
  38. $user_id = User::login($from, Account::TYPE_TG, $name);
  39. if ($user_id) {
  40. self::setUserByTG($from, $user_id);
  41. self::setCSRFStatus($token, $user_id);
  42. $text = 'You\'ve successfully logged in';
  43. } else {
  44. $text = 'Login failure';
  45. }
  46. } else if (strpos($token, 'bind') !== false) {
  47. self::setCSRFStatus($token, $user_id);
  48. $text = 'You\'ve successfully bind meechat account';
  49. } else if (strpos($token, 'sync') !== false) { // 群同步流程
  50. list($_, $groupId) = explode('_', $token);
  51. $groupId = intval($groupId);
  52. if (!$groupId) {
  53. $text = "Wrong operation!!";
  54. } else {
  55. $administrators = ThirdApi::getTelegramGroupAdministrators($groupId);
  56. if (in_array($from, $administrators)) {
  57. $text = self::buildSyncText($from, $groupId);
  58. } else {
  59. $text = "you are not administrator";
  60. }
  61. }
  62. } else {
  63. return;
  64. }
  65. self::apiRequest('sendMessage', [
  66. 'chat_id' => $from,
  67. 'text' => $text,
  68. 'parse_mode' => 'HTML',
  69. ]);
  70. return;
  71. }
  72. // 只转发群消息
  73. if (!in_array($chatType, ['group', 'supergroup'])) {
  74. return;
  75. }
  76. $objGroup = new GroupInfo();
  77. $objSession = new Session();
  78. // 处理升级为超级群的信息
  79. if (isset($message['migrate_from_chat_id'])) {
  80. $srcId = $message['migrate_from_chat_id'];
  81. $groupId = self::getGroupByTG($srcId);
  82. self::setGroupByTG($chat_id, $groupId);
  83. self::delGroupByTG($srcId);
  84. $objGroup->objTable->updateObject(['tg_group_id' => $chat_id], ['tg_group_id' => $srcId]);
  85. return;
  86. }
  87. $groupId = self::getGroupByTG($chat_id);
  88. if (!$groupId) {
  89. // 判断是否拉进MeeChat机器人
  90. $doGuide = false;
  91. if (isset($message['new_chat_members'])) {
  92. foreach ($message['new_chat_members'] as $member) {
  93. if ($member['id'] == BOT_ID) {
  94. $doGuide = true;
  95. break;
  96. }
  97. }
  98. }
  99. // 创建群的时候已拉入机器人
  100. if (isset($message['group_chat_created']) && $message['group_chat_created']) {
  101. $doGuide = true;
  102. }
  103. // 开始引导同步操作
  104. if ($doGuide) {
  105. $url = BOT_CHAT_URL . "?start=sync_{$chat_id}";
  106. $text = "点击<a href='{$url}'>@meechatbot</a>查看私聊信息,将该群与MeeChat群组进行关联同步
  107. To sync up this chat with MeeChat group, take a look to private message from <a href='{$url}'>@meechatbot</a>";
  108. self::apiRequest('sendMessage', [
  109. 'chat_id' => $chat_id,
  110. 'text' => $text,
  111. 'parse_mode' => 'HTML',
  112. ]);
  113. }
  114. return;
  115. }
  116. $userId = self::getUserByTG($from);
  117. !$userId && $userId = TG_BOT_ID;
  118. $extInfo = [
  119. 'is_tg' => 1,
  120. ];
  121. if ($userId == TG_BOT_ID) {
  122. $name = $message['from']['first_name'];
  123. $message['from']['last_name'] && $name .= "_{$message['from']['last_name']}";
  124. $extInfo['tg_nick'] = $name;
  125. }
  126. $msgType = Session::MSG_TYPE_TEXT;
  127. if (isset($message['text'])) { // 文本消息
  128. $text = $message['text'];
  129. // 不处理command
  130. if ($text[0] == '/') {
  131. return;
  132. }
  133. } else { // 图片视频音频
  134. $mineType = '';
  135. $fileId = '';
  136. foreach (self::$messageTypes as $messageType) {
  137. if (isset($message[$messageType])) {
  138. $entity = $message[$messageType];
  139. if ($messageType == 'photo') { // 图片会有多个尺寸,取原图
  140. $entity = array_pop($entity);
  141. }
  142. $fileId = $entity['file_id'];
  143. $mineType = $message[$messageType]['mime_type'] ?: '';
  144. break;
  145. }
  146. }
  147. if (!$fileId) {
  148. return;
  149. }
  150. // 标题
  151. if ($message['caption']) {
  152. $caption = $message['caption'];
  153. $caption = Utils::encodeRC4($caption);
  154. $objSession->sendGroupMsg($userId, $groupId, Session::MSG_TYPE_TEXT, $caption, false, true, $extInfo);
  155. }
  156. list($url, $mineType, $coverUrl) = self::getURLFromFileId($fileId, $mineType);
  157. $msgType = FileUrl::getType($mineType);
  158. $text = $url;
  159. $coverUrl && $extInfo['cover_url'] = $coverUrl;
  160. }
  161. $text = Utils::encodeRC4($text);
  162. $objSession->sendGroupMsg($userId, $groupId, $msgType, $text, false, true, $extInfo);
  163. }
  164. /**
  165. * 转存文件
  166. * @author solu
  167. * @param $fileId
  168. * @param string $mineType
  169. * @throws Exception
  170. * @return array
  171. */
  172. public static function getURLFromFileId($fileId, $mineType = '') {
  173. $tgFilePath = self::getFilePath($fileId);
  174. $url = TELEGRAM_FILE_URL . $tgFilePath;
  175. list($path, $filename, $_, $_) = Bs2UploadHelper::saveFileFromUrl($url);
  176. if (!$mineType) {
  177. $mineType = mime_content_type($path);
  178. }
  179. $objFile = new FileUrl();
  180. $url = $objFile->getFileUrl($path, $filename, $mineType);
  181. $url = awsReplaceImg($url);
  182. // 处理视频封面
  183. $coverUrl = '';
  184. if (strpos($mineType, 'video') !== false) {
  185. $coverUrl = $objFile->getVideoCover($path, $filename);
  186. $coverUrl = awsReplaceImg($coverUrl);
  187. }
  188. unlink($path);
  189. return [$url, $mineType, $coverUrl];
  190. }
  191. /**
  192. * 获取文件路径
  193. * @param $fileId
  194. * @return mixed
  195. * @throws Exception
  196. */
  197. private static function getFilePath($fileId) {
  198. $url = TELEGRAM_API_URL . 'getFile?file_id=' . $fileId;
  199. $objHttp = new dwHttp();
  200. $resp = $objHttp->get2($url);
  201. $data = json_decode($resp, true);
  202. if (!$data || !$data['ok']) {
  203. $msg = $data['description'] ?: 'get file info error';
  204. throw new Exception($msg, CODE_PARAM_ERROR);
  205. }
  206. return $data['result']['file_path'];
  207. }
  208. /**
  209. * @param $method
  210. * @param $parameters
  211. * @return bool|mixed
  212. * @throws Exception
  213. * @return mixed
  214. */
  215. public static function apiRequestJson($method, $parameters) {
  216. if (!is_string($method)) {
  217. throw new Exception("Method name must be a string\n");
  218. }
  219. if (!$parameters) {
  220. $parameters = array();
  221. } else if (!is_array($parameters)) {
  222. throw new Exception("Parameters must be an array\n");
  223. }
  224. $parameters["method"] = $method;
  225. $handle = curl_init(TELEGRAM_API_URL);
  226. curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
  227. curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 5);
  228. curl_setopt($handle, CURLOPT_TIMEOUT, 60);
  229. curl_setopt($handle, CURLOPT_POST, true);
  230. curl_setopt($handle, CURLOPT_POSTFIELDS, json_encode($parameters));
  231. curl_setopt($handle, CURLOPT_HTTPHEADER, array("Content-Type: application/json"));
  232. return self::exec_curl_request($handle);
  233. }
  234. /**
  235. * @param $method
  236. * @param $parameters
  237. * @return mixed
  238. * @throws Exception
  239. */
  240. public static function apiRequest($method, $parameters) {
  241. if (!is_string($method)) {
  242. throw new Exception("Method name must be a string\n");
  243. }
  244. if (!$parameters) {
  245. $parameters = array();
  246. } else if (!is_array($parameters)) {
  247. throw new Exception("Parameters must be an array\n");
  248. }
  249. foreach ($parameters as $key => &$val) {
  250. // encoding to JSON array parameters, for example reply_markup
  251. if (!is_numeric($val) && !is_string($val)) {
  252. $val = json_encode($val);
  253. }
  254. }
  255. $url = TELEGRAM_API_URL.$method.'?'.http_build_query($parameters);
  256. $handle = curl_init($url);
  257. curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
  258. curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 5);
  259. curl_setopt($handle, CURLOPT_TIMEOUT, 60);
  260. return self::exec_curl_request($handle);
  261. }
  262. /**
  263. * @param $handle
  264. * @return bool|mixed
  265. * @throws Exception
  266. * @return mixed
  267. */
  268. public static function exec_curl_request($handle) {
  269. $response = curl_exec($handle);
  270. if ($response === false) {
  271. $errno = curl_errno($handle);
  272. $error = curl_error($handle);
  273. curl_close($handle);
  274. throw new Exception("Curl returned error $errno: $error\n");
  275. }
  276. $http_code = intval(curl_getinfo($handle, CURLINFO_HTTP_CODE));
  277. curl_close($handle);
  278. if ($http_code >= 500) {
  279. // do not wat to DDOS server if something goes wrong
  280. sleep(10);
  281. return false;
  282. } else if ($http_code != 200) {
  283. $response = json_decode($response, true);
  284. throw new Exception("Request has failed with error {$response['error_code']}: {$response['description']}\n");
  285. } else {
  286. $response = json_decode($response, true);
  287. if (isset($response['description'])) {
  288. throw new Exception("Request was successful: {$response['description']}\n");
  289. }
  290. $response = $response['result'];
  291. }
  292. // file_put_contents('/tmp/telegram_message.log', $response, FILE_APPEND);
  293. return $response;
  294. }
  295. private static function getCSRFKey($token) {
  296. return self::REDIS_PREFIX_TG_CSRF . $token;
  297. }
  298. public static function initCSRF($token) {
  299. $objRedis = dwRedis::init();
  300. $key = self::getCSRFKey($token);
  301. $objRedis->setex($key, self::REDIS_TG_CSRF_TTL, 0);
  302. }
  303. /**
  304. * 获取状态
  305. * @author solu
  306. * @param $token
  307. * @return int
  308. */
  309. public static function getCSRFStatus($token) {
  310. $objRedis = dwRedis::init();
  311. $key = self::getCSRFKey($token);
  312. $status = $objRedis->get($key);
  313. return false !== $status ? intval($status) : -1;
  314. }
  315. public static function setCSRFStatus($token, $id) {
  316. $objRedis = dwRedis::init();
  317. $key = self::getCSRFKey($token);
  318. $objRedis->setex($key, self::REDIS_TG_CSRF_TTL, $id);
  319. }
  320. public static function setGroupByTG($tgGroup, $group) {
  321. $objRedis = dwRedis::init();
  322. $objRedis->hSet(self::REDIS_TG_GROUP_TO_GROUP_HASH, $tgGroup, $group);
  323. }
  324. public static function getGroupByTG($tgGroup) {
  325. $objRedis = dwRedis::init();
  326. return $objRedis->hGet(self::REDIS_TG_GROUP_TO_GROUP_HASH, $tgGroup);
  327. }
  328. public static function delGroupByTG($tgGroup) {
  329. $objRedis = dwRedis::init();
  330. return $objRedis->hDel(self::REDIS_TG_GROUP_TO_GROUP_HASH, $tgGroup);
  331. }
  332. public static function setUserByTG($tgUser, $user) {
  333. $objRedis = dwRedis::init();
  334. $objRedis->hSet(self::REDIS_TG_USER_TO_USER_HASH, $tgUser, $user);
  335. }
  336. public static function getUserByTG($tgUser) {
  337. $objRedis = dwRedis::init();
  338. return $objRedis->hGet(self::REDIS_TG_USER_TO_USER_HASH, $tgUser);
  339. }
  340. public static function delUserByTG($tgUser) {
  341. $objRedis = dwRedis::init();
  342. $objRedis->hDel(self::REDIS_TG_USER_TO_USER_HASH, $tgUser);
  343. }
  344. public static function pushMessageList($msg) {
  345. if (is_array($msg)) {
  346. $msg = json_encode($msg);
  347. }
  348. $objRedis = dwRedis::init();
  349. $objRedis->lPush(self::REDIS_TG_MESSAGE_LIST, $msg);
  350. }
  351. public static function popMessageList($objRedis = null) {
  352. !$objRedis && $objRedis = dwRedis::init();
  353. $ret = $objRedis->brPop([self::REDIS_TG_MESSAGE_LIST], 30);
  354. return $ret[1];
  355. }
  356. private static function buildSyncText($from, $chat_id) {
  357. $url = 'https://' . $_SERVER['HTTP_HOST'] . '/#/relateGroup?';
  358. $params = [
  359. 'admin_id' => $from,
  360. 'group_id' => $chat_id,
  361. 'auth_date' => time(),
  362. ];
  363. $params['hash'] = ThirdApi::genTelegramHash($params);
  364. $url .= http_build_query($params);
  365. return "<a href='{$url}'>点击选择你需要同步的MeeChat群组
  366. Click to choose your MeeChat group to sync up</a>";
  367. }
  368. }