From 6c9322d9b48da77793d9583e909df408adb550ce Mon Sep 17 00:00:00 2001 From: Sergey Degtyar Date: Fri, 26 Apr 2024 16:15:55 +0300 Subject: [PATCH 1/9] app: replaced deprecated class --- Syncogram/sourcefiles/userbar/generate.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Syncogram/sourcefiles/userbar/generate.py b/Syncogram/sourcefiles/userbar/generate.py index 49d7dc4..dfe3e9a 100644 --- a/Syncogram/sourcefiles/userbar/generate.py +++ b/Syncogram/sourcefiles/userbar/generate.py @@ -9,8 +9,9 @@ from ..database import SQLite -class UIGenerateAccounts(ft.UserControl): +class UIGenerateAccounts(ft.Container): def __init__(self, page: ft.Page, _, *args, **kwargs) -> None: + super().__init__() self.page: ft.Page = page self.database = SQLite() self._ = _ @@ -38,10 +39,10 @@ def __init__(self, page: ft.Page, _, *args, **kwargs) -> None: self.wrapper_column = ft.Column() self.wrapper_column.controls = [self.account_primary, self.account_secondary] - self.wrapper = ft.Container() - self.wrapper.content = self.wrapper_column + # self.wrapper = ft.Container() + # self.wrapper.content = self.wrapper_column - super().__init__() + self.content = self.wrapper_column def account_button(self, account_id, account_name) -> ft.ElevatedButton: button = ft.ElevatedButton() @@ -112,4 +113,4 @@ async def generate(self) -> None: self.update() def build(self) -> ft.Container: - return self.wrapper + return self From 14932a4d8e9bd96f74cbf15f095d8b2c09f81fcf Mon Sep 17 00:00:00 2001 From: Sergey Degtyar Date: Fri, 26 Apr 2024 16:21:28 +0300 Subject: [PATCH 2/9] app: stage to login by phone number --- Syncogram/sourcefiles/userbar/authenticate.py | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/Syncogram/sourcefiles/userbar/authenticate.py b/Syncogram/sourcefiles/userbar/authenticate.py index 61e7ec9..ed1f7ae 100644 --- a/Syncogram/sourcefiles/userbar/authenticate.py +++ b/Syncogram/sourcefiles/userbar/authenticate.py @@ -13,12 +13,26 @@ def __init__(self, page: ft.Page, _, *args, **kwargs) -> None: self.is_primary = args[1] self.update_mainwindow = args[2] self.password_inputed_event = Event() + self.client = UserClient() + self.qrcode_image = ft.Image("1") - self.log_phone_number = ft.TextButton() - self.log_phone_number.text = _("Use phone number") - self.log_phone_number.disabled = True + self.log_phone_number_button = ft.TextButton() + self.log_phone_number_button.text = _("Use phone number") + self.log_phone_number_button.on_click = self.phone_login_dialog + self.log_phone_number_button.disabled = True + + self.log_qrcode_button = ft.TextButton() + self.log_qrcode_button.text = _("Use QR-code") + self.log_qrcode_button.on_click = ... + self.log_qrcode_button.visible = False + + + self.phone_field = ft.TextField() + self.phone_field.keyboard_type = ft.KeyboardType.PHONE + self.phone_field.visible = False + self.password = ft.TextField() self.password.label = _("2FA password") @@ -31,7 +45,8 @@ def __init__(self, page: ft.Page, _, *args, **kwargs) -> None: self.modal = True self.content = ft.Column( - [ + [ + self.phone_field, self.qrcode_image, self.password ] @@ -41,7 +56,7 @@ def __init__(self, page: ft.Page, _, *args, **kwargs) -> None: self.content.alignment = ft.MainAxisAlignment.CENTER self.content.horizontal_alignment = ft.CrossAxisAlignment.CENTER self.title = ft.Text(_("Authorization")) - self.actions = [self.button_close, self.log_phone_number] + self.actions = [self.button_close, self.log_phone_number_button, self.log_qrcode_button] self.actions_alignment = ft.MainAxisAlignment.SPACE_BETWEEN async def close(self, e): @@ -56,19 +71,27 @@ async def submit(self, e): async def input_2fa_password(self): self.actions.append(self.button_submit) - self.log_phone_number.visible = False + self.log_phone_number_button.visible = False + # self.log_qrcode_button.visible = True self.qrcode_image.visible = False self.password.visible = True self.update() async def qr_login_dialog(self): - client = UserClient() - await client.login_by_qrcode(dialog=self, is_primary=self.is_primary) + await self.client.login_by_qrcode(dialog=self, is_primary=self.is_primary) await self.update_mainwindow() await self.update_accounts.generate() await self.update_accounts.update_async() self.update() + async def phone_login_dialog(self, e): + self.client.disconnect() + self.qrcode_image.visible = False + self.log_qrcode_button.visible = True + self.log_phone_number_button.visible = False + self.phone_field.visible = True + self.update() + async def error(self): self.password.border_color = ft.colors.RED self.password.focus() From 3b4a9101a4faec5ded7cea5f144e71229798afb6 Mon Sep 17 00:00:00 2001 From: Sergey Degtyar Date: Mon, 29 Apr 2024 15:42:35 +0300 Subject: [PATCH 3/9] tests folder for abstract tests --- Syncogram/test.py | 3 --- tests/forwards.py | 5 +++++ 2 files changed, 5 insertions(+), 3 deletions(-) delete mode 100644 Syncogram/test.py create mode 100644 tests/forwards.py diff --git a/Syncogram/test.py b/Syncogram/test.py deleted file mode 100644 index 2198e2a..0000000 --- a/Syncogram/test.py +++ /dev/null @@ -1,3 +0,0 @@ -from sourcefiles.utils import config - -print(config()["APP"]["VERSION"]) diff --git a/tests/forwards.py b/tests/forwards.py new file mode 100644 index 0000000..c2263a5 --- /dev/null +++ b/tests/forwards.py @@ -0,0 +1,5 @@ +import telethon + + +with telethon.TelegramClient as client: + client.connect \ No newline at end of file From aeaeec4b7d64b3cf5a2886592392cd91354dc65d Mon Sep 17 00:00:00 2001 From: Sergey Degtyar Date: Thu, 2 May 2024 19:42:19 +0300 Subject: [PATCH 4/9] GitHub: TODO it plz --- TODO.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..e2104c9 --- /dev/null +++ b/TODO.md @@ -0,0 +1,3 @@ +# Primary: +1. Need to be sure that Database version is newest. First of all check application version. If local config file has equavialent version from repository config AND local config hasn't equavialent version Database from repo config GO TO check. +Если локальный конфигурационный файл содержит в себе версию приложения совпадающую с файлом из удалённого репозитория **AND** локальный файл содержит в себе не совпадающую версию базы данных из удалённого репозитория, то только в этом случае нужно удалить таблицу опций пользователя и продолжить выполнение кода (пересоздать таблицу опций с новыми параметрами). From df0539a867d8223da7f38d2a22a05947051db92d Mon Sep 17 00:00:00 2001 From: Sergey Degtyar Date: Fri, 3 May 2024 16:57:45 +0300 Subject: [PATCH 5/9] app: database version --- Syncogram/config.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Syncogram/config.json b/Syncogram/config.json index e1959af..4d7af35 100644 --- a/Syncogram/config.json +++ b/Syncogram/config.json @@ -5,6 +5,9 @@ "VERSION_IS_ALPHA": "True", "VERSION_IS_BETA": "False" }, + "DATABASE": { + "VERSION": "0.0.1" + }, "GIT": { "REPO_LINK": "https://github.com/pwd491/syncogram.git", "REPO_ISSUES": "https://github.com/pwd491/syncogram/issues", From 1b7bc7d68bc031b6d211efffbce092f4e1fd1b12 Mon Sep 17 00:00:00 2001 From: Sergey Degtyar Date: Fri, 3 May 2024 20:10:47 +0300 Subject: [PATCH 6/9] todo: updated --- TODO.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/TODO.md b/TODO.md index e2104c9..a709888 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,8 @@ -# Primary: -1. Need to be sure that Database version is newest. First of all check application version. If local config file has equavialent version from repository config AND local config hasn't equavialent version Database from repo config GO TO check. -Если локальный конфигурационный файл содержит в себе версию приложения совпадающую с файлом из удалённого репозитория **AND** локальный файл содержит в себе не совпадающую версию базы данных из удалённого репозитория, то только в этом случае нужно удалить таблицу опций пользователя и продолжить выполнение кода (пересоздать таблицу опций с новыми параметрами). +### Важно: +- [x] Если локальный конфигурационный файл содержит в себе версию приложения совпадающую с файлом из удалённого репозитория **AND** локальный файл содержит в себе не совпадающую версию базы данных из удалённого репозитория, то только в этом случае нужно удалить таблицу опций пользователя и продолжить выполнение кода (пересоздать таблицу опций с новыми параметрами). +- [ ] Если **имя пользователя** не установлено, нужно предложить установить случайный или попросить самому установить в официальном приложении. Перед тем как начать выполнять задачи, нужно проверить на двустороннее существование имён пользователей для корректного нахождения сущностей аккаунтов. +- [ ] В **release.py** добавить увелечение версии базы данных, если были произведены изменения функционала. + +### Второстепенно: +- [ ] Найти решение отказаться от использования нахождения сущностей строго по **username**. Механизмы телеграмма не позволяют писать человеку по его **ID**, если **access_hash** не содержит информацию об этом чате. То есть, если общение между двумя собеседниками не было и чат между ними не существует, то отправить сообщение указывая ID аккаунта не получится. +- [ ] При попытке авторизовать уже существующий аккаунт в программе, это нужно обработать. SQL алгоритм уже готов, нужно допилить только GUI. From 33e9956f0db88f2f619474f8792291b07e7d01ba Mon Sep 17 00:00:00 2001 From: Sergey Degtyar Date: Fri, 3 May 2024 22:31:38 +0300 Subject: [PATCH 7/9] app: added constants SQL requests --- .github/workflows/release.yml | 8 - Syncogram/application.py | 2 +- Syncogram/config.json | 3 +- Syncogram/sourcefiles/database/constants.py | 127 +++++++++++ Syncogram/sourcefiles/database/consts.py | 44 ---- Syncogram/sourcefiles/database/sqlite.py | 161 ++++++++++--- Syncogram/sourcefiles/telegram/client.py | 52 ++++- Syncogram/sourcefiles/telegram/manager.py | 211 +++++++++++++----- Syncogram/sourcefiles/telegram/task.py | 11 + Syncogram/sourcefiles/userbar/authenticate.py | 1 + Syncogram/sourcefiles/userbar/generate.py | 6 +- Syncogram/sourcefiles/userbar/logout.py | 2 +- Syncogram/sourcefiles/userbar/main.py | 3 +- Syncogram/sourcefiles/userbar/settings.py | 12 +- Syncogram/sourcefiles/utils.py | 79 +++++-- Syncogram/sourcefiles/window/main.py | 3 +- TODO.md | 22 +- release.py | 22 +- tests/forwards.py | 5 - 19 files changed, 579 insertions(+), 195 deletions(-) create mode 100644 Syncogram/sourcefiles/database/constants.py delete mode 100644 Syncogram/sourcefiles/database/consts.py delete mode 100644 tests/forwards.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8448f99..913f7a0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,11 +43,7 @@ jobs: cd Syncogram flet pack application.py ` --name "Syncogram" ` -<<<<<<< HEAD --icon "assets/logo/icns/duck512x512.icns" ` -======= - --icon "assets\logo\ico\duck128x128.ico" ` ->>>>>>> dev --product-name "Syncogram Application" ` --product-version "${{ env.json_APP_VERSION }}" ` --company-name "Syncogram Application" ` @@ -57,11 +53,7 @@ jobs: --add-data assets:assets ` --add-data config.json:. ` --distpath ../craft -<<<<<<< HEAD cd ../ -======= - cd ..\ ->>>>>>> dev - name: Archive Windows Application run: | diff --git a/Syncogram/application.py b/Syncogram/application.py index aa4d632..c345b1c 100644 --- a/Syncogram/application.py +++ b/Syncogram/application.py @@ -37,7 +37,7 @@ async def application(page: ft.Page) -> None: expand=True, ) ) - newest_version(page, cfg["APP"]["VERSION"], _) + newest_version(page, _) if __name__ == "__main__": ft.app(target=application, assets_dir="assets") diff --git a/Syncogram/config.json b/Syncogram/config.json index 4d7af35..6b811d9 100644 --- a/Syncogram/config.json +++ b/Syncogram/config.json @@ -13,7 +13,8 @@ "REPO_ISSUES": "https://github.com/pwd491/syncogram/issues", "RELEASES": "https://github.com/pwd491/Syncogram/releases", "BRANCH_MAIN": "master", - "BRANCH_SECOND": "dev" + "BRANCH_SECOND": "dev", + "REMOTE_CONFIG_URL": "https://raw.githubusercontent.com/pwd491/Syncogram/master/Syncogram/config.json" }, "AUTHOR": { "NAME": "Sergey Degtyar", diff --git a/Syncogram/sourcefiles/database/constants.py b/Syncogram/sourcefiles/database/constants.py new file mode 100644 index 0000000..d822feb --- /dev/null +++ b/Syncogram/sourcefiles/database/constants.py @@ -0,0 +1,127 @@ +SQL_TABLE_USERS = \ +""" +CREATE TABLE IF NOT EXISTS users + ( + user_id INTEGER PRIMARY KEY, + is_primary INTEGER, + username VARCHAR(32), + phone VARCHAR(16), + first_name VARCHAR(64), + last_name VARCHAR(64), + restricted INTEGER, + restriction_reason TEXT, + stories_hidden INTEGER, + stories_unavailable INTEGER, + contact_require_premium INTEGER, + scam INTEGER, + fake INTEGER, + premium INTEGER, + photo INTEGER, + emoji_status TEXT, + usernames TEXT, + color TEXT, + profile_color TEXT, + session TEXT, + access_hash INTEGER + ) +""" + +SQL_TABLE_OPTIONS = \ +""" +CREATE TABLE IF NOT EXISTS options +( + user_id INTEGER PREFERENCE UNIQUE, + is_sync_fav INTEGER DEFAULT 0, + is_sync_profile_name INTEGER DEFAULT 0 +) +""" + +SQL_UPDATE_OPTIONS = \ +""" +UPDATE `options` +SET +is_sync_fav = (?), +is_sync_profile_name = (?) +FROM +( + SELECT user_id FROM users WHERE is_primary = 1 +) as users +WHERE +( + options.user_id = users.user_id +) +""" + +SQL_INSERT_OPTIONS = \ +""" +INSERT INTO options +VALUES +( +(SELECT user_id FROM users WHERE is_primary = 1), +(?), +(?) +) +""" + + +SQL_TABLE_DB_DATA = \ +""" +CREATE TABLE IF NOT EXISTS db_data (version VARCHAR) +""" + +SQL_TRIGGER_DEFAULT_OPTIONS = \ +""" +CREATE TRIGGER IF NOT EXISTS defaults_options_on_insert +AFTER INSERT ON users +BEGIN + INSERT INTO options + VALUES + ( + NEW.user_id, + 0, + 0, + 0 + ); + END; +""" + +SQL_TRIGGER_DROP_OPTIONS = \ +""" +CREATE TRIGGER IF NOT EXISTS delete_options_on_delete +BEFORE DELETE + ON users +BEGIN + DELETE FROM options + WHERE + ( + old.user_id = options.user_id + ); +END; +""" + +SQL_ADD_USER = \ +""" +INSERT INTO users +VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) +""" + +SQL_GET_USERS = """SELECT * FROM users""" +SQL_GET_USER_ID_BY_STATUS = """SELECT user_id FROM users WHERE is_primary = (?) """ +SQL_GET_USERNAME_BY_STATUS = """SELECT username FROM users WHERE is_primary = (?)""" +SQL_GET_SESSION_BY_ID = """SELECT session FROM users WHERE user_id = ?""" +SQL_GET_SESSION_BY_STATUS = """SELECT session FROM users WHERE is_primary = ?""" +SQL_DELETE_USER_BY_ID = """DELETE FROM users WHERE user_id = ?""" +SQL_DROP_OPTIONS = """DROP TABLE options""" +SQL_GET_DATABASE_VERSION = """SELECT version FROM db_data""" +SQL_INSERT_DB_VERSION = """INSERT INTO db_data VALUES (?)""" +SQL_UPDATE_DB_VERSION = """UPDATE db_data SET version = (?)""" + +SQL_GET_OPTIONS = \ +""" +SELECT * +FROM options +WHERE options.user_id = +( + SELECT user_id FROM users WHERE is_primary = 1 +) +""" diff --git a/Syncogram/sourcefiles/database/consts.py b/Syncogram/sourcefiles/database/consts.py deleted file mode 100644 index d3781f1..0000000 --- a/Syncogram/sourcefiles/database/consts.py +++ /dev/null @@ -1,44 +0,0 @@ -SQL_CREATE_USERS = """ - CREATE TABLE IF NOT EXISTS users ( - user_id INTEGER, - name TEXT NOT NULL, - is_primary BOOLEAN NOT NULL DEFAULT (0), - session TEXT, - PRIMARY KEY (user_id AUTOINCREMENT)) - """ - -SQL_CREATE_OPTIONS = """ - CREATE TABLE IF NOT EXISTS options ( - user_id INTEGER REFERENCES users (user_id) UNIQUE, - is_sync_fav INTEGER DEFAULT 0, - is_sync_pin_fav INTEGER DEFAULT 0, - is_sync_profile_name INTEGER DEFAULT 0, - PRIMARY KEY ( - user_id - ) - ) - """ - -SQL_TRIGGER_DEFAULT_OPTIONS = """ - CREATE TRIGGER IF NOT EXISTS defaults_options_on_insert - AFTER INSERT ON users - BEGIN - INSERT INTO options - VALUES ( - NEW.user_id, - 0, - 0, - 0 - ); - END; - """ - -SQL_TRIGGER_DROP_OPTIONS = """ - CREATE TRIGGER IF NOT EXISTS delete_options_on_delete - BEFORE DELETE - ON users - BEGIN - DELETE FROM options - WHERE (old.user_id = options.user_id); - END; - """ \ No newline at end of file diff --git a/Syncogram/sourcefiles/database/sqlite.py b/Syncogram/sourcefiles/database/sqlite.py index add00ca..ef3f3e3 100644 --- a/Syncogram/sourcefiles/database/sqlite.py +++ b/Syncogram/sourcefiles/database/sqlite.py @@ -1,13 +1,32 @@ -import os, sys +"""Database module of Syncogram application.""" + +import os +import sys import sqlite3 + from contextlib import closing from typing import Any -from .consts import ( - SQL_CREATE_USERS, - SQL_CREATE_OPTIONS, - SQL_TRIGGER_DROP_OPTIONS, - SQL_TRIGGER_DEFAULT_OPTIONS +from ..utils import check_db_version +from .constants import ( + SQL_TABLE_USERS, + SQL_TABLE_OPTIONS, + SQL_TABLE_DB_DATA, + SQL_ADD_USER, + SQL_GET_USERS, + SQL_GET_OPTIONS, + SQL_GET_SESSION_BY_ID, + SQL_GET_SESSION_BY_STATUS, + SQL_GET_USER_ID_BY_STATUS, + SQL_GET_USERNAME_BY_STATUS, + SQL_DELETE_USER_BY_ID, + SQL_INSERT_OPTIONS, + SQL_UPDATE_OPTIONS, + SQL_DROP_OPTIONS, + SQL_GET_DATABASE_VERSION, + SQL_INSERT_DB_VERSION, + SQL_UPDATE_DB_VERSION, + SQL_TRIGGER_DROP_OPTIONS ) WORK_DIR = "" @@ -23,64 +42,132 @@ if not os.path.exists(WORK_DIR) or not os.path.isdir(WORK_DIR): os.mkdir(WORK_DIR) -DB_PATH = os.path.join(WORK_DIR, "test.db") +DB_NAME = "syncogram" +DB_EXTENSION = ".sqlite3" +DB_FILE = os.path.join(WORK_DIR, DB_NAME + DB_EXTENSION) + class SQLite: + """The main class of Database.""" + def __init__(self) -> None: self.database: sqlite3.Connection = sqlite3.connect( - DB_PATH, check_same_thread=False + DB_FILE, check_same_thread=False ) - self.database.cursor().execute(SQL_CREATE_USERS).close() - self.database.cursor().execute(SQL_CREATE_OPTIONS).close() + self.database.cursor().execute(SQL_TABLE_DB_DATA).close() + self.database.cursor().execute(SQL_TABLE_USERS).close() + self.database.cursor().execute(SQL_TABLE_OPTIONS).close() self.database.cursor().execute(SQL_TRIGGER_DROP_OPTIONS).close() - self.database.cursor().execute(SQL_TRIGGER_DEFAULT_OPTIONS).close() + + def add_user(self, *args) -> bool | int: + """Get user data and save to database.""" + with self.database as connect: + with closing(connect.cursor()) as cursor: + try: + request = cursor.execute(SQL_ADD_USER, (*args,)) + return bool(request) + except sqlite3.IntegrityError as error: + return error.sqlite_errorcode def get_users(self) -> list[Any]: + """Get all users.""" with self.database as connect: with closing(connect.cursor()) as cursor: - return cursor.execute("SELECT * FROM users").fetchall() - - def get_user_by_id(self, account_id): + return cursor.execute(SQL_GET_USERS).fetchall() + + def get_session_by_id(self, account_id) -> list[int]: + """Get user by id""" with self.database as connect: with closing(connect.cursor()) as cursor: - return cursor.execute("SELECT session FROM users WHERE user_id = ?", (account_id,)).fetchone() + return cursor.execute( + SQL_GET_SESSION_BY_ID, (account_id,) + ).fetchone()[0] - def get_user_by_status(self, is_primary: int): + def get_user_id_by_status(self, status: int) -> list[str]: + """Get user by id""" with self.database as connect: with closing(connect.cursor()) as cursor: - return cursor.execute("SELECT session FROM users WHERE is_primary = ?", (is_primary,)).fetchone() + return cursor.execute( + SQL_GET_USER_ID_BY_STATUS, (status,) + ).fetchone()[0] - def add_user(self, user_id: int, name: str, is_primary: int, session: str) -> bool: + def get_session_by_status(self, is_primary: int) -> list[str]: + """Get user by status (sender or recepient).""" with self.database as connect: with closing(connect.cursor()) as cursor: - request = cursor.execute( - "INSERT INTO `users` VALUES (?,?,?,?)", - ( - user_id, - name, - is_primary, - session, - ), - ) - return bool(request) - - def delete_user_by_id(self, account_id): + return cursor.execute( + SQL_GET_SESSION_BY_STATUS, (is_primary,) + ).fetchone()[0] + + def get_username_by_status(self, is_primary: int) -> str: + """Get username by status (sender or recepient).""" + with self.database as connect: + with closing(connect.cursor()) as cursor: + return cursor.execute( + SQL_GET_USERNAME_BY_STATUS, (is_primary,) + ).fetchone()[0] + + def delete_user_by_id(self, account_id) -> None: + """Delete user by id.""" with self.database as connect: with closing(connect.cursor()) as cursor: - return cursor.execute("DELETE FROM users WHERE user_id = ?", (account_id,)) + return cursor.execute( + SQL_DELETE_USER_BY_ID, (account_id,) + ) - def get_options(self): + def get_options(self) -> list[int]: + """Get options values only for primary account (sender).""" with self.database as connect: with closing(connect.cursor()) as cursor: return cursor.execute( - "SELECT * FROM options WHERE options.user_id = (SELECT user_id FROM users WHERE is_primary = 1)" + SQL_GET_OPTIONS ).fetchone() def set_options(self, *args) -> bool: + """Set options values only for primary account (sender).""" with self.database as connect: with closing(connect.cursor()) as cursor: - request = cursor.execute( - "UPDATE `options` SET is_sync_fav = (?), is_sync_pin_fav = (?), is_sync_profile_name = (?) FROM (SELECT user_id FROM users WHERE is_primary = 1) as users WHERE (options.user_id = users.user_id)", - (*args,), - ) + user = bool(self.get_options()) + if user: + request = cursor.execute(SQL_UPDATE_OPTIONS, (*args,)) + else: + user = self.get_user_id_by_status(1) + if user: + request = cursor.execute(SQL_INSERT_OPTIONS, (*args,)) + else: + return False return bool(request) + + def get_version(self) -> str | None: + """Get database version from db_data table.""" + with self.database as connect: + with closing(connect.cursor()) as cursor: + request = cursor.execute(SQL_GET_DATABASE_VERSION).fetchone() + if bool(request): + return request[0] + return None + + def set_version(self, version) -> None: + """Set database version if not exists.""" + with self.database as connect: + with closing(connect.cursor()) as cursor: + cursor.execute(SQL_INSERT_DB_VERSION, (version,)) + + def update_version(self, version) -> None: + """Update database version if exists newest.""" + with self.database as connect: + with closing(connect.cursor()) as cursor: + cursor.execute(SQL_UPDATE_DB_VERSION, (version,)) + + def check_update(self): + """Check and update database options.""" + current_db_version = self.get_version() + if current_db_version is not None: + need_to_update = check_db_version(current_db_version) + if isinstance(need_to_update, tuple): + self.database.cursor().execute(SQL_DROP_OPTIONS).close() + self.database.cursor().execute(SQL_TABLE_OPTIONS).close() + self.update_version(need_to_update[1]) + else: + from ..utils import get_local_database_version + self.set_version(get_local_database_version()) diff --git a/Syncogram/sourcefiles/telegram/client.py b/Syncogram/sourcefiles/telegram/client.py index 4480e54..3ef0550 100644 --- a/Syncogram/sourcefiles/telegram/client.py +++ b/Syncogram/sourcefiles/telegram/client.py @@ -5,11 +5,19 @@ from telethon.sessions import StringSession from telethon.tl.custom.qrlogin import QRLogin from telethon.tl.types import InputPeerUser, User -from telethon.errors import SessionPasswordNeededError, PasswordHashInvalidError +from telethon.errors import ( + SessionPasswordNeededError, + PasswordHashInvalidError, + UsernameNotModifiedError, + UsernameInvalidError, + UsernameOccupiedError + ) +from telethon import functions from ..database import SQLite from ..utils import config from ..utils import generate_qrcode +from ..utils import generate_username from .environments import API_ID, API_HASH cfg = config() @@ -54,13 +62,47 @@ async def login_by_qrcode(self, dialog, is_primary): dialog.open = False dialog.update() user: User | InputPeerUser = await self.get_me() - self.database.add_user( - user.id, # type: ignore - user.first_name, # type: ignore + if user.username is None: + while True: + try: + username = generate_username() + await self(functions.account.UpdateUsernameRequest( + username + )) + user.username = username + break + except ( + UsernameNotModifiedError, + UsernameInvalidError, + UsernameOccupiedError + ): + continue + + response = self.database.add_user( + user.id, is_primary, - self.session.save(), # type: ignore + user.username, + user.phone, + user.first_name, + user.last_name, + int(user.restricted), + str(user.restriction_reason), + int(user.stories_hidden), + int(user.stories_unavailable), + int(user.contact_require_premium), + int(user.scam), + int(user.fake), + int(user.premium), + user.photo.photo_id if user.photo is not None else None, + str(user.emoji_status), + str(user.usernames), + user.color, + str(user.profile_color), + self.session.save(), + user.access_hash ) self.disconnect() + return response async def logout(self): if not self.is_connected(): diff --git a/Syncogram/sourcefiles/telegram/manager.py b/Syncogram/sourcefiles/telegram/manager.py index 327de9f..91f2719 100644 --- a/Syncogram/sourcefiles/telegram/manager.py +++ b/Syncogram/sourcefiles/telegram/manager.py @@ -1,46 +1,48 @@ -from asyncio import sleep +import asyncio import flet as ft from telethon.tl.functions.account import UpdateProfileRequest from telethon.tl.functions.users import GetFullUserRequest -from telethon.tl.types import UserFull +from telethon.tl.patched import MessageService +from telethon.tl.types import UserFull, PeerUser, Message from .client import UserClient from .task import CustomTask from ..database import SQLite from ..userbar.settings import SettingsDialog + class Manager: - def __init__(self, page: ft.Page, _, mainwindow = None) -> None: + def __init__(self, page: ft.Page, _, mainwindow=None) -> None: self.page: ft.Page = page self.database = SQLite() self.mainwindow = mainwindow self.client = UserClient - + self.options = { "is_sync_fav": { "title": _("Sync my favorite messages between accounts."), "function": self.sync_favorite_messages, "status": bool(), - "ui_task_object": CustomTask - }, - "is_sync_pin_fav": { - "title": _("Synchronize the sequence of pinned messages in your favorite messages."), - "function": self.sync_sequence_of_pinned_messages, - "status": bool(), - "ui_task_object": CustomTask + "ui_task_object": CustomTask, }, "is_sync_profile_name": { - "title": _("Synchronize the first name, last name and biography of the profile."), + "title": _( + "Synchronize the first name, last name and biography of the profile." + ), "function": self.sync_profile_first_name_and_second_name, "status": bool(), - "ui_task_object": CustomTask - } + "ui_task_object": CustomTask, + }, } async def build(self): - list_of_options = self.database.get_options()[1:] - + + list_of_options = self.database.get_options() + if list_of_options is None: + return + list_of_options = list_of_options[1:] + for n, option in enumerate(self.options.items()): option[1].update({"status": bool(list_of_options[n])}) @@ -48,63 +50,170 @@ async def build(self): if option[1].get("status"): title = option[1].get("title") task = CustomTask(title) - option[1].update( - { - "ui_task_object": task - } - ) + option[1].update({"ui_task_object": task}) self.mainwindow.wrapper_side_column.controls.append(task) self.mainwindow.update() - async def sync_favorite_messages(self, ui_task_object: CustomTask): - """ - Важно учитывать: - a. Последовательность отвеченных сообщений - b. Контекст сообщения, для навигации используют хештег - c. Статус о закреплении сообщения в диалоге - - Важно итерировать диалог, таким образом получится достичь максимума - информации для каждого сообщения. Стоит коолекционировать данные, для - следующих функций, где мы могли бы переиспользовать эти данные. - """ - pass + # ui_task_object.progress.value = None + # ui_task_object.progress.update() + + sender = self.client(self.database.get_session_by_status(1)) + recepient = self.client(self.database.get_session_by_status(0)) + + sender_username = self.database.get_username_by_status(1) + recepient_username = self.database.get_username_by_status(0) + + if not (sender.is_connected() and recepient.is_connected()): + await sender.connect() + await recepient.connect() + + sender_entity = await recepient.get_input_entity(sender_username) + recepient_entity = await sender.get_input_entity(recepient_username) - async def sync_sequence_of_pinned_messages(self, ui_task_object: CustomTask): - await sleep(3) - ui_task_object.progress.value = 1 - ui_task_object.header.controls.pop(-1) - ui_task_object.header.controls.append(ft.Icon(ft.icons.TASK_ALT, color=ft.colors.GREEN)) - ui_task_object.border = ft.Border = ft.border.all(0.5, ft.colors.GREEN) - ui_task_object.update() + # Getting messages from sender source + source_messages = sender.iter_messages( + sender_entity, min_id=0, max_id=0, reverse=True + ) + is_grouped_id = None + is_pinned = False + is_replied = False + group = [] + msg_ids = {} + + async def recepient_save_message( + message_id, message_length, is_pin: bool, is_reply: None | Message + ): + data = await recepient.get_messages(sender_entity, limit=message_length) + messages = [message for message in data] + if is_reply: + destination_message_id = msg_ids.get(is_reply.reply_to_msg_id) + if messages[0].media: + await asyncio.sleep(3) + message = await recepient.send_message( + recepient_entity, + messages[-1].message, + file=messages, + reply_to=destination_message_id, + ) + msg_ids[message_id] = message[0].id + else: + await asyncio.sleep(3) + message = await recepient.send_message( + recepient_entity, + message=messages[0], + reply_to=destination_message_id, + ) + msg_ids[message_id] = message.id + else: + message = await recepient.forward_messages(recepient_entity, messages) + msg_ids[message_id] = message[0].id + + if is_pin: + await asyncio.sleep(3) + await recepient.pin_message(recepient_entity, message[0]) + await asyncio.sleep(3) + await recepient.delete_messages(sender_entity, messages) + + try: + ui_task_object.progress_counters.visible = True + i = 0 + async for message in source_messages: + i += 1 + ui_task_object.total.value = source_messages.total + ui_task_object.progress.value = i / source_messages.total + ui_task_object.value.value = i + ui_task_object.update() + # await asyncio.sleep(0.00001) + if not isinstance(message, MessageService): + if message.grouped_id is not None: + if message.pinned: + is_pinned = True + if message.reply_to: + is_replied = message.reply_to + if is_grouped_id != message.grouped_id: + is_grouped_id = message.grouped_id + if group: + await asyncio.sleep(3) + await sender.forward_messages( + recepient_entity, group, silent=True + ) + await recepient_save_message( + message.id, len(group), is_pinned, is_replied + ) + is_pinned = False + is_replied = False + group.clear() + group.append(message) + continue + if group: + await asyncio.sleep(3) + await sender.forward_messages( + recepient_entity, group, silent=True + ) + await recepient_save_message( + message.id, len(group), is_pinned, is_replied + ) + is_pinned = False + is_replied = False + group.clear() + + await asyncio.sleep(3) + await sender.forward_messages( + recepient_entity, message, silent=True + ) + await recepient_save_message( + message.id, 1, message.pinned, message.reply_to + ) + + if group: + await asyncio.sleep(3) + await sender.forward_messages(recepient_entity, group, silent=True) + await recepient_save_message( + group[-1].id, len(group), is_pinned, is_replied + ) + is_pinned = False + is_replied = False + group.clear() + except Exception as e: + return ui_task_object.unsuccess(e) + finally: + del msg_ids + sender.disconnect() + recepient.disconnect() + + ui_task_object.success() async def sync_profile_first_name_and_second_name(self, ui_task_object: CustomTask): + """ + Connecting accounts, getting profile data and sets. + """ ui_task_object.progress.value = None ui_task_object.progress.update() - self.sender = self.client(*self.database.get_user_by_status(1)) - self.recepient = self.client(*self.database.get_user_by_status(0)) + sender = self.client(self.database.get_session_by_status(1)) + recepient = self.client(self.database.get_session_by_status(0)) - if not (self.sender.is_connected() and self.recepient.is_connected()): - await self.sender.connect() - await self.recepient.connect() + if not (sender.is_connected() and recepient.is_connected()): + await sender.connect() + await recepient.connect() try: - user: UserFull = await self.sender(GetFullUserRequest("me")) + user: UserFull = await sender(GetFullUserRequest("me")) first_name = user.users[0].first_name first_name = "" if first_name is None else first_name last_name = user.users[0].last_name last_name = "" if last_name is None else last_name bio = user.full_user.about bio = "" if bio is None else bio - - await self.recepient(UpdateProfileRequest(first_name, last_name, bio)) + + await recepient(UpdateProfileRequest(first_name, last_name, bio)) except Exception as e: return ui_task_object.unsuccess(e) - self.sender.disconnect() - self.recepient.disconnect() + sender.disconnect() + recepient.disconnect() ui_task_object.success() async def start_all_tasks(self, btn, _): @@ -114,7 +223,7 @@ async def start_all_tasks(self, btn, _): settings.open = True btn.state = False return self.page.update() - + for option in self.options.items(): if option[1].get("status"): func = option[1].get("function") diff --git a/Syncogram/sourcefiles/telegram/task.py b/Syncogram/sourcefiles/telegram/task.py index d95786e..fcba172 100644 --- a/Syncogram/sourcefiles/telegram/task.py +++ b/Syncogram/sourcefiles/telegram/task.py @@ -12,6 +12,16 @@ def __init__(self, title: str) -> None: self.progress = ft.ProgressBar() self.progress.value = 0 + self.value = ft.Text() + self.value.value = 0 + self.total = ft.Text() + self.total.value = 0 + + self.progress_counters = ft.Row() + self.progress_counters.controls = [self.value, self.total] + self.progress_counters.alignment = ft.MainAxisAlignment.SPACE_BETWEEN + self.progress_counters.visible = False + self.header = ft.Row() self.header.controls = [self.title, ft.Icon(ft.icons.UPDATE, color=ft.colors.ORANGE_500)] self.header.alignment = ft.MainAxisAlignment.SPACE_BETWEEN @@ -20,6 +30,7 @@ def __init__(self, title: str) -> None: self.wrapper = ft.Column([ self.header, ft.Divider(opacity=0), + self.progress_counters, self.progress ]) diff --git a/Syncogram/sourcefiles/userbar/authenticate.py b/Syncogram/sourcefiles/userbar/authenticate.py index ed1f7ae..a5e314e 100644 --- a/Syncogram/sourcefiles/userbar/authenticate.py +++ b/Syncogram/sourcefiles/userbar/authenticate.py @@ -79,6 +79,7 @@ async def input_2fa_password(self): async def qr_login_dialog(self): await self.client.login_by_qrcode(dialog=self, is_primary=self.is_primary) + # Need to except 1555 error UNIQUE ID PRIMARY KEY (user exists.) await self.update_mainwindow() await self.update_accounts.generate() await self.update_accounts.update_async() diff --git a/Syncogram/sourcefiles/userbar/generate.py b/Syncogram/sourcefiles/userbar/generate.py index dfe3e9a..e0e056b 100644 --- a/Syncogram/sourcefiles/userbar/generate.py +++ b/Syncogram/sourcefiles/userbar/generate.py @@ -102,13 +102,13 @@ async def generate(self) -> None: while len(self.account_secondary.controls) > 3: self.account_secondary.controls.pop(-2) for account in accounts: - if bool(account[2]): + if bool(account[1]): self.account_primary.controls.insert( - -1, self.account_button(account[0], account[1]) + -1, self.account_button(account[0], account[4][0:16]) ) else: self.account_secondary.controls.insert( - -1, self.account_button(account[0], account[1]) + -1, self.account_button(account[0], account[4][0:16]) ) self.update() diff --git a/Syncogram/sourcefiles/userbar/logout.py b/Syncogram/sourcefiles/userbar/logout.py index b46dcf9..b8c38fa 100644 --- a/Syncogram/sourcefiles/userbar/logout.py +++ b/Syncogram/sourcefiles/userbar/logout.py @@ -22,7 +22,7 @@ def __init__(self, account_id, _, *args) -> None: self.actions_alignment = ft.MainAxisAlignment.CENTER async def submit(self, e) -> None: - session = self.database.get_user_by_id(self.account_id) + session = self.database.get_session_by_id(self.account_id) client = UserClient(*session) if await client.logout(): self.database.delete_user_by_id(self.account_id) diff --git a/Syncogram/sourcefiles/userbar/main.py b/Syncogram/sourcefiles/userbar/main.py index a0d1104..1289850 100644 --- a/Syncogram/sourcefiles/userbar/main.py +++ b/Syncogram/sourcefiles/userbar/main.py @@ -22,7 +22,6 @@ def __init__(self, page: ft.Page, update_mainwin, _) -> None: self.settings_btn.text = _("Settings") self.settings_btn.icon = ft.icons.SETTINGS self.settings_btn.expand = True - # self.settings_btn.width = 200 self.settings_btn.height = 45 self.settings_btn.on_click = self.settings @@ -64,7 +63,7 @@ async def settings(self, e) -> None: settings = SettingsDialog(self.update_mainwin, self._) self.page.dialog = settings settings.open = True - await self.page.update_async() + self.page.update() async def generate_accounts_callback(self): await self.generate_accounts.generate() diff --git a/Syncogram/sourcefiles/userbar/settings.py b/Syncogram/sourcefiles/userbar/settings.py index c63bc53..64e2297 100644 --- a/Syncogram/sourcefiles/userbar/settings.py +++ b/Syncogram/sourcefiles/userbar/settings.py @@ -15,18 +15,12 @@ def __init__(self, update_mainwin, _) -> None: self.c1 = ft.Checkbox( label=_("Sync my favorite messages"), value=bool(self.options[0]), - disabled=True, + disabled=False, tooltip=_("It will be available in the next updates") ) self.c2 = ft.Checkbox( - label=_("Save the sequence of pinned messages"), - value=bool(self.options[1]), - disabled=True, - tooltip=_("It will be available in the next updates") - ) - self.c3 = ft.Checkbox( label=_("Sync my profile first name, last name and biography."), - value=bool(self.options[2]), + value=bool(self.options[1]), disabled=False ) """!!!""" @@ -34,7 +28,6 @@ def __init__(self, update_mainwin, _) -> None: x = [ self.c1, self.c2, - self.c3, ] x.sort(key=lambda x: x.disabled == True) @@ -63,7 +56,6 @@ async def save(self, e) -> None: self.database.set_options( int(self.c1.value), # type: ignore int(self.c2.value), # type: ignore - int(self.c3.value) # type: ignore ) await self.update_mainwin() self.open = False diff --git a/Syncogram/sourcefiles/utils.py b/Syncogram/sourcefiles/utils.py index 13e4c75..07e8567 100644 --- a/Syncogram/sourcefiles/utils.py +++ b/Syncogram/sourcefiles/utils.py @@ -1,6 +1,8 @@ import os import base64 import json +import random +import string from json import loads from requests import request from io import BytesIO @@ -9,16 +11,6 @@ import flet as ft import qrcode - -def config(): - dir = os.path.dirname(os.path.dirname(__file__)) - cfg = os.path.join(dir, "config.json") - - if os.path.isfile(cfg): - with open(cfg, "r", encoding="utf-8") as cfg: - return json.load(cfg) - - def generate_qrcode(url): buffered = BytesIO() QRcode = qrcode.QRCode( @@ -31,14 +23,66 @@ def generate_qrcode(url): img.save(buffered, format="PNG") return base64.b64encode(buffered.getvalue()).decode("utf-8") -def newest_version(page: ft.Page, __version__, _) -> None: - __newest__ = loads( + +def generate_username(): + """Generate random username.""" + letters = string.ascii_letters + string.digits + return \ + ''.join(random.choice(letters) for _ in range(random.randint(5, 32))) + + +if __name__ == '__main__': + generate_username() + +def config(): + dir = os.path.dirname(os.path.dirname(__file__)) + cfg = os.path.join(dir, "config.json") + + if os.path.isfile(cfg): + with open(cfg, "r", encoding="utf-8") as cfg: + return json.load(cfg) + +def get_remote_application_version(): + return \ + loads( + request( + "GET", + config()["GIT"]["REMOTE_CONFIG_URL"], + timeout=15 + ).text + )["APP"]["VERSION"] + +def get_local_appication_version(): + return config()["APP"]["VERSION"] + +def get_local_database_version(): + return config()["DATABASE"]["VERSION"] + +def get_remote_database_version(): + return \ + loads( request( "GET", - "https://raw.githubusercontent.com/pwd491/Syncogram/dev/Syncogram/config.json", + config()["GIT"]["REMOTE_CONFIG_URL"], timeout=15 - ).text - )["APP"]["VERSION"] + ).text + )["DATABASE"]["VERSION"] + + +def check_db_version(__version__) -> tuple | None: + remote_app_version = get_remote_application_version() + local_app_version = get_local_appication_version() + remote_db_version = get_remote_database_version() + local_db_version = __version__ + + if local_db_version != remote_db_version and \ + local_app_version == remote_app_version: + return (True, remote_db_version) + + +def newest_version(page: ft.Page, _) -> None: + __version__ = get_local_appication_version() + __newest__ = get_remote_application_version() if __version__ != __newest__: icon = ft.Icon() icon.name = ft.icons.BROWSER_UPDATED @@ -60,7 +104,4 @@ def newest_version(page: ft.Page, __version__, _) -> None: snack.bgcolor = ft.colors.BLACK87 page.snack_bar = snack page.snack_bar.open = True - page.update() - - - + page.update() \ No newline at end of file diff --git a/Syncogram/sourcefiles/window/main.py b/Syncogram/sourcefiles/window/main.py index 8cf34fe..fb51172 100644 --- a/Syncogram/sourcefiles/window/main.py +++ b/Syncogram/sourcefiles/window/main.py @@ -58,6 +58,7 @@ def __init__(self, page: ft.Page, _) -> None: super().__init__() self.page = page self.database = SQLite() + self.database.check_update() self.manager = Manager(self.page, _, self) self.button_start = ButtonStartTasks(self.manager, _) @@ -103,7 +104,7 @@ def __init__(self, page: ft.Page, _) -> None: self.border_radius = ft.BorderRadius(10, 10, 10, 10) self.padding = 20 - + async def callback_update(self) -> None: users = self.database.get_users() self.wrapper.controls.clear() diff --git a/TODO.md b/TODO.md index a709888..4700bf2 100644 --- a/TODO.md +++ b/TODO.md @@ -1,8 +1,28 @@ ### Важно: - [x] Если локальный конфигурационный файл содержит в себе версию приложения совпадающую с файлом из удалённого репозитория **AND** локальный файл содержит в себе не совпадающую версию базы данных из удалённого репозитория, то только в этом случае нужно удалить таблицу опций пользователя и продолжить выполнение кода (пересоздать таблицу опций с новыми параметрами). - [ ] Если **имя пользователя** не установлено, нужно предложить установить случайный или попросить самому установить в официальном приложении. Перед тем как начать выполнять задачи, нужно проверить на двустороннее существование имён пользователей для корректного нахождения сущностей аккаунтов. -- [ ] В **release.py** добавить увелечение версии базы данных, если были произведены изменения функционала. + - [x] Скрыто устанавливать имя пользователя (временное решение). +- [x] В **release.py** добавить увелечение версии базы данных, если были произведены изменения функционала. +- [x] Правильная индикация прогресса при выполнении задачи. +- [ ] Переписать синхронизацию избранных сообщений включая следующие механизмы: + - [ ] Пересылать за раз 100 сообщений (максимальное кол-во допустимое тг) + - [ ] Сохранять последовательность + - [ ] Сохранять последовательность отвеченных сообщений в этом же чате. + - [ ] Подумать над коллизеей сообщений, убедиться что группированные сообщения входят допустимое значение =< 100. Максимальное количество группированных сообщений = 10, значит надо тут ещё подумать... + ### Второстепенно: - [ ] Найти решение отказаться от использования нахождения сущностей строго по **username**. Механизмы телеграмма не позволяют писать человеку по его **ID**, если **access_hash** не содержит информацию об этом чате. То есть, если общение между двумя собеседниками не было и чат между ними не существует, то отправить сообщение указывая ID аккаунта не получится. - [ ] При попытке авторизовать уже существующий аккаунт в программе, это нужно обработать. SQL алгоритм уже готов, нужно допилить только GUI. +- [ ] Добавить авторизацию по номеру телефона, болван! +- [ ] Отображать пользовательский аватар вместо иконки. +- [ ] Проверять актуальность данных пользователя при запуске программы (если пользователь авторизовался в программе). + + +### Будущие функции: +- [x] Синхронизация имени, фамилии и описания. +- [x] Синхронизация избранных сообщений. **50/50** +- [ ] Синхронизация пользовательских фотографий. +- [ ] Синхронизация настроек конфиденциальности. +- [ ] Синхронизация публичных каналов и групп. +- [ ] **HARD** Синхронизация закрытых каналов и групп (с автоматическим поиском/парсером ссылок приглашения). diff --git a/release.py b/release.py index 40f76fd..7a0d8ec 100644 --- a/release.py +++ b/release.py @@ -11,12 +11,22 @@ with open("Syncogram/config.json", "r", encoding="utf-8") as f: data = json.load(f) - CURRENT_VERSION = data["APP"]["VERSION"] + CURRENT_APP_VERSION = data["APP"]["VERSION"] + CURRENT_DB_VERSION = data["DATABASE"]["VERSION"] -NEW_VERSION = str(input(f"Specify the new version ({CURRENT_VERSION}): ")) +NEW_APP_VERSION = str(input(f"What is new application version ({CURRENT_APP_VERSION}): ")) print("---------------------") -print(f"New version: {NEW_VERSION}") -data["APP"]["VERSION"] = NEW_VERSION +print(f"New application version: {NEW_APP_VERSION}") + +NEW_DB_VERSION = str(input(f"What is new Database version ({CURRENT_DB_VERSION}): ")) +if NEW_DB_VERSION == "": + NEW_DB_VERSION = CURRENT_DB_VERSION + print(f"Stay on: {NEW_APP_VERSION}") +else: + print(f"New database version: {NEW_DB_VERSION}") + +data["DATABASE"]["VERSION"] = NEW_DB_VERSION +data["APP"]["VERSION"] = NEW_APP_VERSION print("---------------------") choice = input("Start merge? yes/no: ").lower() @@ -26,13 +36,13 @@ def merge(): json.dump(data, f, ensure_ascii=False, indent=4) os.system("git add .") time.sleep(1) - os.system(f"""git commit -am "Version {NEW_VERSION}" """) + os.system(f"""git commit -am "Version {NEW_APP_VERSION}" """) time.sleep(1) os.system("git push") time.sleep(3) os.system("git checkout master") os.system("git add .") - os.system(f"""git commit -am "Version {NEW_VERSION}" """) + os.system(f"""git commit -am "Version {NEW_APP_VERSION}" """) os.system("git push") time.sleep(3) os.system("git merge dev") diff --git a/tests/forwards.py b/tests/forwards.py deleted file mode 100644 index c2263a5..0000000 --- a/tests/forwards.py +++ /dev/null @@ -1,5 +0,0 @@ -import telethon - - -with telethon.TelegramClient as client: - client.connect \ No newline at end of file From 2c631bfc112fc9306c8260d94cdfac19e065ab52 Mon Sep 17 00:00:00 2001 From: Sergey Degtyar Date: Fri, 3 May 2024 22:38:10 +0300 Subject: [PATCH 8/9] app: auto generate current date like last version --- release.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/release.py b/release.py index 7a0d8ec..6974383 100644 --- a/release.py +++ b/release.py @@ -2,6 +2,7 @@ import sys import json import time +import datetime yes = {"yes", "y", "ye", ""} no = {"no", "n"} @@ -14,14 +15,18 @@ CURRENT_APP_VERSION = data["APP"]["VERSION"] CURRENT_DB_VERSION = data["DATABASE"]["VERSION"] -NEW_APP_VERSION = str(input(f"What is new application version ({CURRENT_APP_VERSION}): ")) +now = datetime.datetime.strftime(datetime.datetime.now(), "%Y.%d.%m") +NEW_APP_VERSION = str(input(f"What is new application version ({CURRENT_APP_VERSION}) -> ({now}): ")) + +if NEW_APP_VERSION == "": + NEW_APP_VERSION = now print("---------------------") print(f"New application version: {NEW_APP_VERSION}") NEW_DB_VERSION = str(input(f"What is new Database version ({CURRENT_DB_VERSION}): ")) if NEW_DB_VERSION == "": NEW_DB_VERSION = CURRENT_DB_VERSION - print(f"Stay on: {NEW_APP_VERSION}") + print(f"Stay on: {NEW_DB_VERSION}") else: print(f"New database version: {NEW_DB_VERSION}") From 13280201bfae3de43de8d55d20265dd989ac69da Mon Sep 17 00:00:00 2001 From: Sergey Degtyar Date: Fri, 3 May 2024 22:38:39 +0300 Subject: [PATCH 9/9] Version 2024.03.05 --- Syncogram/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Syncogram/config.json b/Syncogram/config.json index 6b811d9..561927b 100644 --- a/Syncogram/config.json +++ b/Syncogram/config.json @@ -1,7 +1,7 @@ { "APP": { "NAME": "Syncogram", - "VERSION": "2024.20.04", + "VERSION": "2024.03.05", "VERSION_IS_ALPHA": "True", "VERSION_IS_BETA": "False" },