diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index df60446..17a752d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -61,7 +61,7 @@ jobs: run: | VERSION=${{ needs.versioning_pipeline.outputs.version }} echo "Version: $VERSION" - sed -i "s/__version__ = \"1.0.0\"/__version__ = \"$VERSION\"/g" setup.py + sed -i "s/version='1.0.0',/version='$VERSION',/g" setup.py - name: Install build run: python -m pip install build - name: Build dist diff --git a/README.md b/README.md index a50a2c3..e2f37ff 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ⭐️ Thanks **everyone** who has starred the project, it means a lot! -This project is to help you use [Telethon](https://docs.telethon.dev/en/stable/index.html). +This project is to help you use [Telethon](https://docs.telethon.dev/en/stable/index.html). Django-Telethon is an asyncio Python 3 MTProto library to interact with Telegram's API as a user or through a bot account (bot API alternative). @@ -31,7 +31,7 @@ Django-Telethon is a session storage implementation backend for Django ORM to us pip install django-telethon ``` -**OR** +**OR** * You can use the following command to set it up locally so that you can fix bugs or whatever and send pull requests: @@ -49,7 +49,7 @@ For better understanding, please read the: ### settings.py - + ``` python INSTALLED_APPS = [ # .... @@ -115,11 +115,11 @@ Read more (proxy, bot and etc) [Here](https://docs.telethon.dev/en/stable/basic/ from django_telethon.sessions import DjangoSession from django_telethon.models import App, ClientSession from telethon.errors import SessionPasswordNeededError - + # Use your own values from my.telegram.org API_ID = 12345 API_HASH = '0123456789abcdef0123456789abcdef' - + app, is_created = App.objects.update_or_create( api_id=API_ID, api_hash=API_HASH @@ -129,7 +129,7 @@ Read more (proxy, bot and etc) [Here](https://docs.telethon.dev/en/stable/basic/ ) telegram_client = TelegramClient(DjangoSession(client_session=cs), app.api_id, app.api_hash) telegram_client.connect() - + if not telegram_client.is_user_authorized(): phone = input('Enter your phone number: ') telegram_client.send_code_request(phone) @@ -138,7 +138,7 @@ Read more (proxy, bot and etc) [Here](https://docs.telethon.dev/en/stable/basic/ telegram_client.sign_in(phone, code) except SessionPasswordNeededError: password = input('Enter your password: ') - telegram_client.sign_in(password=password) + telegram_client.sign_in(password=password) ``` #### Doing stuffs @@ -171,17 +171,17 @@ async def handler(event): ```shell script python manage.py runtelegram ``` - + 1. go to [admin panel](http://127.0.0.1:8000/admin/) and [telegram app section](http://127.0.0.1:8000/admin/django_telethon/app/). create a new app. get data from the [your Telegram account](https://my.telegram.org/auth). 1. Request code from telegram: - + ```python import requests import json - + url = "http://127.0.0.1:8000/telegram/send-code-request/" - + payload = json.dumps({ "phone_number": "+12345678901", "client_session_name": "name of the client session" @@ -189,20 +189,20 @@ async def handler(event): headers = { 'Content-Type': 'application/json' } - + response = requests.request("POST", url, headers=headers, data=payload) - + print(response.text) ``` 1. Send this request for sign in: - + ```python import requests import json - + url = "http://127.0.0.1:8000/telegram/login-user-request/" - + payload = json.dumps({ "phone_number": "+12345678901", "client_session_name": "name of the client session", @@ -212,22 +212,22 @@ async def handler(event): headers = { 'Content-Type': 'application/json' } - + response = requests.request("POST", url, headers=headers, data=payload) - + print(response.text) ``` -#### Bot login +#### Bot login Send this request for sign in: - + ```python import requests import json - + url = "http://127.0.0.1:8000/telegram/login-bot-request/" - + payload = json.dumps({ "bot_token": "bot token", "client_session_name": "name of the client session", @@ -235,9 +235,9 @@ Send this request for sign in: headers = { 'Content-Type': 'application/json' } - + response = requests.request("POST", url, headers=headers, data=payload) - + print(response.text) ``` @@ -275,25 +275,25 @@ python manage.py runtelegram ## Listen to events -After login telegram client the signal `telegram_client_registered` is emitted. +After login telegram client the signal `telegram_client_registered` is emitted. 1. You can listen to this signal by using the following code for example put this code to your ```receivers.py``` file in app directory: - + ```python from functools import partial - + from django.dispatch import receiver from telethon import events - + from django_telethon.signals import telegram_client_registered - + async def event_handler(event, client_session): print(client_session.name, event.raw_text, sep=' | ') # if you need access to telegram client, you can use event.client # telegram_client = event.client await event.respond('!pong') - - + + @receiver(telegram_client_registered) def receiver_telegram_registered(telegram_client, client_session, *args, **kwargs): handler = partial(event_handler, client_session=client_session) @@ -301,17 +301,17 @@ After login telegram client the signal `telegram_client_registered` is emitted. handler, events.NewMessage(incoming=True, pattern='ping'), ) - + ``` 1. In the `apps.py` file, add the following code: - + ```python from django.apps import AppConfig - + class MyAppConfig(AppConfig): ... - + def ready(self): from .receivers import receiver_telegram_registered # noqa: F401 ``` diff --git a/django_telethon/admin.py b/django_telethon/admin.py index 3796753..b3fc65b 100644 --- a/django_telethon/admin.py +++ b/django_telethon/admin.py @@ -3,6 +3,7 @@ from .models import App, ClientSession, Entity, Login, SentFile, Session, UpdateState +@admin.register(App) class AppAdmin(admin.ModelAdmin): list_display = ( 'api_id', @@ -10,11 +11,13 @@ class AppAdmin(admin.ModelAdmin): ) +@admin.register(ClientSession) class ClientSessionAdmin(admin.ModelAdmin): list_display = ['id', 'name', 'login_status'] list_filter = ('login_status',) +@admin.register(Login) class LoginAdmin(admin.ModelAdmin): list_display = [ 'id', @@ -30,6 +33,7 @@ class LoginAdmin(admin.ModelAdmin): list_filter = ['created_at', 'client_session__name'] +@admin.register(Session) class SessionAdmin(admin.ModelAdmin): list_display = [ 'client_session', @@ -43,6 +47,7 @@ class SessionAdmin(admin.ModelAdmin): list_filter = ['client_session__name'] +@admin.register(Entity) class EntityAdmin(admin.ModelAdmin): list_display = [ 'id', @@ -55,9 +60,15 @@ class EntityAdmin(admin.ModelAdmin): ] raw_id_fields = ['client_session'] list_filter = ['client_session__name'] - search_fields = ('hash_value', 'username', 'phone', 'name', ) + search_fields = ( + 'hash_value', + 'username', + 'phone', + 'name', + ) +@admin.register(UpdateState) class UpdateStateAdmin(admin.ModelAdmin): list_display = [ 'id', @@ -71,6 +82,7 @@ class UpdateStateAdmin(admin.ModelAdmin): list_filter = ['client_session__name'] +@admin.register(SentFile) class SentFileAdmin(admin.ModelAdmin): list_display = [ 'id', @@ -85,10 +97,3 @@ class SentFileAdmin(admin.ModelAdmin): list_filter = ['client_session__name'] -admin.site.register(App, AppAdmin) -admin.site.register(ClientSession, ClientSessionAdmin) -admin.site.register(Login, LoginAdmin) -admin.site.register(Session, SessionAdmin) -admin.site.register(Entity, EntityAdmin) -admin.site.register(UpdateState, UpdateStateAdmin) -admin.site.register(SentFile, SentFileAdmin) diff --git a/django_telethon/default_settings.py b/django_telethon/default_settings.py index d5f873a..7f2e5a0 100644 --- a/django_telethon/default_settings.py +++ b/django_telethon/default_settings.py @@ -31,5 +31,3 @@ def get_telethon_config(key, default=None): if missing_settings: raise ValueError(f"'Telethon' {', '.join(missing_settings)} must be set if RABBITMQ_ACTIVE is True.") - - diff --git a/django_telethon/management/commands/runtelegram.py b/django_telethon/management/commands/runtelegram.py index 2016742..ac2919a 100644 --- a/django_telethon/management/commands/runtelegram.py +++ b/django_telethon/management/commands/runtelegram.py @@ -31,7 +31,6 @@ async def _entry_point(): logging.exception(e, exc_info=True) - class Command(BaseCommand): help = 'Run telegram' diff --git a/django_telethon/migrations/0001_initial.py b/django_telethon/migrations/0001_initial.py index 091e641..9226b21 100644 --- a/django_telethon/migrations/0001_initial.py +++ b/django_telethon/migrations/0001_initial.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [] @@ -14,7 +13,12 @@ class Migration(migrations.Migration): migrations.CreateModel( name='App', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name='ID' + ), + ), ('api_id', models.CharField(max_length=255, verbose_name='API ID')), ('api_hash', models.CharField(max_length=255, verbose_name='API Hash')), ], @@ -26,7 +30,12 @@ class Migration(migrations.Migration): migrations.CreateModel( name='ClientSession', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name='ID' + ), + ), ('name', models.CharField(max_length=255, verbose_name='Client Session Name')), ( 'login_status', @@ -50,7 +59,12 @@ class Migration(migrations.Migration): migrations.CreateModel( name='UpdateState', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name='ID' + ), + ), ('pts', models.IntegerField(verbose_name='pts')), ('qts', models.IntegerField(verbose_name='qts')), ('date', models.DateTimeField(verbose_name='date')), @@ -72,13 +86,30 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Login', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name='ID' + ), + ), ('have_to_send_code', models.BooleanField(default=True, verbose_name='Have to send code')), - ('bot_token', models.CharField(blank=True, max_length=255, null=True, verbose_name='Bot token')), - ('phone_number', models.CharField(blank=True, max_length=20, null=True, verbose_name='Phone number')), + ( + 'bot_token', + models.CharField(blank=True, max_length=255, null=True, verbose_name='Bot token'), + ), + ( + 'phone_number', + models.CharField(blank=True, max_length=20, null=True, verbose_name='Phone number'), + ), ('code', models.CharField(blank=True, max_length=10, null=True, verbose_name='Code')), - ('passcode', models.CharField(blank=True, max_length=100, null=True, verbose_name='Passcode')), - ('hash_code', models.CharField(blank=True, max_length=100, null=True, verbose_name='Hash code')), + ( + 'passcode', + models.CharField(blank=True, max_length=100, null=True, verbose_name='Passcode'), + ), + ( + 'hash_code', + models.CharField(blank=True, max_length=100, null=True, verbose_name='Hash code'), + ), ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), ( 'client_session', @@ -97,10 +128,18 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Entity', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name='ID' + ), + ), ('entity_id', models.BigIntegerField(verbose_name='Entity ID')), ('hash_value', models.BigIntegerField(verbose_name='Hash Value')), - ('username', models.CharField(blank=True, max_length=255, null=True, verbose_name='Username')), + ( + 'username', + models.CharField(blank=True, max_length=255, null=True, verbose_name='Username'), + ), ('phone', models.CharField(blank=True, max_length=15, null=True, verbose_name='Phone')), ('name', models.CharField(blank=True, max_length=150, null=True, verbose_name='Name')), ('date', models.DateTimeField(auto_now_add=True, verbose_name='Date')), @@ -147,10 +186,18 @@ class Migration(migrations.Migration): migrations.CreateModel( name='SentFile', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name='ID' + ), + ), ('md5_digest', models.BinaryField(verbose_name='MD5 digest')), ('file_size', models.IntegerField(verbose_name='File size')), - ('file_type', models.IntegerField(choices=[(0, 'Document'), (1, 'Photo')], verbose_name='Type')), + ( + 'file_type', + models.IntegerField(choices=[(0, 'Document'), (1, 'Photo')], verbose_name='Type'), + ), ('hash_value', models.BigIntegerField(verbose_name='Hash value')), ('file_id', models.IntegerField(verbose_name='File ID')), ( diff --git a/django_telethon/models/login.py b/django_telethon/models/login.py index 3afc0e1..5fc2b09 100644 --- a/django_telethon/models/login.py +++ b/django_telethon/models/login.py @@ -11,7 +11,8 @@ class LoginQueryset(models.QuerySet): def expired(self): return self.filter( - Q(created_at__lt=now() - timedelta(minutes=5)) | Q(client_session__login_status__in=LoginStatus.approve()) + Q(created_at__lt=now() - timedelta(minutes=5)) + | Q(client_session__login_status__in=LoginStatus.approve()) ) def have_to_send_code(self): @@ -30,7 +31,9 @@ def remove_old_logins(self): class Login(models.Model): - client_session = models.ForeignKey('ClientSession', on_delete=models.CASCADE, verbose_name=_('Client session')) + client_session = models.ForeignKey( + 'ClientSession', on_delete=models.CASCADE, verbose_name=_('Client session') + ) have_to_send_code = models.BooleanField(default=True, verbose_name=_('Have to send code')) bot_token = models.CharField( max_length=255, diff --git a/django_telethon/rabbitmq.py b/django_telethon/rabbitmq.py index fd06e44..0e0c7ac 100644 --- a/django_telethon/rabbitmq.py +++ b/django_telethon/rabbitmq.py @@ -3,7 +3,12 @@ import aio_pika import pika -from django_telethon.default_settings import RABBITMQ_URL, QUEUE_CHANNEL_NAME, QUEUE_CALLBACK_FN +from django_telethon.default_settings import ( + QUEUE_CALLBACK_FN, + QUEUE_CHANNEL_NAME, + RABBITMQ_URL, +) + _rabbit_registered = False @@ -16,9 +21,7 @@ async def connect_rabbitmq(): _rabbit_registered = True - connection = await aio_pika.connect_robust( - RABBITMQ_URL - ) + connection = await aio_pika.connect_robust(RABBITMQ_URL) # Creating a channel channel = await connection.channel() @@ -50,7 +53,8 @@ def send_to_telegra_thread(**payload): body=byte_payload, properties=pika.BasicProperties( delivery_mode=2, # Make message persistent - )) + ), + ) connection.close() except Exception as e: diff --git a/django_telethon/sessions.py b/django_telethon/sessions.py index 0fb755e..7983cf7 100644 --- a/django_telethon/sessions.py +++ b/django_telethon/sessions.py @@ -10,6 +10,7 @@ from .models import ClientSession, Entity, Session, UpdateState from .models.sentfiles import SentFileType + """ Read the following carefully before changing anything in this file. https://github.com/LonamiWebs/Telethon/blob/master/telethon/sessions.py @@ -119,7 +120,6 @@ def save(self): pass def delete(self): - """Deletes the current session file""" try: self.client_session.delete() @@ -130,7 +130,6 @@ def delete(self): @classmethod def list_sessions(cls): - """Lists all the sessions of the users who have ever connected using this client and never logged out """ @@ -179,30 +178,32 @@ def _process_entities(self, tlo): self.client_session.entity_set.bulk_create(entities_does_not_exists) def get_entity_rows_by_phone(self, phone): - - return self.client_session.entity_set.filter(phone=phone).values_list('entity_id', 'hash_value').first() + return ( + self.client_session.entity_set.filter(phone=phone).values_list('entity_id', 'hash_value').first() + ) def get_entity_rows_by_username(self, username): - queryset = list(self.client_session.entity_set.filter(username=username).order_by('-date')) if len(queryset) > 1: # If there is more than one result for the same username, evict the oldest one logging.warning('Found more than one entity with username %s', username) - self.client_session.entity_set.filter(entity_id__in=[obj.entity_id for obj in queryset[1:]]).update( - username=None - ) + self.client_session.entity_set.filter( + entity_id__in=[obj.entity_id for obj in queryset[1:]] + ).update(username=None) if not queryset: return None return queryset[0].entity_id, queryset[0].hash_value def get_entity_rows_by_name(self, name): - return self.client_session.entity_set.filter(name=name).values_list('entity_id', 'hash_value').first() def get_entity_rows_by_id(self, id, exact=True): - if exact: - return self.client_session.entity_set.filter(entity_id=id).values_list('entity_id', 'hash_value').first() + return ( + self.client_session.entity_set.filter(entity_id=id) + .values_list('entity_id', 'hash_value') + .first() + ) else: return ( self.client_session.entity_set.filter( @@ -219,7 +220,6 @@ def get_entity_rows_by_id(self, id, exact=True): # File processing def get_file(self, md5_digest, file_size, cls): - if ( row := self.client_session.sentfile_set.filter( md5_digest=md5_digest, file_size=file_size, file_type=SentFileType.from_type(cls).value @@ -231,7 +231,6 @@ def get_file(self, md5_digest, file_size, cls): return cls(row[0], row[1]) def cache_file(self, md5_digest, file_size, instance): - if not isinstance(instance, (InputDocument, InputPhoto)): raise TypeError(f'Cannot cache {type(instance)} instance') self.client_session.sentfile_set.update_or_create( diff --git a/django_telethon/signals.py b/django_telethon/signals.py index 2978e22..6775a25 100644 --- a/django_telethon/signals.py +++ b/django_telethon/signals.py @@ -1,4 +1,5 @@ import django.dispatch + # providing_args = ["telegram_client", "client_session"] telegram_client_registered = django.dispatch.Signal() diff --git a/django_telethon/urls.py b/django_telethon/urls.py index d2767ad..122b4e8 100644 --- a/django_telethon/urls.py +++ b/django_telethon/urls.py @@ -3,6 +3,7 @@ from .apps import DjangoTelethonConfig from .views import login_bot_view, login_user_view, send_code_request_view + app_name = DjangoTelethonConfig.name _urlpatterns = [ diff --git a/django_telethon/views.py b/django_telethon/views.py index 2a7b490..3098457 100644 --- a/django_telethon/views.py +++ b/django_telethon/views.py @@ -8,7 +8,7 @@ def _get_data(request): - if 'application/json' in request.META.get('CONTENT_TYPE', ''): + if 'application/json' in request.headers.get('content-type', ''): data = request.body.decode('utf-8') data = json.loads(data) else: @@ -34,7 +34,9 @@ def send_code_request_view(request): if Login.objects.filter(client_session=client_session).have_to_send_code().exists(): return JsonResponse({'error': 'Client session is already waiting for send code.'}, status=400) - login = Login.objects.create(client_session=client_session, phone_number=phone_number, have_to_send_code=True) + login = Login.objects.create( + client_session=client_session, phone_number=phone_number, have_to_send_code=True + ) return JsonResponse({'login_id': login.id}, status=200) diff --git a/setup.py b/setup.py index dc7ca88..764b6cb 100644 --- a/setup.py +++ b/setup.py @@ -2,6 +2,7 @@ from setuptools import find_packages, setup + with open(os.path.join(os.path.dirname(__file__), 'README.md'), encoding="utf-8") as readme: README = readme.read() @@ -42,12 +43,7 @@ 'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', ], - install_requires=[ - 'Django>=3.0', - 'Telethon>=1.24.0,<2', - 'aio-pika>=9.3.0', - 'pika>=1.3.2' - ], + install_requires=['Django>=3.0', 'Telethon>=1.24.0,<2', 'aio-pika>=9.3.0', 'pika>=1.3.2'], extras_require={ 'dev': [ 'flake8',