Граббер всех файлов телеграм канала

В свете того, что телеграм начали замедлять, появились опасения, что потеряю в конце концов доступ к телеграм каналу, где собрано много всяких интересных файлов. В связи с чем было принято решение спарсить и скачать всё его содержимое. Написанный скрипт проходит по каждому сообщению канала и скачивает все файлы в папки соответствующие идентифактору сообщения (или идентификатору группы сообщений). В общем получилось что-то вроде:

#!/usr/bin/env python3
# encoding: utf-8
import sys
import os,fcntl
import funcs
from telethon import TelegramClient
import requests
import configparser

## Защита от дубля запуска скрипта
fp = open(os.path.realpath(__file__), 'r')
try:
    fcntl.flock(fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError:
    sys.exit(0)

config = configparser.ConfigParser()
config.read("settings.ini")

print(config["Telegram"]["telegram_api_id"])

async def SaveFile(dir_name,message):
     if os.path.isdir(os.path.dirname(os.path.abspath(__file__))+f"/files/{dir_name}")==False:
       os.mkdir(os.path.dirname(os.path.abspath(__file__))+f"/files/{dir_name}")
     with open(os.path.dirname(os.path.abspath(__file__))+f"/files/{dir_name}/message.txt", 'w') as file:
        file.write(message.message)
     print(f"Вложения: {message.media}")
     if hasattr(message.media,"document"):
       if message.media.document != False:
         file_name=message.media.document.attributes[0].file_name
         print(f"-вложенный файл {file_name}")
         print("--сохраняю..")
         if os.path.isfile(os.path.dirname(os.path.abspath(__file__))+f"/files/{dir_name}/{file_name}")==False:
           await client.download_media(message.media, file=os.path.dirname(os.path.abspath(__file__))+f"/files/{dir_name}/{file_name}")
           print("--ок..")
         else:
           print("--уже скачивали!")
     if hasattr(message.media,"video"):
       if message.media.video!=False:
          print(f"-вложенное видео")

async def tele_news_parse():
    dp = await client.get_entity(config["Telegram"]["stl_chanel"])
    poz=int(config["Telegram"]["current_message_id"])
    print(f"-start at {poz}")
    async for message in client.iter_messages(dp,limit=10,offset_id=poz):
        print("---------------------------------------------------------")
        if hasattr(message, "message"):
            if message.message!=None:
                print(f"Сообщение: {message}")
                if message.grouped_id:
                   print("-its album!")
                   target_group_id = message.grouped_id
                   search_ids = range(message.id - 20, message.id + 21)
                   posts = await client.get_messages(config["Telegram"]["stl_chanel"], ids=list(search_ids))
                   media_group = []
                   for post in posts:
                     if post is not None and post.grouped_id == target_group_id and post.media is not None:
                      media_group.append(post)
                   for message in media_group:
                      print(f"--из альбома: {message}")
                      await SaveFile(target_group_id,message)
                else:
                  print("-its one file!")
                  await SaveFile(message.id,message)


    with open('settings.ini', 'w') as configfile:
        poz+=10
        print(f"-new poz at {poz}")
        config["Telegram"]["current_message_id"]=str(poz)
        config.write(configfile)

if __name__ == '__main__':
    client = TelegramClient("parser_data", config["Telegram"]["telegram_api_id"], config["Telegram"]["telegram_api_hash"])
    client.start()
    client.loop.run_until_complete(tele_news_parse())

Парсинг телеграм каналов

Задача: необходимо просматривать несколько новостных телеграм каналов, и в случае обнаружения в новости неких стоп-слов, высылать уведомление на электронную почту.

К сожалению воспользоваться для решения этой задачи API для работы с ботами не получится, т.к. такого функционала просто нет. Выходом может служить — воспользоваться одним из многочисленных клиентов Телеграм, реализованых на PHP, Python, JavaScript (NodeJS) и т.д. В моём случае — воспользуюсь python и библиотекой telethon. К ней довольно толковая документация, в том числе и на русском

Итак, для начала нужно зайти на ресурс https://my.telegram.org/apps и получить api_id и api_hash, для того чтобы библиотека смогла создать соединение. Далее создам файл с настройками вида:

{
  "chanels": [
    "rus_now_news","-1001237513492"
  ],
  "limit": 200,
  "alert_words": [
    "кусь","мейнкун","бабки","лапландия","рабочие","песель-акробат"
  ],
  "api_id": "12435245235",
  "api_hash": "екыпукерпенуркенрке",
  "notify_email": "екпукеп@укепукеуке.ru",
  "email_login": "уепукеп-куепукеп@кепукеп.ru",
  "email_from": "уекпукеп@кепукепук.ru",
  "from_password": "укацука!укауцка",
  "smtp_server": "уцкацука-owa.уцкацука.ru",
  "smtp_port":587
}

В нём перечисляем каналы которые мониторим и стоп слова, которые ловим. Алгоритм работы скрипта:

  • соединяемся с сервером телеграм
  • получаем список последних новостей канала
  • если ID новости уже смотрели, пропускаем его
  • если в тексте новости нашли стоп слово — отправляем уведомление на почту

А вот и сам скрипт:

#!/usr/bin/env python3
# encoding: utf-8
import asyncio
import json
import sys
import re
from telethon import TelegramClient
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from telethon.tl.functions.contacts import ResolveUsernameRequest
from telethon.tl.functions.channels import GetMessagesRequest
from telethon.tl.functions.messages import GetHistoryRequest, ReadHistoryRequest

with open('config.json', 'r') as file:
    config_data = json.load(file)
    print(config_data)
client = TelegramClient("parser_data", config_data["api_id"], config_data["api_hash"])
async def TConnect():
    await client.start()
async def ListChanels():
    async for dialog in client.iter_dialogs():
        print(dialog.name, 'has ID', dialog.id)

def save_chan_json(chan,chan_data):
    f = open(chan + '.json', "w+")
    json.dump(chan_data, f)
    f.close()

def get_chan_json(chan):
    # узнаём какое последнее сообщение прочитали на канале?
    chan_data = {}
    chan_data["las_id"] = 0
    try:
        with open(chan + '.json', 'r') as file:
            chan_data = json.load(file)
            print(chan_data)
            return chan_data
    except:
        save_chan_json(chan, chan_data)
        return chan_data
    return chan_data;

def SendMail(chan,word,message):
    msg = MIMEMultipart()
    msg['Subject'] = f"Найдено слово '{word}' в новости на канале {chan} в Телеграм"
    msg.add_header('Content-Type', 'text/html')
    message.text=message.text.replace(word,"<strong>"+word+"</strong>")
    dt_pub=message.date.strftime('%d-%m-%Y %H:%M:%S')
    msg.set_payload(f"Канал: <a href='https://t.me/{chan}'>https://t.me/{chan}</a>, опубликовано {dt_pub}<hr/>"+message.text)

    smtpObj = smtplib.SMTP(config_data["smtp_server"], config_data["smtp_port"])
    smtpObj.starttls()
    smtpObj.login(config_data["email_login"], config_data["from_password"])
    smtpObj.sendmail(config_data["email_from"], config_data["notify_email"], msg.as_string().encode('utf-8'))
    smtpObj.quit()
async def main():
    print("-start")
    await TConnect()
    #await ListChanels()
    for chan in config_data["chanels"]:
        chan_data=get_chan_json(chan)
        skeep_after = chan_data["las_id"]
        if "-" in chan:
          dp = await client.get_entity(int(chan))
        else:
          dp = await client.get_entity(chan)
        poz=0
        async for message in client.iter_messages(dp,limit=config_data["limit"]):
           if poz==0:
               chan_data["las_id"]=message.id
               save_chan_json(chan, chan_data)
           if skeep_after==message.id:
               print("Все новости уже прочитаны...")
               break
           print(f"-смотрим message_id:{message.id}")
           for word in config_data["alert_words"]:
               if word in message.text:
                   print(f"--нашли слово {word}")
                   SendMail(chan, word, message)
                   print(message)
           #print(message.id, message.text)
           poz=poz+1
    print("all done..");
if __name__ == '__main__':
    for param in sys.argv:
        if param == "--list_chanels":
            TConnect()
            ListChanels()
    with client:
        client.loop.run_until_complete(main())

Отправляем сообщения пользователям Telegram из коммандной строки. FreeBSD

1) Ставим из портов trelegram-cli

2) Заходим в telegram-cli, регистрируемся. По help — список комманд.

Нюансы: сначала нужно добавить контакты. Отправлять сообщения тоже можно не сразу, а только после выполнения комманды contact_list. Или запускать telegram-cli с ключем -W

Пробелы в именах при операциях с коммандами необходимо заменять на _

Например отправка сообщения:

3) Для получения списка контактов и их состояния, можно использовать что-то типа на PHP (требует доработки):

Из огромных минусов такого способа информирования — нельзя знать наверняка, получил контакт сообщение или нет. Во первых нет в API получения статуса отправленого сообщения, а во вторых  — при свернутом в трей телеграмме, статус контакта будет offline. Т.е. нет уверенности, на самом деле контакт в оффлайне, или у него приложение просто свернуто. Этот огромный жирный минус перечеркивает все достоинства Telegram, и соответственно использовать его для информирования например событий  в Zabbix не представляется возможным