trmsg 1.0.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.
- trmsg-1.0.0/LICENSE +5 -0
- trmsg-1.0.0/PKG-INFO +188 -0
- trmsg-1.0.0/README.md +144 -0
- trmsg-1.0.0/cli/__init__.py +0 -0
- trmsg-1.0.0/cli/commands/__init__.py +0 -0
- trmsg-1.0.0/cli/commands/auth.py +85 -0
- trmsg-1.0.0/cli/commands/social.py +111 -0
- trmsg-1.0.0/cli/config.py +83 -0
- trmsg-1.0.0/cli/games/__init__.py +0 -0
- trmsg-1.0.0/cli/main.py +112 -0
- trmsg-1.0.0/cli/network/__init__.py +0 -0
- trmsg-1.0.0/cli/network/client.py +94 -0
- trmsg-1.0.0/cli/ui/__init__.py +0 -0
- trmsg-1.0.0/cli/ui/chat_ui.py +860 -0
- trmsg-1.0.0/cli/ui/theme.py +89 -0
- trmsg-1.0.0/pyproject.toml +57 -0
- trmsg-1.0.0/server/__init__.py +0 -0
- trmsg-1.0.0/server/ai/__init__.py +0 -0
- trmsg-1.0.0/server/ai/gemini.py +66 -0
- trmsg-1.0.0/server/api/__init__.py +0 -0
- trmsg-1.0.0/server/api/endpoints.py +700 -0
- trmsg-1.0.0/server/auth/__init__.py +0 -0
- trmsg-1.0.0/server/auth/auth.py +55 -0
- trmsg-1.0.0/server/config.py +29 -0
- trmsg-1.0.0/server/database/__init__.py +0 -0
- trmsg-1.0.0/server/database/db.py +276 -0
- trmsg-1.0.0/server/games/__init__.py +0 -0
- trmsg-1.0.0/server/games/engine.py +203 -0
- trmsg-1.0.0/server/main.py +35 -0
- trmsg-1.0.0/server/websocket/__init__.py +0 -0
- trmsg-1.0.0/server/websocket/manager.py +643 -0
- trmsg-1.0.0/setup.cfg +4 -0
- trmsg-1.0.0/trmsg.egg-info/PKG-INFO +188 -0
- trmsg-1.0.0/trmsg.egg-info/SOURCES.txt +36 -0
- trmsg-1.0.0/trmsg.egg-info/dependency_links.txt +1 -0
- trmsg-1.0.0/trmsg.egg-info/entry_points.txt +3 -0
- trmsg-1.0.0/trmsg.egg-info/requires.txt +16 -0
- trmsg-1.0.0/trmsg.egg-info/top_level.txt +2 -0
trmsg-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
Copyright (c) 2025 trmsg Contributors
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
5
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.
|
trmsg-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: trmsg
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Terminal messaging platform â chat, share files, play games, all from your CLI
|
|
5
|
+
Author: trmsg Contributors
|
|
6
|
+
License: MIT License
|
|
7
|
+
Copyright (c) 2025 trmsg Contributors
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
9
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
10
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.
|
|
11
|
+
|
|
12
|
+
Project-URL: Homepage, https://github.com/yourusername/trmsg
|
|
13
|
+
Project-URL: Repository, https://github.com/yourusername/trmsg
|
|
14
|
+
Keywords: chat,terminal,cli,messaging,websocket,real-time,games,ai
|
|
15
|
+
Classifier: Development Status :: 4 - Beta
|
|
16
|
+
Classifier: Environment :: Console
|
|
17
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
18
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: Communications :: Chat
|
|
24
|
+
Classifier: Topic :: Terminals
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: click>=8.1.7
|
|
29
|
+
Requires-Dist: rich>=13.7.0
|
|
30
|
+
Requires-Dist: websockets>=12.0
|
|
31
|
+
Requires-Dist: httpx>=0.27.0
|
|
32
|
+
Requires-Dist: python-jose>=3.3.0
|
|
33
|
+
Requires-Dist: pydantic>=2.6.0
|
|
34
|
+
Provides-Extra: server
|
|
35
|
+
Requires-Dist: fastapi>=0.110.0; extra == "server"
|
|
36
|
+
Requires-Dist: uvicorn[standard]>=0.29.0; extra == "server"
|
|
37
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0.28; extra == "server"
|
|
38
|
+
Requires-Dist: aiosqlite>=0.20.0; extra == "server"
|
|
39
|
+
Requires-Dist: bcrypt>=4.1.2; extra == "server"
|
|
40
|
+
Requires-Dist: python-multipart>=0.0.9; extra == "server"
|
|
41
|
+
Requires-Dist: pydantic-settings>=2.2.1; extra == "server"
|
|
42
|
+
Requires-Dist: python-jose>=3.3.0; extra == "server"
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
|
|
45
|
+
# ⥠trmsg
|
|
46
|
+
|
|
47
|
+
**Terminal messaging. Chat. Share files. Play games. All from your CLI.**
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install trmsg
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Features
|
|
56
|
+
|
|
57
|
+
| Feature | Command |
|
|
58
|
+
|---|---|
|
|
59
|
+
| đŦ Real-time chat | `trmsg chat` |
|
|
60
|
+
| đ File sharing (200MB) | `/sendfile photo.jpg` |
|
|
61
|
+
| đĨ Friends & DMs | `/add username` |
|
|
62
|
+
| đ¤ AI Assistant (Gemini) | `/ai what is python?` |
|
|
63
|
+
| đŖ Self-destruct messages | `/burn 30 secret message` |
|
|
64
|
+
| đŽ TicTacToe | `/game ttt opponent` |
|
|
65
|
+
| âī¸ Chess | `/game chess opponent` |
|
|
66
|
+
| đ§ Quiz Battle | `/game quiz` |
|
|
67
|
+
| đ Leaderboard | `/leaderboard` |
|
|
68
|
+
| đ Polls | `/poll "Q?" A \| B \| C` |
|
|
69
|
+
| đ Reactions | `/react 5 đ` |
|
|
70
|
+
| âŠī¸ Reply to messages | `/reply 5 good point!` |
|
|
71
|
+
| đ Keyword alerts | `/alert homework` |
|
|
72
|
+
| đ Do Not Disturb | `/dnd 11pm-7am` |
|
|
73
|
+
| đ¨ Themes | `/theme matrix` |
|
|
74
|
+
| đ Invite links | `/invite create` |
|
|
75
|
+
| đ Message search | `/search-msg keyword` |
|
|
76
|
+
| đģ Code sharing | `/code python print("hi")` |
|
|
77
|
+
| đŖ Announcements | `/announce Important update!` |
|
|
78
|
+
| đ Roles | Owner / Admin / VIP / Member |
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Quick Start
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Install
|
|
86
|
+
pip install trmsg
|
|
87
|
+
|
|
88
|
+
# Connect to a server
|
|
89
|
+
trmsg config
|
|
90
|
+
|
|
91
|
+
# Register
|
|
92
|
+
trmsg register
|
|
93
|
+
|
|
94
|
+
# Chat!
|
|
95
|
+
trmsg chat
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Self-Host Server
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
pip install "trmsg[server]"
|
|
104
|
+
|
|
105
|
+
# Setup
|
|
106
|
+
echo "SECRET_KEY=$(python3 -c 'import secrets; print(secrets.token_hex(32))')" > .env
|
|
107
|
+
echo "DATABASE_URL=sqlite+aiosqlite:///./trmsg.db" >> .env
|
|
108
|
+
echo "GEMINI_API_KEY=your-key-here" >> .env # optional AI
|
|
109
|
+
mkdir uploads
|
|
110
|
+
|
|
111
|
+
# Start
|
|
112
|
+
trmsg-server
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Friends connect: `trmsg config` â enter your IP â `trmsg register` â `trmsg chat`
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## AI Commands
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
/ai what is async/await?
|
|
123
|
+
/ai summarize # summarize last 50 messages
|
|
124
|
+
/ai translate Japanese hello # translate text
|
|
125
|
+
/ai explain def foo(): pass # explain code
|
|
126
|
+
/ai roast janak # friendly roast đ
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Powered by **Gemini 1.5 Flash** (free). Add `GEMINI_API_KEY` to server `.env`.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Game Commands
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
/game ttt rohan # TicTacToe vs rohan
|
|
137
|
+
/game chess priya # Chess vs priya
|
|
138
|
+
/game quiz # Quiz battle (room plays together)
|
|
139
|
+
|
|
140
|
+
/move <id> 5 # TTT: pick cell 1-9
|
|
141
|
+
/move <id> e2e4 # Chess: algebraic notation
|
|
142
|
+
/answer B # Quiz: answer A/B/C/D
|
|
143
|
+
|
|
144
|
+
/leaderboard # Overall rankings
|
|
145
|
+
/leaderboard ttt # Game-specific rankings
|
|
146
|
+
/mystats # Your personal stats
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## All CLI Commands
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
trmsg register trmsg login trmsg logout
|
|
155
|
+
trmsg chat trmsg chat <user> (DM someone)
|
|
156
|
+
trmsg friends trmsg users trmsg rooms
|
|
157
|
+
trmsg add <user> trmsg whois <user> trmsg stats
|
|
158
|
+
trmsg leaderboard trmsg profile trmsg config
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Themes
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
/theme cyberpunk # neon green (default)
|
|
167
|
+
/theme matrix # matrix falling code style
|
|
168
|
+
/theme ocean # cool blue tones
|
|
169
|
+
/theme sunset # warm red/yellow
|
|
170
|
+
/theme hacker # deep green hacker
|
|
171
|
+
/theme minimal # clean white
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Deploy to Render (Free)
|
|
177
|
+
|
|
178
|
+
1. Push to GitHub
|
|
179
|
+
2. Render â New Web Service â connect repo
|
|
180
|
+
3. Build: `pip install ".[server]"`
|
|
181
|
+
4. Start: `trmsg-server`
|
|
182
|
+
5. Env vars: `SECRET_KEY`, `DATABASE_URL`, `GEMINI_API_KEY`
|
|
183
|
+
|
|
184
|
+
Share URL â friends install trmsg â connect â chat! đ
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
*Built with FastAPI ¡ SQLAlchemy ¡ WebSockets ¡ Rich ¡ Click ¡ Gemini AI*
|
trmsg-1.0.0/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# ⥠trmsg
|
|
2
|
+
|
|
3
|
+
**Terminal messaging. Chat. Share files. Play games. All from your CLI.**
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install trmsg
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
| Feature | Command |
|
|
14
|
+
|---|---|
|
|
15
|
+
| đŦ Real-time chat | `trmsg chat` |
|
|
16
|
+
| đ File sharing (200MB) | `/sendfile photo.jpg` |
|
|
17
|
+
| đĨ Friends & DMs | `/add username` |
|
|
18
|
+
| đ¤ AI Assistant (Gemini) | `/ai what is python?` |
|
|
19
|
+
| đŖ Self-destruct messages | `/burn 30 secret message` |
|
|
20
|
+
| đŽ TicTacToe | `/game ttt opponent` |
|
|
21
|
+
| âī¸ Chess | `/game chess opponent` |
|
|
22
|
+
| đ§ Quiz Battle | `/game quiz` |
|
|
23
|
+
| đ Leaderboard | `/leaderboard` |
|
|
24
|
+
| đ Polls | `/poll "Q?" A \| B \| C` |
|
|
25
|
+
| đ Reactions | `/react 5 đ` |
|
|
26
|
+
| âŠī¸ Reply to messages | `/reply 5 good point!` |
|
|
27
|
+
| đ Keyword alerts | `/alert homework` |
|
|
28
|
+
| đ Do Not Disturb | `/dnd 11pm-7am` |
|
|
29
|
+
| đ¨ Themes | `/theme matrix` |
|
|
30
|
+
| đ Invite links | `/invite create` |
|
|
31
|
+
| đ Message search | `/search-msg keyword` |
|
|
32
|
+
| đģ Code sharing | `/code python print("hi")` |
|
|
33
|
+
| đŖ Announcements | `/announce Important update!` |
|
|
34
|
+
| đ Roles | Owner / Admin / VIP / Member |
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Install
|
|
42
|
+
pip install trmsg
|
|
43
|
+
|
|
44
|
+
# Connect to a server
|
|
45
|
+
trmsg config
|
|
46
|
+
|
|
47
|
+
# Register
|
|
48
|
+
trmsg register
|
|
49
|
+
|
|
50
|
+
# Chat!
|
|
51
|
+
trmsg chat
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Self-Host Server
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install "trmsg[server]"
|
|
60
|
+
|
|
61
|
+
# Setup
|
|
62
|
+
echo "SECRET_KEY=$(python3 -c 'import secrets; print(secrets.token_hex(32))')" > .env
|
|
63
|
+
echo "DATABASE_URL=sqlite+aiosqlite:///./trmsg.db" >> .env
|
|
64
|
+
echo "GEMINI_API_KEY=your-key-here" >> .env # optional AI
|
|
65
|
+
mkdir uploads
|
|
66
|
+
|
|
67
|
+
# Start
|
|
68
|
+
trmsg-server
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Friends connect: `trmsg config` â enter your IP â `trmsg register` â `trmsg chat`
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## AI Commands
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
/ai what is async/await?
|
|
79
|
+
/ai summarize # summarize last 50 messages
|
|
80
|
+
/ai translate Japanese hello # translate text
|
|
81
|
+
/ai explain def foo(): pass # explain code
|
|
82
|
+
/ai roast janak # friendly roast đ
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Powered by **Gemini 1.5 Flash** (free). Add `GEMINI_API_KEY` to server `.env`.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Game Commands
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
/game ttt rohan # TicTacToe vs rohan
|
|
93
|
+
/game chess priya # Chess vs priya
|
|
94
|
+
/game quiz # Quiz battle (room plays together)
|
|
95
|
+
|
|
96
|
+
/move <id> 5 # TTT: pick cell 1-9
|
|
97
|
+
/move <id> e2e4 # Chess: algebraic notation
|
|
98
|
+
/answer B # Quiz: answer A/B/C/D
|
|
99
|
+
|
|
100
|
+
/leaderboard # Overall rankings
|
|
101
|
+
/leaderboard ttt # Game-specific rankings
|
|
102
|
+
/mystats # Your personal stats
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## All CLI Commands
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
trmsg register trmsg login trmsg logout
|
|
111
|
+
trmsg chat trmsg chat <user> (DM someone)
|
|
112
|
+
trmsg friends trmsg users trmsg rooms
|
|
113
|
+
trmsg add <user> trmsg whois <user> trmsg stats
|
|
114
|
+
trmsg leaderboard trmsg profile trmsg config
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Themes
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
/theme cyberpunk # neon green (default)
|
|
123
|
+
/theme matrix # matrix falling code style
|
|
124
|
+
/theme ocean # cool blue tones
|
|
125
|
+
/theme sunset # warm red/yellow
|
|
126
|
+
/theme hacker # deep green hacker
|
|
127
|
+
/theme minimal # clean white
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Deploy to Render (Free)
|
|
133
|
+
|
|
134
|
+
1. Push to GitHub
|
|
135
|
+
2. Render â New Web Service â connect repo
|
|
136
|
+
3. Build: `pip install ".[server]"`
|
|
137
|
+
4. Start: `trmsg-server`
|
|
138
|
+
5. Env vars: `SECRET_KEY`, `DATABASE_URL`, `GEMINI_API_KEY`
|
|
139
|
+
|
|
140
|
+
Share URL â friends install trmsg â connect â chat! đ
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
*Built with FastAPI ¡ SQLAlchemy ¡ WebSockets ¡ Rich ¡ Click ¡ Gemini AI*
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""trmsg - Auth Commands"""
|
|
2
|
+
from rich.prompt import Prompt, Confirm
|
|
3
|
+
from rich.panel import Panel
|
|
4
|
+
from cli.config import config
|
|
5
|
+
from cli.network.client import APIClient, APIError
|
|
6
|
+
from cli.ui.theme import print_banner, print_error, print_success, print_info, console
|
|
7
|
+
|
|
8
|
+
async def register_command():
|
|
9
|
+
print_banner(config.theme)
|
|
10
|
+
console.print(Panel.fit("[bold bright_green]Create Account[/bold bright_green]", border_style="bright_green"))
|
|
11
|
+
api = APIClient()
|
|
12
|
+
try:
|
|
13
|
+
username = Prompt.ask("[cyan]Username[/cyan]").strip()
|
|
14
|
+
password = Prompt.ask("[cyan]Password[/cyan]", password=True)
|
|
15
|
+
confirm = Prompt.ask("[cyan]Confirm password[/cyan]", password=True)
|
|
16
|
+
if password != confirm: print_error("Passwords don't match!"); return
|
|
17
|
+
if len(password) < 8: print_error("Password must be 8+ characters!"); return
|
|
18
|
+
email = Prompt.ask("[cyan]Email[/cyan] [dim](optional)[/dim]", default="").strip() or None
|
|
19
|
+
display_name = Prompt.ask("[cyan]Display name[/cyan]", default=username).strip()
|
|
20
|
+
with console.status("[green]Creating account...[/green]"):
|
|
21
|
+
result = await api.post("/api/v1/users/register", {"username":username,"password":password,"email":email,"display_name":display_name})
|
|
22
|
+
config.token = result["token"]; config.username = result["username"]; config.avatar_color = result.get("avatar_color","#00ff88")
|
|
23
|
+
print_success(f"Welcome to trmsg, [bold]{result['username']}[/bold]! âĄ")
|
|
24
|
+
print_info("Run [bold]trmsg chat[/bold] to start!")
|
|
25
|
+
except APIError as e: print_error(f"Registration failed: {e.message}")
|
|
26
|
+
finally: await api.close()
|
|
27
|
+
|
|
28
|
+
async def login_command():
|
|
29
|
+
print_banner(config.theme)
|
|
30
|
+
if config.is_authenticated():
|
|
31
|
+
print_info(f"Already logged in as [bold]{config.username}[/bold]")
|
|
32
|
+
if not Confirm.ask("Login as different user?"): return
|
|
33
|
+
api = APIClient()
|
|
34
|
+
try:
|
|
35
|
+
username = Prompt.ask("[cyan]Username[/cyan]").strip()
|
|
36
|
+
password = Prompt.ask("[cyan]Password[/cyan]", password=True)
|
|
37
|
+
with console.status("[green]Logging in...[/green]"):
|
|
38
|
+
result = await api.post("/api/v1/users/login", {"username":username,"password":password})
|
|
39
|
+
config.token = result["token"]; config.username = result["username"]
|
|
40
|
+
config.avatar_color = result.get("avatar_color","#00ff88")
|
|
41
|
+
if result.get("theme"): config.theme = result["theme"]
|
|
42
|
+
print_success(f"Welcome back, [bold]{result.get('display_name',username)}[/bold]! âĄ")
|
|
43
|
+
print_info("Run [bold]trmsg chat[/bold] to start!")
|
|
44
|
+
except APIError as e: print_error(f"Login failed: {e.message}")
|
|
45
|
+
finally: await api.close()
|
|
46
|
+
|
|
47
|
+
def logout_command():
|
|
48
|
+
if not config.is_authenticated(): print_info("Not logged in."); return
|
|
49
|
+
username = config.username; config.clear_auth()
|
|
50
|
+
print_success(f"Logged out [bold]{username}[/bold]. Goodbye! đ")
|
|
51
|
+
|
|
52
|
+
async def profile_command():
|
|
53
|
+
api = APIClient()
|
|
54
|
+
try:
|
|
55
|
+
me = await api.get("/api/v1/users/me")
|
|
56
|
+
s = await api.get(f"/api/v1/users/{me['username']}/stats")
|
|
57
|
+
console.print(f"\n[bold cyan]Your Profile[/bold cyan]")
|
|
58
|
+
console.print(f" Username: [bold]{me['username']}[/bold]")
|
|
59
|
+
console.print(f" Display name: {me.get('display_name','â')}")
|
|
60
|
+
console.print(f" Bio: {me.get('bio','â')}")
|
|
61
|
+
console.print(f" Status: {me.get('status','offline')}")
|
|
62
|
+
console.print(f" Role: {me.get('role','member')}")
|
|
63
|
+
console.print(f" Score: đ {s.get('score',0)} (Rank #{s.get('rank','?')})")
|
|
64
|
+
console.print(f" Messages: {s.get('total_messages',0)}")
|
|
65
|
+
console.print(f" Theme: {me.get('theme','cyberpunk')}")
|
|
66
|
+
console.print(f" Server: {config.server_url}\n")
|
|
67
|
+
if Confirm.ask("Edit profile?"):
|
|
68
|
+
display_name = Prompt.ask("Display name", default=me.get("display_name","")).strip()
|
|
69
|
+
bio = Prompt.ask("Bio", default=me.get("bio","")).strip()
|
|
70
|
+
with console.status("[green]Updating...[/green]"):
|
|
71
|
+
await api.patch("/api/v1/users/me", {"display_name":display_name,"bio":bio})
|
|
72
|
+
print_success("Profile updated!")
|
|
73
|
+
except APIError as e: print_error(f"Failed: {e.message}")
|
|
74
|
+
finally: await api.close()
|
|
75
|
+
|
|
76
|
+
def configure_command():
|
|
77
|
+
console.print("\n[bold cyan]trmsg Configuration[/bold cyan]\n")
|
|
78
|
+
server = Prompt.ask("[cyan]Server URL[/cyan]", default=config.server_url)
|
|
79
|
+
config.server_url = server.rstrip("/")
|
|
80
|
+
from cli.ui.theme import THEMES
|
|
81
|
+
theme = Prompt.ask("[cyan]Theme[/cyan]", default=config.theme, choices=list(THEMES.keys()))
|
|
82
|
+
config.theme = theme
|
|
83
|
+
dl_dir = Prompt.ask("[cyan]Download directory[/cyan]", default=str(config.download_dir))
|
|
84
|
+
config.download_dir = dl_dir
|
|
85
|
+
print_success("Configuration saved!")
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""trmsg - Social Commands"""
|
|
2
|
+
from rich.table import Table
|
|
3
|
+
from cli.network.client import APIClient, APIError
|
|
4
|
+
from cli.ui.theme import print_error, print_success, print_info, get_status_icon, get_role_badge, format_timestamp, console
|
|
5
|
+
|
|
6
|
+
async def cmd_add(username):
|
|
7
|
+
api = APIClient()
|
|
8
|
+
try:
|
|
9
|
+
with console.status(f"[green]Sending to {username}...[/green]"):
|
|
10
|
+
await api.post(f"/api/v1/friends/add/{username}")
|
|
11
|
+
print_success(f"Friend request sent to [bold]{username}[/bold] đ¨")
|
|
12
|
+
except APIError as e: print_error(f"Failed: {e.message}")
|
|
13
|
+
finally: await api.close()
|
|
14
|
+
|
|
15
|
+
async def cmd_friends():
|
|
16
|
+
api = APIClient()
|
|
17
|
+
try:
|
|
18
|
+
with console.status("[green]Loading...[/green]"): r = await api.get("/api/v1/friends/list")
|
|
19
|
+
friends = r.get("friends",[])
|
|
20
|
+
if not friends: print_info("No friends yet. Use [bold]trmsg add <username>[/bold]"); return
|
|
21
|
+
t = Table(title="đĨ Friends",border_style="bright_black",header_style="bold cyan")
|
|
22
|
+
t.add_column("",width=3); t.add_column("Username",style="bright_cyan"); t.add_column("Display Name"); t.add_column("Status"); t.add_column("Last Seen",style="dim")
|
|
23
|
+
for f in friends:
|
|
24
|
+
icon = get_status_icon(f.get("status","offline"))
|
|
25
|
+
t.add_row(icon, f["username"], f.get("display_name",""), f.get("status_message","") or "", format_timestamp(f.get("last_seen","")) if f.get("last_seen") else "â")
|
|
26
|
+
console.print(t)
|
|
27
|
+
except APIError as e: print_error(f"Failed: {e.message}")
|
|
28
|
+
finally: await api.close()
|
|
29
|
+
|
|
30
|
+
async def cmd_users():
|
|
31
|
+
api = APIClient()
|
|
32
|
+
try:
|
|
33
|
+
with console.status("[green]Fetching...[/green]"): r = await api.get("/api/v1/users/online")
|
|
34
|
+
users = r.get("users",[]); count = r.get("count",0)
|
|
35
|
+
if not users: print_info("No users online."); return
|
|
36
|
+
t = Table(title=f"đĸ Online ({count})",border_style="green",header_style="bold green")
|
|
37
|
+
t.add_column("",width=3); t.add_column("",width=3); t.add_column("Username",style="bright_cyan"); t.add_column("Display Name"); t.add_column("Score",justify="right",style="yellow")
|
|
38
|
+
for u in users:
|
|
39
|
+
icon = get_status_icon(u.get("status","online"))
|
|
40
|
+
badge = get_role_badge(u.get("role","member"))
|
|
41
|
+
t.add_row(icon, badge, u["username"], u.get("display_name",""), str(u.get("score",0)))
|
|
42
|
+
console.print(t)
|
|
43
|
+
except APIError as e: print_error(f"Failed: {e.message}")
|
|
44
|
+
finally: await api.close()
|
|
45
|
+
|
|
46
|
+
async def cmd_leaderboard():
|
|
47
|
+
api = APIClient()
|
|
48
|
+
try:
|
|
49
|
+
with console.status("[green]Loading...[/green]"): r = await api.get("/api/v1/games/leaderboard")
|
|
50
|
+
t = Table(title="đ Leaderboard",border_style="yellow",header_style="bold yellow")
|
|
51
|
+
t.add_column("Rank",width=5); t.add_column("",width=3); t.add_column("Username",style="bright_cyan"); t.add_column("Score",justify="right",style="bright_yellow"); t.add_column("Status",width=3)
|
|
52
|
+
medals = ["đĨ","đĨ","đĨ"]
|
|
53
|
+
for u in r.get("overall",[]):
|
|
54
|
+
rank = medals[u["rank"]-1] if u["rank"]<=3 else f"#{u['rank']}"
|
|
55
|
+
status = "[green]â[/green]" if u.get("is_online") else "[dim]â[/dim]"
|
|
56
|
+
t.add_row(rank, "", u["username"], str(u["score"]), status)
|
|
57
|
+
console.print(t)
|
|
58
|
+
except APIError as e: print_error(f"Failed: {e.message}")
|
|
59
|
+
finally: await api.close()
|
|
60
|
+
|
|
61
|
+
async def cmd_stats():
|
|
62
|
+
api = APIClient()
|
|
63
|
+
try:
|
|
64
|
+
with console.status("[green]Fetching...[/green]"): s = await api.get("/api/v1/stats")
|
|
65
|
+
console.print("\n[bold cyan]ââ trmsg Server Stats ââ[/bold cyan]")
|
|
66
|
+
console.print(f" đĨ Users: [bold]{s.get('users',0)}[/bold]")
|
|
67
|
+
console.print(f" đĸ Online: [bold bright_green]{s.get('online_now',0)}[/bold bright_green]")
|
|
68
|
+
console.print(f" đŦ Rooms: [bold]{s.get('rooms',0)}[/bold]")
|
|
69
|
+
console.print(f" đ¨ Messages: [bold]{s.get('messages',0)}[/bold]")
|
|
70
|
+
console.print(f" đ Files: [bold]{s.get('files',0)}[/bold]")
|
|
71
|
+
console.print(f" đŽ Games: [bold]{s.get('games_played',0)}[/bold]\n")
|
|
72
|
+
except APIError as e: print_error(f"Failed: {e.message}")
|
|
73
|
+
finally: await api.close()
|
|
74
|
+
|
|
75
|
+
async def cmd_whois(username):
|
|
76
|
+
api = APIClient()
|
|
77
|
+
try:
|
|
78
|
+
with console.status(f"[green]Looking up {username}...[/green]"):
|
|
79
|
+
u = await api.get(f"/api/v1/users/{username}")
|
|
80
|
+
s = await api.get(f"/api/v1/users/{username}/stats")
|
|
81
|
+
icon = get_status_icon(u.get("status","offline"))
|
|
82
|
+
badge = get_role_badge(u.get("role","member"))
|
|
83
|
+
console.print(f"\n[bold cyan]ââ {u['username']} {badge} ââ[/bold cyan]")
|
|
84
|
+
console.print(f" Display: {u.get('display_name',u['username'])}")
|
|
85
|
+
console.print(f" Status: {icon} {u.get('status','offline')}")
|
|
86
|
+
if u.get("status_message"): console.print(f" Message: {u['status_message']}")
|
|
87
|
+
if u.get("bio"): console.print(f" Bio: {u['bio']}")
|
|
88
|
+
console.print(f" Score: đ {s.get('score',0)} (Rank #{s.get('rank','?')})")
|
|
89
|
+
console.print(f" Messages: {s.get('total_messages',0)} | Files: {s.get('total_files',0)}")
|
|
90
|
+
gs = s.get("game_stats",{})
|
|
91
|
+
if gs:
|
|
92
|
+
for game, stat in gs.items():
|
|
93
|
+
console.print(f" {game.upper()}: W:{stat['wins']} L:{stat['losses']} Score:{stat['score']}")
|
|
94
|
+
console.print(f" Joined: {u.get('created_at','')[:10]}\n")
|
|
95
|
+
except APIError as e: print_error(f"Failed: {e.message}")
|
|
96
|
+
finally: await api.close()
|
|
97
|
+
|
|
98
|
+
async def cmd_rooms():
|
|
99
|
+
api = APIClient()
|
|
100
|
+
try:
|
|
101
|
+
with console.status("[green]Loading...[/green]"): r = await api.get("/api/v1/rooms")
|
|
102
|
+
rooms = r.get("rooms",[])
|
|
103
|
+
if not rooms: print_info("No public rooms."); return
|
|
104
|
+
t = Table(title="đĸ Public Rooms",border_style="bright_black",header_style="bold cyan")
|
|
105
|
+
t.add_column("Icon",width=4); t.add_column("Room",style="bright_cyan"); t.add_column("Category"); t.add_column("Description"); t.add_column("đĸ",justify="right"); t.add_column("đ",width=3)
|
|
106
|
+
for rm in rooms:
|
|
107
|
+
t.add_row(rm.get("icon","đŦ"), f"#{rm['name']}", rm.get("category","") or "", rm.get("description","") or "", str(rm.get("online",0)), "đ" if rm.get("has_password") else "")
|
|
108
|
+
console.print(t)
|
|
109
|
+
console.print("[dim] Join: trmsg chat â /join <room>[/dim]\n")
|
|
110
|
+
except APIError as e: print_error(f"Failed: {e.message}")
|
|
111
|
+
finally: await api.close()
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""trmsg - CLI Config"""
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
class CLIConfig:
|
|
7
|
+
CONFIG_DIR = Path.home() / ".trmsg"
|
|
8
|
+
CONFIG_FILE = CONFIG_DIR / "config.json"
|
|
9
|
+
TOKEN_FILE = CONFIG_DIR / "token"
|
|
10
|
+
HISTORY_FILE= CONFIG_DIR / "history"
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
14
|
+
self._data = self._load()
|
|
15
|
+
|
|
16
|
+
def _load(self):
|
|
17
|
+
if self.CONFIG_FILE.exists():
|
|
18
|
+
try: return json.loads(self.CONFIG_FILE.read_text())
|
|
19
|
+
except: return {}
|
|
20
|
+
return {}
|
|
21
|
+
|
|
22
|
+
def _save(self):
|
|
23
|
+
self.CONFIG_FILE.write_text(json.dumps(self._data, indent=2))
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def server_url(self): return self._data.get("server_url", "http://localhost:8000")
|
|
27
|
+
@server_url.setter
|
|
28
|
+
def server_url(self, v): self._data["server_url"] = v.rstrip("/"); self._save()
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def ws_url(self): return self.server_url.replace("http://","ws://").replace("https://","wss://")
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def username(self): return self._data.get("username")
|
|
35
|
+
@username.setter
|
|
36
|
+
def username(self, v): self._data["username"] = v; self._save()
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def avatar_color(self): return self._data.get("avatar_color","#00ff88")
|
|
40
|
+
@avatar_color.setter
|
|
41
|
+
def avatar_color(self, v): self._data["avatar_color"] = v; self._save()
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def theme(self): return self._data.get("theme","cyberpunk")
|
|
45
|
+
@theme.setter
|
|
46
|
+
def theme(self, v): self._data["theme"] = v; self._save()
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def token(self):
|
|
50
|
+
if self.TOKEN_FILE.exists(): return self.TOKEN_FILE.read_text().strip() or None
|
|
51
|
+
return None
|
|
52
|
+
@token.setter
|
|
53
|
+
def token(self, v):
|
|
54
|
+
if v: self.TOKEN_FILE.write_text(v); self.TOKEN_FILE.chmod(0o600)
|
|
55
|
+
elif self.TOKEN_FILE.exists(): self.TOKEN_FILE.unlink()
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def download_dir(self):
|
|
59
|
+
p = Path(self._data.get("download_dir", str(Path.home()/"Downloads"/"trmsg")))
|
|
60
|
+
p.mkdir(parents=True, exist_ok=True)
|
|
61
|
+
return p
|
|
62
|
+
@download_dir.setter
|
|
63
|
+
def download_dir(self, v): self._data["download_dir"] = v; self._save()
|
|
64
|
+
|
|
65
|
+
def add_history(self, text: str):
|
|
66
|
+
if not self.HISTORY_FILE.exists(): h = []
|
|
67
|
+
else:
|
|
68
|
+
try: h = json.loads(self.HISTORY_FILE.read_text())
|
|
69
|
+
except: h = []
|
|
70
|
+
if text and (not h or h[-1] != text):
|
|
71
|
+
h.append(text)
|
|
72
|
+
if len(h) > 200: h = h[-200:]
|
|
73
|
+
self.HISTORY_FILE.write_text(json.dumps(h))
|
|
74
|
+
|
|
75
|
+
def is_authenticated(self): return bool(self.token and self.username)
|
|
76
|
+
|
|
77
|
+
def clear_auth(self):
|
|
78
|
+
self.token = None
|
|
79
|
+
self._data.pop("username", None)
|
|
80
|
+
self._data.pop("avatar_color", None)
|
|
81
|
+
self._save()
|
|
82
|
+
|
|
83
|
+
config = CLIConfig()
|
|
File without changes
|