'111:token', 'chat' => '@username', // @username канала/чата или числовой id начинающийся с -100 'src' => 'truexanewsua', // источник 'first' => 10, // сколько постов репостить при первом запуске 'ads' => '~\s*\|?\s*(ТРУХА|Надіслати новину).*$~us', // регэксп рекламного хвоста 'skip_words' => ['промокод', 'реклама', 'партнерська', '#ad'], // слова в тексте поста -> скип 'skip_links' => ['bit.ly', 'cutt.ly', 't.me/+'], // подстроки в ссылках текста и url-кнопок -> скип 'skip_buttons' => true, // скипать любой пост с url-кнопками ]; const TEXT_LIMIT = 4096; // лимит текста сообщения (premium: 8192) const CAPTION_LIMIT = 1024; // лимит подписи к медиа (premium: 4096) function apiGet(string $method, array $params) { global $config; $response = @file_get_contents( "https://api.telegram.org/bot{$config['bot']}/$method?" . http_build_query($params), false, stream_context_create(['http' => ['ignore_errors' => true, 'timeout' => 30]]) ); if ($response === false) return false; $decoded = json_decode($response, true) ?: []; empty($decoded['ok']) and exitIfChatForbidden($decoded); return $decoded; } function apiUpload(string $method, array $params) { global $config; $curl = curl_init("https://api.telegram.org/bot{$config['bot']}/$method"); curl_setopt_array($curl, [ CURLOPT_RETURNTRANSFER => 1, CURLOPT_POSTFIELDS => $params, CURLOPT_TIMEOUT => 300, CURLOPT_CONNECTTIMEOUT => 10, ]); $response = curl_exec($curl); curl_close($curl); if ($response === false) return false; $decoded = json_decode($response, true) ?: []; empty($decoded['ok']) and exitIfChatForbidden($decoded); return $decoded; } function exitIfChatForbidden(array $response): void { if (($response['error_code'] ?? 0) == 403 || str_contains($response['description'] ?? '', 'chat not found')) exit(2); } function randomUserAgent() { $desktop = ['Windows NT 10.0; Win64; x64', 'Windows NT 11.0; Win64; x64', 'Macintosh; Intel Mac OS X 10_15_7', 'X11; Linux x86_64'][rand(0, 3)]; $webkit = 'AppleWebKit/537.36 (KHTML, like Gecko)'; $chrome = 'Chrome/' . rand(126, 132) . '.0.' . rand(6000, 6900) . '.' . rand(30, 220); return match (rand(1, 5)) { 1 => "Mozilla/5.0 ($desktop) $webkit $chrome Safari/537.36", 2 => "Mozilla/5.0 ($desktop) $webkit $chrome Safari/537.36 Edg/" . rand(126, 132) . '.0.0.0', 3 => "Mozilla/5.0 ($desktop; rv:" . ($firefox = rand(130, 134)) . ".0) Gecko/20100101 Firefox/$firefox.0", 4 => "Mozilla/5.0 (Linux; Android " . rand(12, 15) . '; SM-S' . rand(911, 928) . "B) $webkit $chrome Mobile Safari/537.36", 5 => 'Mozilla/5.0 (iPhone; CPU iPhone OS ' . rand(16, 18) . '_' . rand(0, 6) . ' like Mac OS X) AppleWebKit/605.1.15' . ' (KHTML, like Gecko) Version/' . rand(16, 18) . '.' . rand(0, 6) . ' Mobile/15E148 Safari/604.1', }; } for ($attempt = 0; $attempt < 3; $attempt++) { $html = @file_get_contents("https://t.me/s/{$config['src']}", false, stream_context_create([ 'http' => ['header' => 'User-Agent: ' . randomUserAgent(), 'timeout' => 30, 'ignore_errors' => true], ])); if (str_contains((string)$html, 'data-post')) break; sleep(2); } str_contains((string)$html, 'data-post') or exit("fetch fail\n"); preg_match_all( '~data-post="' . $config['src'] . '/(\d+)".*?(?=
str_contains($url, '.mp4') || str_contains($url, '.mov') ? 'video' : 'photo'; $mimeType = fn($type, $url) => $type == 'video' ? (str_contains($url, '.mov') ? 'video/quicktime' : 'video/mp4') : 'image/jpeg'; $truncate = fn($text, $limit) => mb_strlen($text) > $limit ? mb_substr($text, 0, $limit - 1) . '…' : $text; $fileIdCache = json_decode(@file_get_contents("$stateFile.m") ?: '[]', true) ?: []; $lastProcessedId = 0; $networkFailed = false; $caption = ''; $sendTextFallback = function () use (&$networkFailed, $config, &$caption, $truncate) { if ($caption === '') return; $decoded = apiGet('sendMessage', ['chat_id' => $config['chat'], 'text' => $truncate($caption, TEXT_LIMIT)]); $decoded === false and $networkFailed = true; }; foreach ($posts as $post) { if ($post[1] <= $lastPostedId) continue; preg_match('~message_text[^>]*>(.*?)
~s', $post[0], $textMatch); preg_match_all("~photo_wrap[^>]*url\('([^']+)'~", $post[0], $photos); preg_match_all('~]*src="([^"]+)"~', $post[0], $videos); $mediaUrls = array_values(array_unique(array_merge($photos[1], $videos[1]))); preg_match_all('~url_button"[^>]*href="([^"]+)~', $post[0], $buttons); preg_match_all('~href="(https?[^"]+)"~', ($textMatch[1] ?? '') . implode(' ', $buttons[1]), $links); $isAd = (bool)$config['skip_buttons'] && strpos($post[0], 'url_button'); foreach ($config['skip_words'] as $word) $isAd = $isAd || stripos(strip_tags($textMatch[1] ?? ''), $word) !== false; foreach ($config['skip_links'] as $word) foreach ($links[1] as $link) $isAd = $isAd || stripos($link, $word) !== false; if ($isAd) { $lastProcessedId = $post[1]; continue; } $caption = $truncate(trim(preg_replace( $config['ads'], '', html_entity_decode(strip_tags(preg_replace(['~~i', '~

~i'], "\n", $textMatch[1] ?? '')), ENT_QUOTES, 'UTF-8') )), CAPTION_LIMIT); $tempFiles = []; foreach ($mediaUrls as $url) { if (isset($fileIdCache[$url]) || isset($tempFiles[$url])) continue; $raw = @file_get_contents($url, false, stream_context_create( ['http' => ['header' => 'User-Agent: ' . randomUserAgent(), 'timeout' => 120]] )); if ($raw === false) continue; file_put_contents($tempFiles[$url] = tempnam(sys_get_temp_dir(), 'tg'), $raw); } $readyMedia = array_values(array_filter($mediaUrls, fn($url) => isset($fileIdCache[$url]) || isset($tempFiles[$url]))); if (!$mediaUrls || !$readyMedia) { $sendTextFallback(); } elseif (count($readyMedia) == 1) { $type = $detectMediaType($url = $readyMedia[0]); $decoded = apiUpload($type == 'video' ? 'sendVideo' : 'sendPhoto', [ 'chat_id' => $config['chat'], 'caption' => $caption, $type => $fileIdCache[$url] ?? new CURLFile($tempFiles[$url], $mimeType($type, $url)), ]); if ($decoded === false) { $networkFailed = true; break; } if (!empty($decoded['result'])) $fileIdCache[$url] = $type == 'video' ? $decoded['result']['video']['file_id'] : end($decoded['result']['photo'])['file_id']; else $sendTextFallback(); } else { $albumItems = []; $attachFiles = []; foreach ($readyMedia as $index => $url) { $type = $detectMediaType($url); $albumItems[] = ['type' => $type, 'media' => $fileIdCache[$url] ?? 'attach://m' . $index]; isset($fileIdCache[$url]) or $attachFiles['m' . $index] = new CURLFile($tempFiles[$url], $mimeType($type, $url)); } $albumItems[0]['caption'] = $caption; $decoded = apiUpload('sendMediaGroup', ['chat_id' => $config['chat'], 'media' => json_encode($albumItems)] + $attachFiles); if ($decoded === false) { $networkFailed = true; break; } if (empty($decoded['result'])) $sendTextFallback(); else foreach ($decoded['result'] as $index => $item) $fileIdCache[$readyMedia[$index]] = isset($item['video']) ? $item['video']['file_id'] : end($item['photo'])['file_id']; } foreach ($tempFiles as $tmpFile) @unlink($tmpFile); if ($networkFailed) break; $lastProcessedId = $post[1]; } if (!$networkFailed) $lastProcessedId and file_put_contents($stateFile, max($lastPostedId, $lastProcessedId)); @file_put_contents("$stateFile.m", json_encode($fileIdCache)); exit($networkFailed ? 1 : 0);