diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..03059b5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,40 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '[BUG] ' +labels: bug +assignees: '' +--- + +## Bug Description + + +## Steps To Reproduce +1. +2. +3. +4. + +## Expected Behavior + + +## Actual Behavior + + +## Screenshots + + +## Environment +- OS: [e.g. macOS 14.0, Windows 11, Ubuntu 22.04] +- Python Version: [e.g. 3.10.5] +- Claude Desktop Version: [e.g. 1.0.3] +- Telegram MCP Version: [e.g. 1.5.0] + +## Additional Context + + +## Error Logs + +``` +Paste your logs here +``` \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..eac63a0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,22 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '[FEATURE] ' +labels: enhancement +assignees: '' +--- + +## Problem Statement + + +## Proposed Solution + + +## Use Cases + + +## Alternatives Considered + + +## Additional Context + \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..4c5c2e9 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,29 @@ +## Description + + + +## Type of Change + + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Documentation update +- [ ] Refactoring (no functional changes) + +## How Has This Been Tested? + + +- [ ] Automated tests +- [ ] Manual testing + +## Checklist + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published in downstream modules \ No newline at end of file diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 0000000..93059c6 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,68 @@ +name: Publish Release + +on: + push: + tags: + - 'v*.*.*' + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Install build dependencies + run: | + python -m pip install --upgrade pip + pip install build wheel + + - name: Build package + run: | + python -m build + + - name: Get version from tag + id: get_version + run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT + + - name: Generate changelog + id: generate_changelog + run: | + git log --pretty=format:"* %s" $(git describe --tags --abbrev=0 HEAD^)..HEAD > CHANGELOG.txt + echo "CHANGELOG<> $GITHUB_OUTPUT + cat CHANGELOG.txt >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Create Release + id: create_release + uses: softprops/action-gh-release@v1 + with: + name: Release ${{ steps.get_version.outputs.VERSION }} + body: | + # Telegram MCP v${{ steps.get_version.outputs.VERSION }} + + ## Changes in this release: + ${{ steps.generate_changelog.outputs.CHANGELOG }} + + ## Installation + + ```bash + git clone https://github.com/${{ github.repository }}.git + cd telegram-mcp-server + pip install -r requirements.txt + ``` + + See the [README](README.md) for complete documentation. + draft: false + prerelease: false + files: | + dist/* + LICENSE + README.md \ No newline at end of file diff --git a/.github/workflows/python-lint.yml b/.github/workflows/python-lint.yml new file mode 100644 index 0000000..f1a3323 --- /dev/null +++ b/.github/workflows/python-lint.yml @@ -0,0 +1,32 @@ +name: Python Lint + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Install linting tools + run: | + python -m pip install --upgrade pip + pip install ruff black + + - name: Lint with ruff + run: | + ruff check . + + - name: Check formatting with black + run: | + black --check --diff . \ No newline at end of file diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml new file mode 100644 index 0000000..e555ea5 --- /dev/null +++ b/.github/workflows/python-tests.yml @@ -0,0 +1,41 @@ +name: Python Tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install uv + run: | + pip install uv + + - name: Install dependencies + run: | + uv pip install --system dotenv>=0.9.9 httpx>=0.28.1 "mcp[cli]>=1.4.1" nest-asyncio>=1.6.0 python-dotenv>=1.1.0 telethon>=1.39.0 pytest pytest-asyncio + + - name: Create mock env file + run: | + echo "TELEGRAM_API_ID=123456" > .env + echo "TELEGRAM_API_HASH=0123456789abcdef0123456789abcdef" >> .env + echo "TELEGRAM_SESSION_NAME=test_session" >> .env + echo "TELEGRAM_SESSION_STRING=" >> .env + + - name: Run tests with pytest + run: | + pytest -xvs tests/ \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 025e726..01cfd51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,26 @@ +[build-system] +requires = ["setuptools>=42", "wheel"] +build-backend = "setuptools.build_meta" + [project] name = "telegram-mcp" -version = "2025.3.201549" -description = "A Telegram MCP (Model Context Protocol) server built using Python, Telethon, and MCP Python SDK. This MCP server provides simple tools for interacting with Telegram chats directly through MCP-compatible hosts, such as Claude for Desktop." +version = "1.5.0" +description = "Telegram integration for Claude via the Model Context Protocol" readme = "README.md" -requires-python = ">=3.13" +authors = [ + {name = "l1v0n1", email = "your.email@example.com"} +] +license = {text = "Apache-2.0"} +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +requires-python = ">=3.10" dependencies = [ "dotenv>=0.9.9", "httpx>=0.28.1", @@ -12,3 +29,23 @@ dependencies = [ "python-dotenv>=1.1.0", "telethon>=1.39.0", ] + +[project.urls] +"Homepage" = "https://github.com/l1v0n1/telegram-mcp-server" +"Bug Tracker" = "https://github.com/l1v0n1/telegram-mcp-server/issues" + +[tool.black] +line-length = 100 +target-version = ["py310", "py311", "py312"] + +[tool.ruff] +target-version = "py310" +line-length = 100 +select = ["E", "F", "B"] +ignore = [] + +[tool.pytest.ini_options] +minversion = "6.0" +addopts = "--verbose" +testpaths = ["tests"] +python_files = "test_*.py" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b9ab6bc --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +dotenv>=0.9.9 +httpx>=0.28.1 +mcp[cli]>=1.4.1 +nest-asyncio>=1.6.0 +python-dotenv>=1.1.0 +telethon>=1.39.0 \ No newline at end of file diff --git a/tests/test_basic.py b/tests/test_basic.py new file mode 100644 index 0000000..38fead6 --- /dev/null +++ b/tests/test_basic.py @@ -0,0 +1,83 @@ +import os +import sys +import pytest +from unittest.mock import patch, MagicMock + +# Add the root directory to the path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +# Mock the telethon client for testing +@pytest.fixture +def mock_client(): + with patch('telethon.TelegramClient') as mock: + # Create a mock instance of TelegramClient + client_instance = MagicMock() + mock.return_value = client_instance + + # Mock basic methods + client_instance.get_dialogs.return_value = [] + client_instance.get_entity.return_value = MagicMock() + + yield client_instance + +# Test functions +def test_imports(): + """Test that all necessary imports are available""" + # This will fail if any import is missing + import main + from mcp.server.fastmcp import FastMCP + from telethon import TelegramClient + from telethon.sessions import StringSession + +@pytest.mark.asyncio +async def test_format_entity(): + """Test the format_entity function with different entity types""" + from main import format_entity + + # Test user entity + user = MagicMock() + user.id = 123 + user.first_name = "John" + user.last_name = "Doe" + user.username = "johndoe" + user.phone = "+1234567890" + + user_result = format_entity(user) + assert user_result["id"] == 123 + assert user_result["name"] == "John Doe" + assert user_result["type"] == "user" + assert user_result["username"] == "johndoe" + assert user_result["phone"] == "+1234567890" + + # Test group entity + from telethon.tl.types import Chat + group = MagicMock(spec=Chat) + group.id = 456 + group.title = "Test Group" + + group_result = format_entity(group) + assert group_result["id"] == 456 + assert group_result["name"] == "Test Group" + assert group_result["type"] == "group" + +@pytest.mark.asyncio +async def test_format_message(): + """Test the format_message function""" + from main import format_message + from datetime import datetime + + # Create a mock message + message = MagicMock() + message.id = 789 + message.date = datetime(2023, 1, 1, 12, 0, 0) + message.message = "Hello, world!" + message.from_id = None + message.media = None + + result = format_message(message) + assert result["id"] == 789 + assert "2023-01-01" in result["date"] + assert result["text"] == "Hello, world!" + assert "has_media" not in result + +# More tests can be added as the project grows \ No newline at end of file