feory 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.
feory-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.4
2
+ Name: feory
3
+ Version: 0.1.0
4
+ Summary: Universal Python SDK for Feory Android App - Dynamic UI Builder & Bot Control Bridge
5
+ Home-page: https://github.com/zizo0/feory
6
+ Author: zizo0
7
+ Author-email: zizo0 <zly30257@gmail.com>
8
+ Project-URL: Homepage, https://github.com/zizo0/feory
9
+ Project-URL: Bug Tracker, https://github.com/zizo0/feory/issues
10
+ Keywords: feory,android,bot,ui-builder,automation,telegram,bridge
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Topic :: Software Development :: User Interfaces
15
+ Classifier: Topic :: Communications
16
+ Requires-Python: >=3.8
17
+ Description-Content-Type: text/markdown
18
+ Dynamic: author
19
+ Dynamic: home-page
20
+ Dynamic: requires-python
21
+
22
+ # Feory Python SDK ⚡
23
+
24
+ Universal Python SDK & Bridge for the **Feory Android App**.
25
+ Create dynamic Bento-style Android UIs, manage bots, handle bi-directional events, and control servers remotely from your mobile device.
26
+
27
+ ---
28
+
29
+ ## 🚀 Installation
30
+
31
+ ```bash
32
+ pip install feory
33
+ ```
34
+
35
+ ---
36
+
37
+ ## 🛠️ Quick Start
38
+
39
+ ```python
40
+ import feory
41
+
42
+ # 1. Initialize with your bot token from the Feory App
43
+ client = feory.Client(
44
+ token="feory_tok_core_829a1b",
45
+ bot_name="My Awesome Python Bot",
46
+ port=8765
47
+ )
48
+
49
+ # 2. Register Dynamic UI Widgets
50
+ client.register_button(
51
+ id="btn_start_scrape",
52
+ label="Start Data Pipeline",
53
+ category="general",
54
+ color_scheme="LILAC",
55
+ icon="play"
56
+ )
57
+
58
+ client.register_slider(
59
+ id="slider_threads",
60
+ label="Worker Threads",
61
+ min_value=1,
62
+ max_value=32,
63
+ default_value=8
64
+ )
65
+
66
+ client.register_metric_card(
67
+ id="stat_processed",
68
+ title="Items Processed",
69
+ value="12,450",
70
+ badge="+14% today",
71
+ accent_color="GREEN"
72
+ )
73
+
74
+ # 3. Handle Events from Feory Android App
75
+ @client.on_action("btn_start_scrape")
76
+ def handle_scrape(payload):
77
+ client.log("Starting scraping tasks...", "INFO")
78
+ # Your automation logic here
79
+ return {"status": "started", "items": 100}
80
+
81
+ @client.on_value_change("slider_threads")
82
+ def handle_thread_change(new_val):
83
+ client.log(f"Thread pool updated to: {new_val}", "INFO")
84
+
85
+ # 4. Keep your bot server running
86
+ if __name__ == "__main__":
87
+ client.run_forever()
88
+ ```
89
+
90
+ ---
91
+
92
+ ## 📖 Features
93
+
94
+ - **Dynamic Android UI Generation**: Inject buttons, sliders, toggles, text inputs, dropdowns, and Bento metric cards on the fly without touching Kotlin code.
95
+ - **Two-Way Event Bus**: Receive instant button clicks and slider callbacks directly in your Python functions.
96
+ - **Real-Time Telemetry & Terminal Logs**: Stream live console logs directly to the Feory App terminal.
97
+ - **Resource Lock & Collision Prevention**: Coordinate multiple bots accessing the same channels or resources with built-in Mutex locks.
98
+ - **Zero-Dependency Core**: Uses Python standard library by default for maximum portability.
99
+
100
+ ---
101
+
102
+ ## 🔒 Authentication
103
+
104
+ Every bot connects securely using its unique **Feory Token** (`feory_tok_...`). You can create, manage, and copy tokens directly inside the **Tokens** screen of the Feory Android App.
105
+
106
+ ---
107
+
108
+ ## 👤 Author
109
+
110
+ Developed by **zizo0**
111
+ Email: `zly30257@gmail.com`
feory-0.1.0/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # Feory Python SDK ⚡
2
+
3
+ Universal Python SDK & Bridge for the **Feory Android App**.
4
+ Create dynamic Bento-style Android UIs, manage bots, handle bi-directional events, and control servers remotely from your mobile device.
5
+
6
+ ---
7
+
8
+ ## 🚀 Installation
9
+
10
+ ```bash
11
+ pip install feory
12
+ ```
13
+
14
+ ---
15
+
16
+ ## 🛠️ Quick Start
17
+
18
+ ```python
19
+ import feory
20
+
21
+ # 1. Initialize with your bot token from the Feory App
22
+ client = feory.Client(
23
+ token="feory_tok_core_829a1b",
24
+ bot_name="My Awesome Python Bot",
25
+ port=8765
26
+ )
27
+
28
+ # 2. Register Dynamic UI Widgets
29
+ client.register_button(
30
+ id="btn_start_scrape",
31
+ label="Start Data Pipeline",
32
+ category="general",
33
+ color_scheme="LILAC",
34
+ icon="play"
35
+ )
36
+
37
+ client.register_slider(
38
+ id="slider_threads",
39
+ label="Worker Threads",
40
+ min_value=1,
41
+ max_value=32,
42
+ default_value=8
43
+ )
44
+
45
+ client.register_metric_card(
46
+ id="stat_processed",
47
+ title="Items Processed",
48
+ value="12,450",
49
+ badge="+14% today",
50
+ accent_color="GREEN"
51
+ )
52
+
53
+ # 3. Handle Events from Feory Android App
54
+ @client.on_action("btn_start_scrape")
55
+ def handle_scrape(payload):
56
+ client.log("Starting scraping tasks...", "INFO")
57
+ # Your automation logic here
58
+ return {"status": "started", "items": 100}
59
+
60
+ @client.on_value_change("slider_threads")
61
+ def handle_thread_change(new_val):
62
+ client.log(f"Thread pool updated to: {new_val}", "INFO")
63
+
64
+ # 4. Keep your bot server running
65
+ if __name__ == "__main__":
66
+ client.run_forever()
67
+ ```
68
+
69
+ ---
70
+
71
+ ## 📖 Features
72
+
73
+ - **Dynamic Android UI Generation**: Inject buttons, sliders, toggles, text inputs, dropdowns, and Bento metric cards on the fly without touching Kotlin code.
74
+ - **Two-Way Event Bus**: Receive instant button clicks and slider callbacks directly in your Python functions.
75
+ - **Real-Time Telemetry & Terminal Logs**: Stream live console logs directly to the Feory App terminal.
76
+ - **Resource Lock & Collision Prevention**: Coordinate multiple bots accessing the same channels or resources with built-in Mutex locks.
77
+ - **Zero-Dependency Core**: Uses Python standard library by default for maximum portability.
78
+
79
+ ---
80
+
81
+ ## 🔒 Authentication
82
+
83
+ Every bot connects securely using its unique **Feory Token** (`feory_tok_...`). You can create, manage, and copy tokens directly inside the **Tokens** screen of the Feory Android App.
84
+
85
+ ---
86
+
87
+ ## 👤 Author
88
+
89
+ Developed by **zizo0**
90
+ Email: `zly30257@gmail.com`
@@ -0,0 +1,47 @@
1
+ """
2
+ Feory Python SDK - Universal Bridge for Android Dynamic UI & Bot Control
3
+ Author: zizo0
4
+ """
5
+
6
+ from .client import Client, FeoryClient
7
+ from .widgets import (
8
+ Widget,
9
+ ButtonWidget,
10
+ SliderWidget,
11
+ ToggleWidget,
12
+ TextFieldWidget,
13
+ MetricWidget,
14
+ DropdownWidget,
15
+ CategorySection,
16
+ WidgetType,
17
+ )
18
+ from .server import FeoryServer
19
+ from .exceptions import (
20
+ FeoryError,
21
+ FeoryAuthError,
22
+ FeoryConnectionError,
23
+ FeoryLockError,
24
+ FeorySchemaError,
25
+ )
26
+
27
+ __version__ = "0.1.0"
28
+ __author__ = "zizo0"
29
+ __all__ = [
30
+ "Client",
31
+ "FeoryClient",
32
+ "Widget",
33
+ "ButtonWidget",
34
+ "SliderWidget",
35
+ "ToggleWidget",
36
+ "TextFieldWidget",
37
+ "MetricWidget",
38
+ "DropdownWidget",
39
+ "CategorySection",
40
+ "WidgetType",
41
+ "FeoryServer",
42
+ "FeoryError",
43
+ "FeoryAuthError",
44
+ "FeoryConnectionError",
45
+ "FeoryLockError",
46
+ "FeorySchemaError",
47
+ ]
@@ -0,0 +1,313 @@
1
+ """
2
+ Feory Python SDK Client - Main Interface
3
+ Author: zizo0
4
+ """
5
+
6
+ import json
7
+ import time
8
+ from typing import Callable, Dict, Any, List, Optional, Union
9
+ from .widgets import (
10
+ Widget,
11
+ ButtonWidget,
12
+ SliderWidget,
13
+ ToggleWidget,
14
+ TextFieldWidget,
15
+ MetricWidget,
16
+ DropdownWidget,
17
+ CategorySection,
18
+ WidgetType,
19
+ )
20
+ from .server import FeoryServer
21
+ from .exceptions import FeoryAuthError, FeoryLockError, FeorySchemaError
22
+
23
+ class Client:
24
+ """
25
+ Feory Universal Client.
26
+ Connects your Python bot or server to Feory Android App.
27
+ """
28
+ def __init__(
29
+ self,
30
+ token: str,
31
+ bot_name: str = "Universal Python Bot",
32
+ category: str = "Automation",
33
+ description: str = "Universal Python Bot instance running on local server",
34
+ host: str = "0.0.0.0",
35
+ port: int = 8765,
36
+ auto_start_server: bool = True,
37
+ ):
38
+ if not token:
39
+ raise FeoryAuthError("A valid token must be provided to initialize Feory Client.")
40
+
41
+ self.token = token
42
+ self.bot_name = bot_name
43
+ self.category = category
44
+ self.description = description
45
+ self.status = "ONLINE"
46
+
47
+ self.categories: Dict[str, CategorySection] = {
48
+ "general": CategorySection(id="general", title="التحكم العام", icon="tune"),
49
+ "metrics": CategorySection(id="metrics", title="الإحصائيات والأداء", icon="trending_up"),
50
+ "settings": CategorySection(id="settings", title="الإعدادات والتخصيص", icon="settings"),
51
+ }
52
+ self.widgets: Dict[str, Widget] = {}
53
+ self.action_handlers: Dict[str, Callable] = {}
54
+ self.value_handlers: Dict[str, Callable] = {}
55
+ self.active_locks: Dict[str, str] = {}
56
+
57
+ # Initialize embedded server
58
+ self.server = FeoryServer(client=self, host=host, port=port)
59
+ if auto_start_server:
60
+ self.server.start()
61
+
62
+ # --- UI Registration Methods ---
63
+
64
+ def register_category(self, id: str, title: str, description: Optional[str] = None, icon: Optional[str] = None) -> 'Client':
65
+ """Registers a custom category group."""
66
+ self.categories[id] = CategorySection(id=id, title=title, description=description, icon=icon)
67
+ return self
68
+
69
+ def register_button(
70
+ self,
71
+ id: str,
72
+ label: str,
73
+ category: str = "general",
74
+ action_id: Optional[str] = None,
75
+ color_scheme: str = "LILAC",
76
+ variant: str = "FILLED",
77
+ icon: Optional[str] = "play",
78
+ description: Optional[str] = None,
79
+ handler: Optional[Callable] = None,
80
+ ) -> ButtonWidget:
81
+ """Registers an interactive button in the Feory Android UI."""
82
+ btn = ButtonWidget(
83
+ id=id,
84
+ label=label,
85
+ category=category,
86
+ action_id=action_id or id,
87
+ color_scheme=color_scheme,
88
+ variant=variant,
89
+ icon=icon,
90
+ description=description,
91
+ )
92
+ self.widgets[id] = btn
93
+ if handler:
94
+ self.action_handlers[action_id or id] = handler
95
+ return btn
96
+
97
+ def register_slider(
98
+ self,
99
+ id: str,
100
+ label: str,
101
+ category: str = "general",
102
+ min_value: float = 0.0,
103
+ max_value: float = 100.0,
104
+ default_value: float = 50.0,
105
+ step: Optional[float] = 1.0,
106
+ unit: Optional[str] = None,
107
+ description: Optional[str] = None,
108
+ handler: Optional[Callable] = None,
109
+ ) -> SliderWidget:
110
+ """Registers a dynamic range slider widget."""
111
+ slider = SliderWidget(
112
+ id=id,
113
+ label=label,
114
+ category=category,
115
+ min_value=min_value,
116
+ max_value=max_value,
117
+ current_value=default_value,
118
+ step=step,
119
+ unit=unit,
120
+ description=description,
121
+ )
122
+ self.widgets[id] = slider
123
+ if handler:
124
+ self.value_handlers[id] = handler
125
+ return slider
126
+
127
+ def register_toggle(
128
+ self,
129
+ id: str,
130
+ label: str,
131
+ category: str = "general",
132
+ default_state: bool = False,
133
+ description: Optional[str] = None,
134
+ handler: Optional[Callable] = None,
135
+ ) -> ToggleWidget:
136
+ """Registers a switch toggle widget."""
137
+ toggle = ToggleWidget(
138
+ id=id,
139
+ label=label,
140
+ category=category,
141
+ is_checked=default_state,
142
+ description=description,
143
+ )
144
+ self.widgets[id] = toggle
145
+ if handler:
146
+ self.value_handlers[id] = handler
147
+ return toggle
148
+
149
+ def register_text_input(
150
+ self,
151
+ id: str,
152
+ label: str,
153
+ placeholder: str = "Enter value...",
154
+ default_value: str = "",
155
+ category: str = "general",
156
+ is_secret: bool = False,
157
+ description: Optional[str] = None,
158
+ handler: Optional[Callable] = None,
159
+ ) -> TextFieldWidget:
160
+ """Registers a text input field widget."""
161
+ field = TextFieldWidget(
162
+ id=id,
163
+ label=label,
164
+ category=category,
165
+ placeholder=placeholder,
166
+ current_value=default_value,
167
+ is_secret=is_secret,
168
+ description=description,
169
+ )
170
+ self.widgets[id] = field
171
+ if handler:
172
+ self.value_handlers[id] = handler
173
+ return field
174
+
175
+ def register_metric_card(
176
+ self,
177
+ id: str,
178
+ title: str,
179
+ value: Union[str, int, float],
180
+ subtitle: Optional[str] = None,
181
+ badge: Optional[str] = None,
182
+ category: str = "metrics",
183
+ accent_color: str = "LILAC",
184
+ icon: Optional[str] = "trending_up",
185
+ ) -> MetricWidget:
186
+ """Registers a styled Bento Stat / Metric card."""
187
+ metric = MetricWidget(
188
+ id=id,
189
+ title=title,
190
+ value=value,
191
+ subtitle=subtitle,
192
+ badge=badge,
193
+ category=category,
194
+ accent_color=accent_color,
195
+ icon=icon,
196
+ )
197
+ self.widgets[id] = metric
198
+ return metric
199
+
200
+ def register_dropdown(
201
+ self,
202
+ id: str,
203
+ label: str,
204
+ options: List[str],
205
+ default_option: Optional[str] = None,
206
+ category: str = "general",
207
+ description: Optional[str] = None,
208
+ handler: Optional[Callable] = None,
209
+ ) -> DropdownWidget:
210
+ """Registers a selection dropdown widget."""
211
+ dropdown = DropdownWidget(
212
+ id=id,
213
+ label=label,
214
+ options=options,
215
+ selected_option=default_option,
216
+ category=category,
217
+ description=description,
218
+ )
219
+ self.widgets[id] = dropdown
220
+ if handler:
221
+ self.value_handlers[id] = handler
222
+ return dropdown
223
+
224
+ # --- Decorators for Event Handlers ---
225
+
226
+ def on_action(self, action_id: str):
227
+ """Decorator to bind a Python function to a button click / action event."""
228
+ def decorator(func: Callable):
229
+ self.action_handlers[action_id] = func
230
+ return func
231
+ return decorator
232
+
233
+ def on_value_change(self, widget_id: str):
234
+ """Decorator to bind a Python function to a slider, toggle, or input field change."""
235
+ def decorator(func: Callable):
236
+ self.value_handlers[widget_id] = func
237
+ return func
238
+ return decorator
239
+
240
+ # --- State and Realtime Methods ---
241
+
242
+ def set_status(self, status: str):
243
+ """Updates the bot's status (ONLINE, IDLE, BUSY, OFFLINE)."""
244
+ self.status = status
245
+ self.log(f"[*] Bot status updated to: {status}", "INFO")
246
+
247
+ def log(self, message: str, level: str = "INFO"):
248
+ """Sends a log line to the Feory App Terminal."""
249
+ timestamp_str = time.strftime("%H:%M:%S")
250
+ print(f"[{timestamp_str}] [{level}] {message}")
251
+
252
+ def update_widget(self, widget_id: str, **kwargs):
253
+ """Updates properties of an existing widget dynamically."""
254
+ if widget_id in self.widgets:
255
+ widget = self.widgets[widget_id]
256
+ for key, val in kwargs.items():
257
+ if hasattr(widget, key):
258
+ setattr(widget, key, val)
259
+
260
+ def acquire_lock(self, resource_key: str) -> bool:
261
+ """Acquires a resource lock to prevent collision with other bots."""
262
+ if resource_key in self.active_locks:
263
+ return False
264
+ self.active_locks[resource_key] = self.bot_name
265
+ self.log(f"[✓] Mutex lock acquired for '{resource_key}'", "INFO")
266
+ return True
267
+
268
+ def release_lock(self, resource_key: str):
269
+ """Releases a previously acquired mutex lock."""
270
+ if resource_key in self.active_locks:
271
+ del self.active_locks[resource_key]
272
+ self.log(f"[✓] Mutex lock released for '{resource_key}'", "INFO")
273
+
274
+ # --- Internal Dispatchers ---
275
+
276
+ def _dispatch_action(self, action_id: str, widget_id: str, payload: Dict[str, Any]):
277
+ handler = self.action_handlers.get(action_id) or self.action_handlers.get(widget_id)
278
+ if handler:
279
+ try:
280
+ self.log(f"[▶] Action invoked from App: {action_id}", "INFO")
281
+ return handler(payload)
282
+ except Exception as e:
283
+ self.log(f"[!] Error in action handler '{action_id}': {e}", "ERROR")
284
+ return {"error": str(e)}
285
+ return {"status": "unhandled"}
286
+
287
+ def get_schema_dict(self) -> Dict[str, Any]:
288
+ """Exports the full dynamic UI schema as JSON-serializable dictionary."""
289
+ return {
290
+ "version": "1.0.0",
291
+ "botName": self.bot_name,
292
+ "category": self.category,
293
+ "status": self.status,
294
+ "categories": [c.to_dict() for c in self.categories.values()],
295
+ "widgets": [w.to_dict() for w in self.widgets.values()],
296
+ }
297
+
298
+ def export_schema_json(self) -> str:
299
+ """Returns the schema as JSON string."""
300
+ return json.dumps(self.get_schema_dict(), ensure_ascii=False, indent=2)
301
+
302
+ def run_forever(self):
303
+ """Blocks main thread to keep bot service alive."""
304
+ self.log("[*] Feory Bot running. Press Ctrl+C to terminate.", "INFO")
305
+ try:
306
+ while True:
307
+ time.sleep(1)
308
+ except KeyboardInterrupt:
309
+ self.log("[*] Shutting down Feory Bot...", "INFO")
310
+ self.server.stop()
311
+
312
+ # Alias
313
+ FeoryClient = Client
@@ -0,0 +1,23 @@
1
+ """
2
+ Feory Custom Exception Classes
3
+ """
4
+
5
+ class FeoryError(Exception):
6
+ """Base exception for Feory SDK."""
7
+ pass
8
+
9
+ class FeoryAuthError(FeoryError):
10
+ """Raised when authentication with token fails."""
11
+ pass
12
+
13
+ class FeoryConnectionError(FeoryError):
14
+ """Raised when connection to Feory App fails."""
15
+ pass
16
+
17
+ class FeoryLockError(FeoryError):
18
+ """Raised when resource lock acquisition fails due to collision."""
19
+ pass
20
+
21
+ class FeorySchemaError(FeoryError):
22
+ """Raised when UI schema registration contains invalid structure."""
23
+ pass
@@ -0,0 +1,127 @@
1
+ """
2
+ Feory Lightweight Local Server Bridge
3
+ Zero-dependency HTTP JSON-RPC / REST bridge for Feory Android App
4
+ """
5
+
6
+ import json
7
+ import threading
8
+ import time
9
+ from http.server import HTTPServer, BaseHTTPRequestHandler
10
+ from typing import Callable, Dict, Any, Optional
11
+
12
+ class FeoryHTTPHandler(BaseHTTPRequestHandler):
13
+ client_instance = None
14
+
15
+ def _set_headers(self, status=200, content_type="application/json"):
16
+ self.send_response(status)
17
+ self.send_header("Content-Type", content_type)
18
+ self.send_header("Access-Control-Allow-Origin", "*")
19
+ self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
20
+ self.send_header("Access-Control-Allow-Headers", "Content-Type, Authorization")
21
+ self.end_headers()
22
+
23
+ def do_OPTIONS(self):
24
+ self._set_headers(204)
25
+
26
+ def do_GET(self):
27
+ if self.path == "/health" or self.path == "/":
28
+ self._set_headers(200)
29
+ res = {
30
+ "status": "ONLINE",
31
+ "service": "Feory Universal Bot Server",
32
+ "version": "0.1.0",
33
+ "timestamp": time.time(),
34
+ }
35
+ self.wfile.write(json.dumps(res).encode("utf-8"))
36
+ return
37
+
38
+ if self.path == "/schema":
39
+ self._set_headers(200)
40
+ schema = self.client_instance.get_schema_dict() if self.client_instance else {}
41
+ self.wfile.write(json.dumps(schema).encode("utf-8"))
42
+ return
43
+
44
+ self._set_headers(404)
45
+ self.wfile.write(json.dumps({"error": "Endpoint not found"}).encode("utf-8"))
46
+
47
+ def do_POST(self):
48
+ content_length = int(self.headers.get("Content-Length", 0))
49
+ post_data = self.rfile.read(content_length)
50
+
51
+ try:
52
+ payload = json.loads(post_data.decode("utf-8")) if post_data else {}
53
+ except Exception as e:
54
+ self._set_headers(400)
55
+ self.wfile.write(json.dumps({"error": f"Invalid JSON payload: {e}"}).encode("utf-8"))
56
+ return
57
+
58
+ # Authenticate Token
59
+ auth_header = self.headers.get("Authorization", "")
60
+ token = auth_header.replace("Bearer ", "").strip()
61
+ if not token and "token" in payload:
62
+ token = payload.get("token")
63
+
64
+ if self.client_instance and self.client_instance.token:
65
+ if token != self.client_instance.token:
66
+ self._set_headers(401)
67
+ self.wfile.write(json.dumps({"error": "Unauthorized: Invalid Bot Token"}).encode("utf-8"))
68
+ return
69
+
70
+ # Route Endpoints
71
+ if self.path == "/action" or self.path == "/events":
72
+ action_id = payload.get("actionId") or payload.get("action")
73
+ widget_id = payload.get("widgetId", "")
74
+ args = payload.get("payload", {})
75
+
76
+ if self.client_instance:
77
+ result = self.client_instance._dispatch_action(action_id, widget_id, args)
78
+ self._set_headers(200)
79
+ self.wfile.write(json.dumps({"success": True, "actionId": action_id, "result": result}).encode("utf-8"))
80
+ return
81
+
82
+ if self.path == "/handshake":
83
+ self._set_headers(200)
84
+ info = {
85
+ "success": True,
86
+ "botName": self.client_instance.bot_name if self.client_instance else "FeoryBot",
87
+ "status": self.client_instance.status if self.client_instance else "ONLINE",
88
+ "widgetsCount": len(self.client_instance.widgets) if self.client_instance else 0
89
+ }
90
+ self.wfile.write(json.dumps(info).encode("utf-8"))
91
+ return
92
+
93
+ self._set_headers(404)
94
+ self.wfile.write(json.dumps({"error": "Unknown POST route"}).encode("utf-8"))
95
+
96
+ def log_message(self, format, *args):
97
+ # Suppress noisy default logging
98
+ pass
99
+
100
+ class FeoryServer:
101
+ """Embedded Zero-dependency HTTP Server for Feory Bot & App coordination."""
102
+ def __init__(self, client, host: str = "0.0.0.0", port: int = 8765):
103
+ self.client = client
104
+ self.host = host
105
+ self.port = port
106
+ self.server: Optional[HTTPServer] = None
107
+ self.thread: Optional[threading.Thread] = None
108
+ self.is_running = False
109
+
110
+ def start(self):
111
+ """Starts the server in a background daemon thread."""
112
+ if self.is_running:
113
+ return
114
+
115
+ FeoryHTTPHandler.client_instance = self.client
116
+ self.server = HTTPServer((self.host, self.port), FeoryHTTPHandler)
117
+ self.is_running = True
118
+ self.thread = threading.Thread(target=self.server.serve_forever, daemon=True)
119
+ self.thread.start()
120
+ self.client.log(f"[✓] Feory Server listening on http://{self.host}:{self.port}", "SUCCESS")
121
+
122
+ def stop(self):
123
+ """Stops the server."""
124
+ if self.server:
125
+ self.server.shutdown()
126
+ self.server.server_close()
127
+ self.is_running = False
@@ -0,0 +1,259 @@
1
+ """
2
+ Feory Dynamic UI Widget Definitions
3
+ """
4
+
5
+ import json
6
+ from enum import Enum
7
+ from typing import Dict, Any, List, Optional, Union
8
+
9
+ class WidgetType(str, Enum):
10
+ BUTTON = "BUTTON"
11
+ SLIDER = "SLIDER"
12
+ TOGGLE = "TOGGLE"
13
+ TEXT_INPUT = "TEXT_INPUT"
14
+ METRIC_CARD = "METRIC_CARD"
15
+ DROPDOWN = "DROPDOWN"
16
+ HEADER = "HEADER"
17
+
18
+ class Widget:
19
+ """Base class for all dynamic UI elements rendered in Feory Android app."""
20
+ def __init__(
21
+ self,
22
+ id: str,
23
+ type: WidgetType,
24
+ label: str,
25
+ category: str = "general",
26
+ description: Optional[str] = None,
27
+ is_enabled: bool = True,
28
+ is_visible: bool = True,
29
+ icon: Optional[str] = None,
30
+ style: Optional[Dict[str, Any]] = None,
31
+ ):
32
+ self.id = id
33
+ self.type = type
34
+ self.label = label
35
+ self.category = category
36
+ self.description = description
37
+ self.is_enabled = is_enabled
38
+ self.is_visible = is_visible
39
+ self.icon = icon
40
+ self.style = style or {}
41
+
42
+ def to_dict(self) -> Dict[str, Any]:
43
+ data = {
44
+ "id": self.id,
45
+ "type": self.type.value if isinstance(self.type, WidgetType) else str(self.type),
46
+ "label": self.label,
47
+ "category": self.category,
48
+ "isEnabled": self.is_enabled,
49
+ "isVisible": self.is_visible,
50
+ }
51
+ if self.description:
52
+ data["description"] = self.description
53
+ if self.icon:
54
+ data["icon"] = self.icon
55
+ if self.style:
56
+ data["style"] = self.style
57
+ return data
58
+
59
+ class ButtonWidget(Widget):
60
+ """Interactive Button Widget."""
61
+ def __init__(
62
+ self,
63
+ id: str,
64
+ label: str,
65
+ category: str = "general",
66
+ action_id: Optional[str] = None,
67
+ color_scheme: str = "LILAC", # LILAC, GREEN, AMBER, ROSE, BLUE
68
+ variant: str = "FILLED", # FILLED, OUTLINED, TONAL
69
+ icon: Optional[str] = "play",
70
+ description: Optional[str] = None,
71
+ ):
72
+ super().__init__(
73
+ id=id,
74
+ type=WidgetType.BUTTON,
75
+ label=label,
76
+ category=category,
77
+ description=description,
78
+ icon=icon,
79
+ style={"colorScheme": color_scheme, "variant": variant}
80
+ )
81
+ self.action_id = action_id or id
82
+
83
+ def to_dict(self) -> Dict[str, Any]:
84
+ d = super().to_dict()
85
+ d["actionId"] = self.action_id
86
+ return d
87
+
88
+ class SliderWidget(Widget):
89
+ """Continuous or Discrete Range Slider Widget."""
90
+ def __init__(
91
+ self,
92
+ id: str,
93
+ label: str,
94
+ category: str = "general",
95
+ min_value: float = 0.0,
96
+ max_value: float = 100.0,
97
+ current_value: float = 50.0,
98
+ step: Optional[float] = None,
99
+ unit: Optional[str] = None,
100
+ description: Optional[str] = None,
101
+ ):
102
+ super().__init__(
103
+ id=id,
104
+ type=WidgetType.SLIDER,
105
+ label=label,
106
+ category=category,
107
+ description=description,
108
+ icon="tune"
109
+ )
110
+ self.min_value = min_value
111
+ self.max_value = max_value
112
+ self.current_value = current_value
113
+ self.step = step
114
+ self.unit = unit
115
+
116
+ def to_dict(self) -> Dict[str, Any]:
117
+ d = super().to_dict()
118
+ d["minValue"] = self.min_value
119
+ d["maxValue"] = self.max_value
120
+ d["currentValue"] = self.current_value
121
+ if self.step is not None:
122
+ d["step"] = self.step
123
+ if self.unit:
124
+ d["unit"] = self.unit
125
+ return d
126
+
127
+ class ToggleWidget(Widget):
128
+ """Switch Toggle Widget."""
129
+ def __init__(
130
+ self,
131
+ id: str,
132
+ label: str,
133
+ category: str = "general",
134
+ is_checked: bool = False,
135
+ description: Optional[str] = None,
136
+ ):
137
+ super().__init__(
138
+ id=id,
139
+ type=WidgetType.TOGGLE,
140
+ label=label,
141
+ category=category,
142
+ description=description,
143
+ icon="toggle_on"
144
+ )
145
+ self.is_checked = is_checked
146
+
147
+ def to_dict(self) -> Dict[str, Any]:
148
+ d = super().to_dict()
149
+ d["isChecked"] = self.is_checked
150
+ return d
151
+
152
+ class TextFieldWidget(Widget):
153
+ """Text Input Field Widget."""
154
+ def __init__(
155
+ self,
156
+ id: str,
157
+ label: str,
158
+ category: str = "general",
159
+ placeholder: str = "",
160
+ current_value: str = "",
161
+ is_secret: bool = False,
162
+ description: Optional[str] = None,
163
+ ):
164
+ super().__init__(
165
+ id=id,
166
+ type=WidgetType.TEXT_INPUT,
167
+ label=label,
168
+ category=category,
169
+ description=description,
170
+ icon="edit"
171
+ )
172
+ self.placeholder = placeholder
173
+ self.current_value = current_value
174
+ self.is_secret = is_secret
175
+
176
+ def to_dict(self) -> Dict[str, Any]:
177
+ d = super().to_dict()
178
+ d["placeholder"] = self.placeholder
179
+ d["currentValue"] = self.current_value
180
+ d["isSecret"] = self.is_secret
181
+ return d
182
+
183
+ class MetricWidget(Widget):
184
+ """Bento-style Metric / Stat Card Widget."""
185
+ def __init__(
186
+ self,
187
+ id: str,
188
+ title: str,
189
+ value: Union[str, int, float],
190
+ subtitle: Optional[str] = None,
191
+ category: str = "metrics",
192
+ badge: Optional[str] = None,
193
+ accent_color: str = "LILAC",
194
+ icon: Optional[str] = "trending_up",
195
+ ):
196
+ super().__init__(
197
+ id=id,
198
+ type=WidgetType.METRIC_CARD,
199
+ label=title,
200
+ category=category,
201
+ icon=icon,
202
+ style={"accentColor": accent_color}
203
+ )
204
+ self.value = str(value)
205
+ self.subtitle = subtitle
206
+ self.badge = badge
207
+
208
+ def to_dict(self) -> Dict[str, Any]:
209
+ d = super().to_dict()
210
+ d["metricValue"] = self.value
211
+ if self.subtitle:
212
+ d["subtitle"] = self.subtitle
213
+ if self.badge:
214
+ d["badge"] = self.badge
215
+ return d
216
+
217
+ class DropdownWidget(Widget):
218
+ """Selection Dropdown Widget."""
219
+ def __init__(
220
+ self,
221
+ id: str,
222
+ label: str,
223
+ options: List[str],
224
+ selected_option: Optional[str] = None,
225
+ category: str = "general",
226
+ description: Optional[str] = None,
227
+ ):
228
+ super().__init__(
229
+ id=id,
230
+ type=WidgetType.DROPDOWN,
231
+ label=label,
232
+ category=category,
233
+ description=description,
234
+ icon="list"
235
+ )
236
+ self.options = options
237
+ self.selected_option = selected_option or (options[0] if options else "")
238
+
239
+ def to_dict(self) -> Dict[str, Any]:
240
+ d = super().to_dict()
241
+ d["options"] = self.options
242
+ d["selectedOption"] = self.selected_option
243
+ return d
244
+
245
+ class CategorySection:
246
+ """Logical grouping of widgets in Feory App."""
247
+ def __init__(self, id: str, title: str, description: Optional[str] = None, icon: Optional[str] = None):
248
+ self.id = id
249
+ self.title = title
250
+ self.description = description
251
+ self.icon = icon
252
+
253
+ def to_dict(self) -> Dict[str, Any]:
254
+ d = {"id": self.id, "title": self.title}
255
+ if self.description:
256
+ d["description"] = self.description
257
+ if self.icon:
258
+ d["icon"] = self.icon
259
+ return d
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.4
2
+ Name: feory
3
+ Version: 0.1.0
4
+ Summary: Universal Python SDK for Feory Android App - Dynamic UI Builder & Bot Control Bridge
5
+ Home-page: https://github.com/zizo0/feory
6
+ Author: zizo0
7
+ Author-email: zizo0 <zly30257@gmail.com>
8
+ Project-URL: Homepage, https://github.com/zizo0/feory
9
+ Project-URL: Bug Tracker, https://github.com/zizo0/feory/issues
10
+ Keywords: feory,android,bot,ui-builder,automation,telegram,bridge
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Topic :: Software Development :: User Interfaces
15
+ Classifier: Topic :: Communications
16
+ Requires-Python: >=3.8
17
+ Description-Content-Type: text/markdown
18
+ Dynamic: author
19
+ Dynamic: home-page
20
+ Dynamic: requires-python
21
+
22
+ # Feory Python SDK ⚡
23
+
24
+ Universal Python SDK & Bridge for the **Feory Android App**.
25
+ Create dynamic Bento-style Android UIs, manage bots, handle bi-directional events, and control servers remotely from your mobile device.
26
+
27
+ ---
28
+
29
+ ## 🚀 Installation
30
+
31
+ ```bash
32
+ pip install feory
33
+ ```
34
+
35
+ ---
36
+
37
+ ## 🛠️ Quick Start
38
+
39
+ ```python
40
+ import feory
41
+
42
+ # 1. Initialize with your bot token from the Feory App
43
+ client = feory.Client(
44
+ token="feory_tok_core_829a1b",
45
+ bot_name="My Awesome Python Bot",
46
+ port=8765
47
+ )
48
+
49
+ # 2. Register Dynamic UI Widgets
50
+ client.register_button(
51
+ id="btn_start_scrape",
52
+ label="Start Data Pipeline",
53
+ category="general",
54
+ color_scheme="LILAC",
55
+ icon="play"
56
+ )
57
+
58
+ client.register_slider(
59
+ id="slider_threads",
60
+ label="Worker Threads",
61
+ min_value=1,
62
+ max_value=32,
63
+ default_value=8
64
+ )
65
+
66
+ client.register_metric_card(
67
+ id="stat_processed",
68
+ title="Items Processed",
69
+ value="12,450",
70
+ badge="+14% today",
71
+ accent_color="GREEN"
72
+ )
73
+
74
+ # 3. Handle Events from Feory Android App
75
+ @client.on_action("btn_start_scrape")
76
+ def handle_scrape(payload):
77
+ client.log("Starting scraping tasks...", "INFO")
78
+ # Your automation logic here
79
+ return {"status": "started", "items": 100}
80
+
81
+ @client.on_value_change("slider_threads")
82
+ def handle_thread_change(new_val):
83
+ client.log(f"Thread pool updated to: {new_val}", "INFO")
84
+
85
+ # 4. Keep your bot server running
86
+ if __name__ == "__main__":
87
+ client.run_forever()
88
+ ```
89
+
90
+ ---
91
+
92
+ ## 📖 Features
93
+
94
+ - **Dynamic Android UI Generation**: Inject buttons, sliders, toggles, text inputs, dropdowns, and Bento metric cards on the fly without touching Kotlin code.
95
+ - **Two-Way Event Bus**: Receive instant button clicks and slider callbacks directly in your Python functions.
96
+ - **Real-Time Telemetry & Terminal Logs**: Stream live console logs directly to the Feory App terminal.
97
+ - **Resource Lock & Collision Prevention**: Coordinate multiple bots accessing the same channels or resources with built-in Mutex locks.
98
+ - **Zero-Dependency Core**: Uses Python standard library by default for maximum portability.
99
+
100
+ ---
101
+
102
+ ## 🔒 Authentication
103
+
104
+ Every bot connects securely using its unique **Feory Token** (`feory_tok_...`). You can create, manage, and copy tokens directly inside the **Tokens** screen of the Feory Android App.
105
+
106
+ ---
107
+
108
+ ## 👤 Author
109
+
110
+ Developed by **zizo0**
111
+ Email: `zly30257@gmail.com`
@@ -0,0 +1,12 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.py
4
+ feory/__init__.py
5
+ feory/client.py
6
+ feory/exceptions.py
7
+ feory/server.py
8
+ feory/widgets.py
9
+ feory.egg-info/PKG-INFO
10
+ feory.egg-info/SOURCES.txt
11
+ feory.egg-info/dependency_links.txt
12
+ feory.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ feory
@@ -0,0 +1,25 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "feory"
7
+ version = "0.1.0"
8
+ authors = [
9
+ { name="zizo0", email="zly30257@gmail.com" },
10
+ ]
11
+ description = "Universal Python SDK for Feory Android App - Dynamic UI Builder & Bot Control Bridge"
12
+ readme = "README.md"
13
+ requires-python = ">=3.8"
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ "Topic :: Software Development :: User Interfaces",
19
+ "Topic :: Communications",
20
+ ]
21
+ keywords = ["feory", "android", "bot", "ui-builder", "automation", "telegram", "bridge"]
22
+
23
+ [project.urls]
24
+ "Homepage" = "https://github.com/zizo0/feory"
25
+ "Bug Tracker" = "https://github.com/zizo0/feory/issues"
feory-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
feory-0.1.0/setup.py ADDED
@@ -0,0 +1,19 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="feory",
5
+ version="0.1.0",
6
+ author="zizo0",
7
+ author_email="zly30257@gmail.com",
8
+ description="Universal Python SDK for Feory Android App - Dynamic UI Builder & Bot Control Bridge",
9
+ long_description=open("README.md", encoding="utf-8").read(),
10
+ long_description_content_type="text/markdown",
11
+ url="https://github.com/zizo0/feory",
12
+ packages=find_packages(),
13
+ classifiers=[
14
+ "Programming Language :: Python :: 3",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Operating System :: OS Independent",
17
+ ],
18
+ python_requires=">=3.8",
19
+ )