minline 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- minline-0.1.0/LICENSE +15 -0
- minline-0.1.0/PKG-INFO +21 -0
- minline-0.1.0/README.md +5 -0
- minline-0.1.0/minline/__init__.py +13 -0
- minline-0.1.0/minline/app.py +152 -0
- minline-0.1.0/minline/components/__init__.py +0 -0
- minline-0.1.0/minline/components/buttons/button.py +7 -0
- minline-0.1.0/minline/runtime/__init__.py +0 -0
- minline-0.1.0/minline/runtime/language_manager.py +26 -0
- minline-0.1.0/minline.egg-info/PKG-INFO +21 -0
- minline-0.1.0/minline.egg-info/SOURCES.txt +15 -0
- minline-0.1.0/minline.egg-info/dependency_links.txt +1 -0
- minline-0.1.0/minline.egg-info/requires.txt +2 -0
- minline-0.1.0/minline.egg-info/top_level.txt +1 -0
- minline-0.1.0/pyproject.toml +18 -0
- minline-0.1.0/setup.cfg +4 -0
- minline-0.1.0/setup.py +15 -0
minline-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Minline License (Non-Commercial)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Bakirullit
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software"), to use, copy, modify, and distribute the Software for **personal, educational, or non-commercial purposes**, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
1. **Commercial use is strictly prohibited**. This includes use in paid products, SaaS platforms, enterprise software, or any application where the software contributes to generating revenue.
|
|
8
|
+
|
|
9
|
+
2. Redistribution of modified versions must include attribution to the original author.
|
|
10
|
+
|
|
11
|
+
3. The above copyright notice and this license must be included in all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
The Software is provided "as is", without warranty of any kind, express or implied.
|
|
14
|
+
|
|
15
|
+
For commercial licensing inquiries, contact: bakirullit@gmail.com
|
minline-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: minline
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Menu-inline framework for Telegram bots
|
|
5
|
+
Author: bakirullit
|
|
6
|
+
Author-email: Bakdaulet <bakirullit@gmail.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: aiogram>=3.3
|
|
12
|
+
Requires-Dist: redis>=5.0.0
|
|
13
|
+
Dynamic: author
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
Dynamic: requires-python
|
|
16
|
+
|
|
17
|
+
# minline
|
|
18
|
+
|
|
19
|
+
**Menu-inline framework for Telegram bots**
|
|
20
|
+
|
|
21
|
+
Build Telegram inline menu systems with localized rendering, button logic separation, and extensible components.
|
minline-0.1.0/README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Minline - Custom Telegram Menu Framework
|
|
2
|
+
# Copyright (c) 2025 Bakirullit
|
|
3
|
+
# License: Minline License (Non-Commercial) – see LICENSE file for details
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from .app import MinlineApp
|
|
7
|
+
from .components.buttons.button import Button
|
|
8
|
+
from .components.activity.menu import Menu
|
|
9
|
+
from .runtime import *
|
|
10
|
+
|
|
11
|
+
__all__ = ["MinlineApp", "Menu", "Button",
|
|
12
|
+
"LanguageManager", "RedisSessionManager",
|
|
13
|
+
"render_menu", "RenderedMenu"]
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Minline - Custom Telegram Menu Framework
|
|
2
|
+
# Copyright (c) 2025 Bakirullit
|
|
3
|
+
# License: Minline License (Non-Commercial) – see LICENSE file for details
|
|
4
|
+
|
|
5
|
+
from aiogram import Bot, Dispatcher, Router
|
|
6
|
+
from aiogram.types import Message, CallbackQuery
|
|
7
|
+
from aiogram.filters import CommandStart
|
|
8
|
+
from aiogram.fsm.storage.memory import MemoryStorage
|
|
9
|
+
import asyncio
|
|
10
|
+
from typing import Callable, Awaitable
|
|
11
|
+
from .session.redis_manager import RedisSessionManager
|
|
12
|
+
from .runtime.language_manager import LanguageManager
|
|
13
|
+
from .components.buttons.button import Button # assuming your Menu and Button classes are here
|
|
14
|
+
from .components.activity.menu import Menu
|
|
15
|
+
|
|
16
|
+
class MinlineApp:
|
|
17
|
+
def __init__(self, token: str, *, language_dir: str = "languages", default_lang: str = "en"):
|
|
18
|
+
self.token = token
|
|
19
|
+
self.dp = Dispatcher(storage=MemoryStorage())
|
|
20
|
+
self.bot = Bot(token=self.token)
|
|
21
|
+
self.router = Router()
|
|
22
|
+
self.default_lang = default_lang
|
|
23
|
+
self.menu_registry = {}
|
|
24
|
+
self.action_commands: dict[str, Callable[[CallbackQuery, str], Awaitable]] = {}
|
|
25
|
+
self.lang = LanguageManager(language_dir)
|
|
26
|
+
self.session = RedisSessionManager()
|
|
27
|
+
self.router.message(CommandStart())(self._menu_handler)
|
|
28
|
+
self.router.message()(self._delete_unknown)
|
|
29
|
+
self._register_callbacks()
|
|
30
|
+
self.dp.include_router(self.router)
|
|
31
|
+
|
|
32
|
+
def action(self, name: str):
|
|
33
|
+
def decorator(func):
|
|
34
|
+
self.action_commands[name] = func
|
|
35
|
+
return func
|
|
36
|
+
return decorator
|
|
37
|
+
|
|
38
|
+
def menu(self, path: str):
|
|
39
|
+
def decorator(fn):
|
|
40
|
+
menu_instance = fn()
|
|
41
|
+
menu_instance.menu_id = path
|
|
42
|
+
if path != "/":
|
|
43
|
+
back_button = Button("back", f"#route://")
|
|
44
|
+
menu_instance.controls.insert(0, [back_button])
|
|
45
|
+
self.menu_registry[path] = menu_instance
|
|
46
|
+
return menu_instance
|
|
47
|
+
return decorator
|
|
48
|
+
|
|
49
|
+
async def _menu_handler(self, message: Message):
|
|
50
|
+
user_id = message.from_user.id
|
|
51
|
+
chat_id = message.chat.id
|
|
52
|
+
lang = self.default_lang
|
|
53
|
+
state = await self.session.get_state(user_id)
|
|
54
|
+
if state:
|
|
55
|
+
old_chat_id = state.get("chat_id")
|
|
56
|
+
old_message_id = state.get("message_id")
|
|
57
|
+
if old_chat_id and old_message_id:
|
|
58
|
+
try:
|
|
59
|
+
await self.bot.delete_message(old_chat_id, old_message_id)
|
|
60
|
+
except Exception:
|
|
61
|
+
pass
|
|
62
|
+
menu = self.menu_registry.get("/")
|
|
63
|
+
if not menu:
|
|
64
|
+
await self.bot.send_message(chat_id, "Menu '/' not found.")
|
|
65
|
+
return
|
|
66
|
+
_, _, message_id = await menu.render(chat_id, None, self.bot, lang)
|
|
67
|
+
await self.session.set_state(user_id, {
|
|
68
|
+
"chat_id": chat_id,
|
|
69
|
+
"message_id": message_id,
|
|
70
|
+
"lang": lang,
|
|
71
|
+
"menu_path": "/"
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
await message.delete()
|
|
76
|
+
except Exception:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
def _register_callbacks(self):
|
|
80
|
+
@self.router.callback_query()
|
|
81
|
+
async def callback_handler(callback: CallbackQuery):
|
|
82
|
+
user_id = callback.from_user.id
|
|
83
|
+
data = callback.data
|
|
84
|
+
if not data or ":" not in data:
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
parts = data.split(":", maxsplit=3)
|
|
88
|
+
if len(parts) < 3:
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
lang, current_path, command = parts[0], parts[1], parts[2]
|
|
92
|
+
arg = parts[3] if len(parts) > 3 else ""
|
|
93
|
+
print(f"Callback received: lang={lang}, path={current_path}, command={command}, arg={arg}", parts)
|
|
94
|
+
|
|
95
|
+
if not command.startswith("#"):
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
if (command == "#route"):
|
|
99
|
+
state = await self.session.get_state(user_id)
|
|
100
|
+
if not state:
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
chat_id = state["chat_id"]
|
|
104
|
+
message_id = state["message_id"]
|
|
105
|
+
|
|
106
|
+
if arg == "//":
|
|
107
|
+
path_parts = current_path.rstrip("/").split("/")
|
|
108
|
+
new_path = "/" if len(path_parts) <= 2 else "/".join(path_parts[:-1])
|
|
109
|
+
else:
|
|
110
|
+
new_path = current_path.rstrip("/") + "/" + arg
|
|
111
|
+
menu = self.menu_registry.get(new_path)
|
|
112
|
+
if not menu:
|
|
113
|
+
await callback.answer("Menu not found", show_alert=True)
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
_, _, new_msg_id = await menu.render(chat_id, message_id, self.bot, lang)
|
|
117
|
+
await self.session.set_state(user_id, {
|
|
118
|
+
"chat_id": chat_id,
|
|
119
|
+
"message_id": new_msg_id,
|
|
120
|
+
"lang": lang,
|
|
121
|
+
"menu_path": new_path
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
await callback.answer()
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
if command in self.action_commands:
|
|
128
|
+
await self.action_commands[command](callback, command)
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
await callback.answer("Unknown action", show_alert=True)
|
|
132
|
+
|
|
133
|
+
async def _open_menu(self, user_id: int, menu: Menu, path: str):
|
|
134
|
+
state = await self.session.get_state(user_id)
|
|
135
|
+
if not state:
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
chat_id = state["chat_id"]
|
|
139
|
+
message_id = state["message_id"]
|
|
140
|
+
lang = state.get("lang", self.default_lang)
|
|
141
|
+
menu.lang = lang
|
|
142
|
+
await menu.render(chat_id, message_id, self.bot)
|
|
143
|
+
|
|
144
|
+
state["menu_path"] = path
|
|
145
|
+
await self.session.set_state(user_id, state)
|
|
146
|
+
|
|
147
|
+
async def _delete_unknown(self, message: Message):
|
|
148
|
+
await message.delete()
|
|
149
|
+
|
|
150
|
+
def run(self):
|
|
151
|
+
asyncio.run(self.dp.start_polling(self.bot))
|
|
152
|
+
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
class LanguageManager:
|
|
5
|
+
_languages: dict[str, dict[str, str]] = {}
|
|
6
|
+
|
|
7
|
+
def __init__(self, directory: str = "languages"):
|
|
8
|
+
self.directory = directory
|
|
9
|
+
self.load_languages(directory)
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def load_languages(cls, folder_path: str = "languages"):
|
|
13
|
+
for filename in os.listdir(folder_path):
|
|
14
|
+
if filename.endswith(".json"):
|
|
15
|
+
lang_code = filename.split(".")[0]
|
|
16
|
+
full_path = os.path.join(folder_path, filename)
|
|
17
|
+
with open(full_path, "r", encoding="utf-8") as f:
|
|
18
|
+
cls._languages[lang_code] = json.load(f)
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def get(cls, lang: str, key: str) -> str:
|
|
22
|
+
return cls._languages.get(lang, {}).get(key, key)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Load on import
|
|
26
|
+
LanguageManager.load_languages()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: minline
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Menu-inline framework for Telegram bots
|
|
5
|
+
Author: bakirullit
|
|
6
|
+
Author-email: Bakdaulet <bakirullit@gmail.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: aiogram>=3.3
|
|
12
|
+
Requires-Dist: redis>=5.0.0
|
|
13
|
+
Dynamic: author
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
Dynamic: requires-python
|
|
16
|
+
|
|
17
|
+
# minline
|
|
18
|
+
|
|
19
|
+
**Menu-inline framework for Telegram bots**
|
|
20
|
+
|
|
21
|
+
Build Telegram inline menu systems with localized rendering, button logic separation, and extensible components.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
setup.py
|
|
5
|
+
minline/__init__.py
|
|
6
|
+
minline/app.py
|
|
7
|
+
minline.egg-info/PKG-INFO
|
|
8
|
+
minline.egg-info/SOURCES.txt
|
|
9
|
+
minline.egg-info/dependency_links.txt
|
|
10
|
+
minline.egg-info/requires.txt
|
|
11
|
+
minline.egg-info/top_level.txt
|
|
12
|
+
minline/components/__init__.py
|
|
13
|
+
minline/components/buttons/button.py
|
|
14
|
+
minline/runtime/__init__.py
|
|
15
|
+
minline/runtime/language_manager.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
minline
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "minline"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Menu-inline framework for Telegram bots"
|
|
5
|
+
authors = [
|
|
6
|
+
{ name="Bakdaulet", email="bakirullit@gmail.com" }
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
license = { text = "MIT" }
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"aiogram>=3.3",
|
|
13
|
+
"redis>=5.0.0"
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["setuptools", "wheel"]
|
|
18
|
+
build-backend = "setuptools.build_meta"
|
minline-0.1.0/setup.cfg
ADDED
minline-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="minline",
|
|
5
|
+
version="0.1.0",
|
|
6
|
+
description="Menu-inline framework for Telegram bots",
|
|
7
|
+
author="bakirullit",
|
|
8
|
+
packages=find_packages(),
|
|
9
|
+
include_package_data=True,
|
|
10
|
+
install_requires=[
|
|
11
|
+
"aiogram>=3.3",
|
|
12
|
+
"redis>=5.0.0"
|
|
13
|
+
],
|
|
14
|
+
python_requires=">=3.10",
|
|
15
|
+
)
|