본문 바로가기

텔레그램 API로 메시지 저장해서 출력 및 다운로드 하기

by 식 2023. 7. 25.

텔레그램 채팅목록 불러오고 비개발자분에게 확인을 시켜줄 일이 있어서 작업해보았다

 

상황 및 요구사항

- 채팅방의 내용을 일별로 불러오고 별도 파일로 다운로드 하고 싶었음

- 해당 채팅방의 대화건수가 많지 않고 작성자는 한명임

 

API키 발급

찾아보니 봇을 활용하는 방법도 있긴 했으나 API로 하는게 편해보여서 번호당 하나 발급 가능하다

https://my.telegram.org/apps

 

Authorization

Delete Account or Manage Apps Log in here to manage your apps using Telegram API or delete your account. Enter your number and we will send you a confirmation code via Telegram (not SMS).

my.telegram.org

 

메시지 불러와서 저장하기

레퍼런스 찾아보고 고민해보다가 파이썬의 Telethon과 sqlite3를 쓰는게 좋아보여서 찾아보고 적용

 

https://github.com/LonamiWebs/Telethon

 

GitHub - LonamiWebs/Telethon: Pure Python 3 MTProto API Telegram client library, for bots too!

Pure Python 3 MTProto API Telegram client library, for bots too! - GitHub - LonamiWebs/Telethon: Pure Python 3 MTProto API Telegram client library, for bots too!

github.com

import asyncio
from telethon import TelegramClient
import sqlite3
from datetime import timedelta

api_id = '앱_아이디'
api_hash = '앱_해시값'
phone = '전화번호'
dialog_name = '채팅방_이름'
db_file_path = '파일명.db'
scrap_interval_secs = 60 * 60

client = TelegramClient('session_name', api_id, api_hash)

def init_db():
    conn = sqlite3.connect(db_file_path)
    c = conn.cursor()
    sql = '''
    CREATE TABLE IF NOT EXISTS messages (
        id INTEGER PRIMARY KEY,
        message TEXT,
        created_at DATE
    )
    '''
    c.execute(sql)
    conn.commit()
    conn.close()

def insert_message(message):
    conn = sqlite3.connect(db_file_path)
    c = conn.cursor()
    sql = 'INSERT OR IGNORE INTO messages(id, message, created_at) VALUES(?,?,?)'

    created_at = message.date + timedelta(hours=9)
    data = (message.id, message.text, created_at)
    c.execute(sql, data)
    conn.commit()
    conn.close()

async def main():
    init_db()

    if not await client.is_user_authorized():
        await client.start()

    dialogs = await client.get_dialogs()
    target_dialog_id = ''
    for dialog in dialogs:
        if dialog_name == dialog.name:
            target_dialog_id = dialog.id
            break

    target_chat = await client.get_input_entity(target_dialog_id)

    while True:
        messages = await client.get_messages(target_chat.channel_id, 1000)
        for message in messages:
            insert_message(message)
        await asyncio.sleep(scrap_interval_secs)

with client:
    client.loop.run_until_complete(main())

 

https://docs.telethon.dev/en/stable/modules/client.html#telethon.client.messages.MessageMethods.get_messages

 

TelegramClient — Telethon 1.29.0 documentation

Whether the user will remain anonymous when sending messages. The sender of the anonymous messages becomes the group itself. Note Users may be able to identify the anonymous admin by its custom title, so additional care is needed when using both anonymous

docs.telethon.dev

메시지 불러오는 주기와 개수는 API참고해서 진행하는게 좋아보인다

빠른 반영이 필요하다면 sleep 주기를 줄이고 offset_id를 적용해서 작업했을 듯

 

 

웹서버 생성

파이썬으로 작업한 김에 웹서버는 플라스크로 가버리기로 결심

import sqlite3
from flask import Flask, render_template
from datetime import datetime

app = Flask(__name__)

def get_messages_by_date(date):
    conn = sqlite3.connect('test.db')
    cursor = conn.cursor()
    cursor.execute('''
        SELECT * FROM messages 
        WHERE date(created_at) = date(?)
        ORDER BY created_at DESC
    ''', (date,))
    messages = cursor.fetchall()
    conn.close()
    return messages

@app.route('/')
def index():
    return '<h1>Hello</h1>'

@app.route('/messages')
def messages():
    return messages_by_date(datetime.now().date())

@app.route('/messages/<date>')
def messages_by_date(date):
    messages = get_messages_by_date(date)
    return render_template('messages.html', messages=messages, date=date)

if __name__ == '__main__':
    app.run(host='0.0.0.0',port=5000,debug=False)

 

웹 서버 템플릿 생성

페이징 없이 그냥 다 뿌려주고 다운로드해주기로 결심

<!DOCTYPE html>
<html>
  <head>
    <title>Messages</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css" />
    <script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
    <script src="https://unpkg.com/papaparse@5.4.1/papaparse.min.js"></script>
    <style>
      .table {
        margin-top: 20px;
        width: 100%;
        border: solid 1px #f1f2f4;
        border-collapse: collapse;
        border-spacing: 0;
        font-size: 14px;
      }
      .table td {
        color: #333;
        padding: 10px;
      }
      .table tr:nth-of-type(odd) td {
        background-color: #f1f2f4;
      }
      .btn-wrap {
        display: flex;
        justify-content: space-between;
      }
      .btn-wrap button {
        font-size: 13px;
      }
    </style>
  </head>
  <body>
    <h1>Messages</h1>
    <div class="btn-wrap">
      <input type="text" id="date-picker" />
      <button id="download-btn">다운로드</button>
    </div>
    <table class="table">
      {% for message in messages %}
      <tr>
        <td>{{loop.revindex}}</td>
        <td style="white-space: pre">{{ message[1] }}</td>
        <td>{{ message[2] }}</td>
      </tr>
      {% endfor %}
    </table>
    <script>
      flatpickr("#date-picker", {
        dateFormat: "Y-m-d",
        maxDate: "today",
        defaultDate: "{{date}}",
        onChange: function (selectedDte, dateStr) {
          location.href = "/messages/" + dateStr;
        },
      });

      document.getElementById("download-btn").addEventListener("click", function () {
        downloadTableAsCSV();
      });

      function downloadTableAsCSV() {
        var tableData = [];
        var tableHeaders = [];

        // Get the table rows and headers
        var tableRows = document.querySelectorAll("table tr");

        // Extract the headers
        for (var i = 0; i < tableRows[0].cells.length; i++) {
          tableHeaders.push(tableRows[0].cells[i].innerText);
        }

        // Extract the data from each row
        for (var j = 1; j < tableRows.length; j++) {
          var rowData = [];
          for (var k = 0; k < tableRows[j].cells.length; k++) {
            rowData.push(tableRows[j].cells[k].innerText);
          }
          tableData.push(rowData);
        }

        // Convert table data to CSV format using PapaParse
        var csvData = Papa.unparse({
          fields: tableHeaders,
          data: tableData,
        });

        // Create an invisible link and trigger the download
        var filename = location.pathname.substring(10) || "recent";
        var downloadLink = document.createElement("a");
        downloadLink.setAttribute("href", "data:text/csv;charset=utf-8," + encodeURIComponent(csvData));
        downloadLink.setAttribute("download", filename + ".csv");
        downloadLink.style.display = "none";
        document.body.appendChild(downloadLink);
        downloadLink.click();
        document.body.removeChild(downloadLink);
      }
    </script>
  </body>
</html>

 

 

작업 후기

텔레그램 API는 처음 써보는데 생각보다 기능이 너무 많아서 신기함

 

그리고 챗지피티 활용해서 작업했는데 총 작업시간이 3시간정도로 예상했던 작업시간보다 훨씬 단축되서 좋았음

 

 

 

반응형