59chat 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.
- 59chat-0.1.0/59chat.egg-info/PKG-INFO +92 -0
- 59chat-0.1.0/59chat.egg-info/SOURCES.txt +11 -0
- 59chat-0.1.0/59chat.egg-info/dependency_links.txt +1 -0
- 59chat-0.1.0/59chat.egg-info/entry_points.txt +2 -0
- 59chat-0.1.0/59chat.egg-info/requires.txt +4 -0
- 59chat-0.1.0/59chat.egg-info/top_level.txt +1 -0
- 59chat-0.1.0/MANIFEST.in +5 -0
- 59chat-0.1.0/PKG-INFO +92 -0
- 59chat-0.1.0/README.md +72 -0
- 59chat-0.1.0/main.py +288 -0
- 59chat-0.1.0/requirements.txt +4 -0
- 59chat-0.1.0/setup.cfg +4 -0
- 59chat-0.1.0/setup.py +25 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: 59chat
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 59-second zero-trace terminal chat
|
|
5
|
+
Home-page: https://github.com/yourusername/59chat
|
|
6
|
+
Author: YourName
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: textual>=0.45.0
|
|
10
|
+
Requires-Dist: supabase>=2.3.0
|
|
11
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
12
|
+
Requires-Dist: pyperclip>=1.8.2
|
|
13
|
+
Dynamic: author
|
|
14
|
+
Dynamic: description
|
|
15
|
+
Dynamic: description-content-type
|
|
16
|
+
Dynamic: home-page
|
|
17
|
+
Dynamic: requires-dist
|
|
18
|
+
Dynamic: requires-python
|
|
19
|
+
Dynamic: summary
|
|
20
|
+
|
|
21
|
+
# Ephemeral Terminal Chat
|
|
22
|
+
|
|
23
|
+
Zero-trace terminal chat application with 59-second disappearing messages.
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- Messages disappear after 59 seconds
|
|
28
|
+
- Zero trace - no history kept
|
|
29
|
+
- Fast terminal UI with Textual framework
|
|
30
|
+
- Mono font recommended (set in your terminal, e.g., JetBrains Mono)
|
|
31
|
+
- Supabase backend for real-time sync
|
|
32
|
+
- Share rooms with 6-character room codes
|
|
33
|
+
- Random nickname generation
|
|
34
|
+
|
|
35
|
+
## Setup
|
|
36
|
+
|
|
37
|
+
### 1. Supabase Setup
|
|
38
|
+
|
|
39
|
+
1. Go to [supabase.com](https://supabase.com) and create a free account
|
|
40
|
+
2. Create a new project
|
|
41
|
+
3. Go to SQL Editor and run the `supabase_setup.sql` file
|
|
42
|
+
4. Get your project URL and anon key from Settings > API
|
|
43
|
+
|
|
44
|
+
### 2. Environment Variables
|
|
45
|
+
|
|
46
|
+
Copy `.env.example` to `.env` and add your Supabase credentials:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
cp .env.example .env
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Edit `.env`:
|
|
53
|
+
```
|
|
54
|
+
SUPABASE_URL=your_supabase_project_url
|
|
55
|
+
SUPABASE_KEY=your_supabase_anon_key
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 3. Install Dependencies
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install -r requirements.txt
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 4. Run
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
python main.py
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
- **Type a message** and press Enter to send
|
|
73
|
+
- **Ctrl+R** - Create new room
|
|
74
|
+
- **Ctrl+C** - Quit
|
|
75
|
+
- Share the **room code** with others to chat
|
|
76
|
+
- Messages auto-delete after 59 seconds
|
|
77
|
+
|
|
78
|
+
## How It Works
|
|
79
|
+
|
|
80
|
+
- Each room has a unique 6-character code
|
|
81
|
+
- Messages are stored in Supabase with timestamp
|
|
82
|
+
- Auto-cleanup deletes messages older than 59 seconds
|
|
83
|
+
- Terminal polls for new messages every second
|
|
84
|
+
- Zero-trace: no message history retained
|
|
85
|
+
|
|
86
|
+
## Terminal Share
|
|
87
|
+
|
|
88
|
+
Share your terminal with:
|
|
89
|
+
- tmate: `tmate`
|
|
90
|
+
- warp: Share session feature
|
|
91
|
+
- ssh: Allow remote connections
|
|
92
|
+
- Or just share the room code via any channel
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
MANIFEST.in
|
|
2
|
+
README.md
|
|
3
|
+
main.py
|
|
4
|
+
requirements.txt
|
|
5
|
+
setup.py
|
|
6
|
+
59chat.egg-info/PKG-INFO
|
|
7
|
+
59chat.egg-info/SOURCES.txt
|
|
8
|
+
59chat.egg-info/dependency_links.txt
|
|
9
|
+
59chat.egg-info/entry_points.txt
|
|
10
|
+
59chat.egg-info/requires.txt
|
|
11
|
+
59chat.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
main
|
59chat-0.1.0/MANIFEST.in
ADDED
59chat-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: 59chat
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 59-second zero-trace terminal chat
|
|
5
|
+
Home-page: https://github.com/yourusername/59chat
|
|
6
|
+
Author: YourName
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: textual>=0.45.0
|
|
10
|
+
Requires-Dist: supabase>=2.3.0
|
|
11
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
12
|
+
Requires-Dist: pyperclip>=1.8.2
|
|
13
|
+
Dynamic: author
|
|
14
|
+
Dynamic: description
|
|
15
|
+
Dynamic: description-content-type
|
|
16
|
+
Dynamic: home-page
|
|
17
|
+
Dynamic: requires-dist
|
|
18
|
+
Dynamic: requires-python
|
|
19
|
+
Dynamic: summary
|
|
20
|
+
|
|
21
|
+
# Ephemeral Terminal Chat
|
|
22
|
+
|
|
23
|
+
Zero-trace terminal chat application with 59-second disappearing messages.
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- Messages disappear after 59 seconds
|
|
28
|
+
- Zero trace - no history kept
|
|
29
|
+
- Fast terminal UI with Textual framework
|
|
30
|
+
- Mono font recommended (set in your terminal, e.g., JetBrains Mono)
|
|
31
|
+
- Supabase backend for real-time sync
|
|
32
|
+
- Share rooms with 6-character room codes
|
|
33
|
+
- Random nickname generation
|
|
34
|
+
|
|
35
|
+
## Setup
|
|
36
|
+
|
|
37
|
+
### 1. Supabase Setup
|
|
38
|
+
|
|
39
|
+
1. Go to [supabase.com](https://supabase.com) and create a free account
|
|
40
|
+
2. Create a new project
|
|
41
|
+
3. Go to SQL Editor and run the `supabase_setup.sql` file
|
|
42
|
+
4. Get your project URL and anon key from Settings > API
|
|
43
|
+
|
|
44
|
+
### 2. Environment Variables
|
|
45
|
+
|
|
46
|
+
Copy `.env.example` to `.env` and add your Supabase credentials:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
cp .env.example .env
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Edit `.env`:
|
|
53
|
+
```
|
|
54
|
+
SUPABASE_URL=your_supabase_project_url
|
|
55
|
+
SUPABASE_KEY=your_supabase_anon_key
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 3. Install Dependencies
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install -r requirements.txt
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 4. Run
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
python main.py
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
- **Type a message** and press Enter to send
|
|
73
|
+
- **Ctrl+R** - Create new room
|
|
74
|
+
- **Ctrl+C** - Quit
|
|
75
|
+
- Share the **room code** with others to chat
|
|
76
|
+
- Messages auto-delete after 59 seconds
|
|
77
|
+
|
|
78
|
+
## How It Works
|
|
79
|
+
|
|
80
|
+
- Each room has a unique 6-character code
|
|
81
|
+
- Messages are stored in Supabase with timestamp
|
|
82
|
+
- Auto-cleanup deletes messages older than 59 seconds
|
|
83
|
+
- Terminal polls for new messages every second
|
|
84
|
+
- Zero-trace: no message history retained
|
|
85
|
+
|
|
86
|
+
## Terminal Share
|
|
87
|
+
|
|
88
|
+
Share your terminal with:
|
|
89
|
+
- tmate: `tmate`
|
|
90
|
+
- warp: Share session feature
|
|
91
|
+
- ssh: Allow remote connections
|
|
92
|
+
- Or just share the room code via any channel
|
59chat-0.1.0/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Ephemeral Terminal Chat
|
|
2
|
+
|
|
3
|
+
Zero-trace terminal chat application with 59-second disappearing messages.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Messages disappear after 59 seconds
|
|
8
|
+
- Zero trace - no history kept
|
|
9
|
+
- Fast terminal UI with Textual framework
|
|
10
|
+
- Mono font recommended (set in your terminal, e.g., JetBrains Mono)
|
|
11
|
+
- Supabase backend for real-time sync
|
|
12
|
+
- Share rooms with 6-character room codes
|
|
13
|
+
- Random nickname generation
|
|
14
|
+
|
|
15
|
+
## Setup
|
|
16
|
+
|
|
17
|
+
### 1. Supabase Setup
|
|
18
|
+
|
|
19
|
+
1. Go to [supabase.com](https://supabase.com) and create a free account
|
|
20
|
+
2. Create a new project
|
|
21
|
+
3. Go to SQL Editor and run the `supabase_setup.sql` file
|
|
22
|
+
4. Get your project URL and anon key from Settings > API
|
|
23
|
+
|
|
24
|
+
### 2. Environment Variables
|
|
25
|
+
|
|
26
|
+
Copy `.env.example` to `.env` and add your Supabase credentials:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
cp .env.example .env
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Edit `.env`:
|
|
33
|
+
```
|
|
34
|
+
SUPABASE_URL=your_supabase_project_url
|
|
35
|
+
SUPABASE_KEY=your_supabase_anon_key
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 3. Install Dependencies
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install -r requirements.txt
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 4. Run
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
python main.py
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
- **Type a message** and press Enter to send
|
|
53
|
+
- **Ctrl+R** - Create new room
|
|
54
|
+
- **Ctrl+C** - Quit
|
|
55
|
+
- Share the **room code** with others to chat
|
|
56
|
+
- Messages auto-delete after 59 seconds
|
|
57
|
+
|
|
58
|
+
## How It Works
|
|
59
|
+
|
|
60
|
+
- Each room has a unique 6-character code
|
|
61
|
+
- Messages are stored in Supabase with timestamp
|
|
62
|
+
- Auto-cleanup deletes messages older than 59 seconds
|
|
63
|
+
- Terminal polls for new messages every second
|
|
64
|
+
- Zero-trace: no message history retained
|
|
65
|
+
|
|
66
|
+
## Terminal Share
|
|
67
|
+
|
|
68
|
+
Share your terminal with:
|
|
69
|
+
- tmate: `tmate`
|
|
70
|
+
- warp: Share session feature
|
|
71
|
+
- ssh: Allow remote connections
|
|
72
|
+
- Or just share the room code via any channel
|
59chat-0.1.0/main.py
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import argparse
|
|
5
|
+
import subprocess
|
|
6
|
+
import json
|
|
7
|
+
import urllib.request
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from typing import Optional, Set, List, Dict
|
|
10
|
+
from textual.app import App, ComposeResult
|
|
11
|
+
from textual.widgets import Input, Static, Button, Footer, RichLog
|
|
12
|
+
from textual.containers import Vertical, Horizontal
|
|
13
|
+
from supabase import create_client, Client
|
|
14
|
+
import random
|
|
15
|
+
import string
|
|
16
|
+
import pyperclip
|
|
17
|
+
|
|
18
|
+
# --- CONFIGURATION ---
|
|
19
|
+
VERSION = "0.1.0" # Yeni isim olduğu için v0.1.0'dan başlıyoruz
|
|
20
|
+
APP_NAME = "59chat"
|
|
21
|
+
CMD_NAME = "59chat"
|
|
22
|
+
|
|
23
|
+
SUPABASE_URL = "https://xdqxebyyjxklzisddmwl.supabase.co"
|
|
24
|
+
SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InhkcXhlYnl5anhrbHppc2RkbXdsIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTk4Mzc0NDAsImV4cCI6MjA3NTQxMzQ0MH0.37JtLjN6mGfdac-t-cqNADa8OQlYIgSkZEFSngwxlM0"
|
|
25
|
+
|
|
26
|
+
# Design System
|
|
27
|
+
COLOR_BG = "#000000"
|
|
28
|
+
COLOR_SURFACE = "#0A0A0A"
|
|
29
|
+
COLOR_BORDER = "#1A1A1A"
|
|
30
|
+
COLOR_ACCENT = "#FFFFFF"
|
|
31
|
+
COLOR_TEXT_SECONDARY = "#525252"
|
|
32
|
+
COLOR_SUCCESS = "#22C55E"
|
|
33
|
+
COLOR_ERROR = "#EF4444"
|
|
34
|
+
|
|
35
|
+
CSS = f"""
|
|
36
|
+
Screen {{
|
|
37
|
+
background: {COLOR_BG};
|
|
38
|
+
color: {COLOR_ACCENT};
|
|
39
|
+
}}
|
|
40
|
+
|
|
41
|
+
#header {{
|
|
42
|
+
dock: top;
|
|
43
|
+
height: 3;
|
|
44
|
+
background: {COLOR_SURFACE};
|
|
45
|
+
border-bottom: solid {COLOR_BORDER};
|
|
46
|
+
content-align: center middle;
|
|
47
|
+
text-style: bold;
|
|
48
|
+
}}
|
|
49
|
+
|
|
50
|
+
#chat-log {{
|
|
51
|
+
height: 1fr;
|
|
52
|
+
background: {COLOR_BG};
|
|
53
|
+
border: none;
|
|
54
|
+
padding: 1 1;
|
|
55
|
+
scrollbar-gutter: stable;
|
|
56
|
+
}}
|
|
57
|
+
|
|
58
|
+
#input-area {{
|
|
59
|
+
dock: bottom;
|
|
60
|
+
height: 3;
|
|
61
|
+
background: {COLOR_SURFACE};
|
|
62
|
+
border-top: solid {COLOR_BORDER};
|
|
63
|
+
}}
|
|
64
|
+
|
|
65
|
+
Input {{
|
|
66
|
+
background: {COLOR_SURFACE};
|
|
67
|
+
border: none;
|
|
68
|
+
width: 1fr;
|
|
69
|
+
padding: 0 2;
|
|
70
|
+
color: {COLOR_ACCENT};
|
|
71
|
+
}}
|
|
72
|
+
|
|
73
|
+
Input:focus {{
|
|
74
|
+
border: none;
|
|
75
|
+
}}
|
|
76
|
+
|
|
77
|
+
#send-btn {{
|
|
78
|
+
width: 10;
|
|
79
|
+
background: {COLOR_ACCENT};
|
|
80
|
+
color: {COLOR_BG};
|
|
81
|
+
border: none;
|
|
82
|
+
text-style: bold;
|
|
83
|
+
height: 1;
|
|
84
|
+
margin: 1 1 0 0;
|
|
85
|
+
}}
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def check_for_updates():
|
|
89
|
+
try:
|
|
90
|
+
url = f"https://pypi.org/pypi/{APP_NAME}/json"
|
|
91
|
+
with urllib.request.urlopen(url, timeout=1.5) as response:
|
|
92
|
+
data = json.load(response)
|
|
93
|
+
latest_version = data['info']['version']
|
|
94
|
+
def v_to_tuple(v): return tuple(map(int, v.split('.')))
|
|
95
|
+
if v_to_tuple(latest_version) > v_to_tuple(VERSION):
|
|
96
|
+
subprocess.check_call([sys.executable, "-m", "pip", "install", "-U", APP_NAME])
|
|
97
|
+
os.execv(sys.executable, ['python'] + sys.argv)
|
|
98
|
+
except Exception:
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
class FiftyNineChat(App): # Sınıf ismini de güncelledik
|
|
102
|
+
CSS = CSS
|
|
103
|
+
|
|
104
|
+
BINDINGS = [
|
|
105
|
+
("ctrl+c", "quit", "Exit"),
|
|
106
|
+
("ctrl+r", "new_room", "New Room"),
|
|
107
|
+
("ctrl+l", "copy_invite", "Copy Invitation"),
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
def __init__(self, room_id: Optional[str] = None, is_new: bool = False):
|
|
111
|
+
super().__init__()
|
|
112
|
+
self.supabase: Optional[Client] = None
|
|
113
|
+
self.room_id: str = room_id or ""
|
|
114
|
+
self.nickname: str = ""
|
|
115
|
+
self.running = True
|
|
116
|
+
self.active_users: int = 1
|
|
117
|
+
self.is_new_session = is_new
|
|
118
|
+
|
|
119
|
+
def compose(self) -> ComposeResult:
|
|
120
|
+
yield Static(id="header")
|
|
121
|
+
yield RichLog(id="chat-log", markup=True, wrap=False)
|
|
122
|
+
with Horizontal(id="input-area"):
|
|
123
|
+
yield Input(placeholder="Type message...", id="message-input")
|
|
124
|
+
yield Button("SEND", id="send-btn")
|
|
125
|
+
yield Footer()
|
|
126
|
+
|
|
127
|
+
def on_mount(self) -> None:
|
|
128
|
+
try:
|
|
129
|
+
self.supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
|
|
130
|
+
self.nickname = self._generate_nickname()
|
|
131
|
+
if not self.room_id:
|
|
132
|
+
self.room_id = self._generate_room_id()
|
|
133
|
+
self.is_new_session = True
|
|
134
|
+
|
|
135
|
+
self._update_header()
|
|
136
|
+
asyncio.create_task(self._watch_and_refresh())
|
|
137
|
+
asyncio.create_task(self._mark_as_read())
|
|
138
|
+
asyncio.create_task(self._presence_heartbeat())
|
|
139
|
+
|
|
140
|
+
if self.is_new_session:
|
|
141
|
+
self.action_copy_invite()
|
|
142
|
+
|
|
143
|
+
except Exception as e:
|
|
144
|
+
self.notify(f"Init failed: {e}", severity="error")
|
|
145
|
+
|
|
146
|
+
def _generate_nickname(self) -> str:
|
|
147
|
+
return f"{random.choice(['Cold','Swift','Pure','Thin'])}{random.choice(['Grid','Line','Type','Form'])}"
|
|
148
|
+
|
|
149
|
+
def _generate_room_id(self) -> str:
|
|
150
|
+
return ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))
|
|
151
|
+
|
|
152
|
+
def _update_header(self):
|
|
153
|
+
presence_info = f" [dim]│[/] [bold {COLOR_SUCCESS}]●[/] {self.active_users} ONLINE"
|
|
154
|
+
self.query_one("#header").update(f"ROOM: [bold]{self.room_id}[/] [dim]│[/] USER: [bold]{self.nickname}[/]{presence_info}")
|
|
155
|
+
|
|
156
|
+
async def _presence_heartbeat(self):
|
|
157
|
+
while self.running and self.supabase:
|
|
158
|
+
try:
|
|
159
|
+
res = self.supabase.table('messages').select('nickname').eq('room_id', self.room_id).execute()
|
|
160
|
+
nicknames = {m['nickname'] for m in res.data}
|
|
161
|
+
self.active_users = max(1, len(nicknames))
|
|
162
|
+
self._update_header()
|
|
163
|
+
await asyncio.sleep(10)
|
|
164
|
+
except:
|
|
165
|
+
await asyncio.sleep(10)
|
|
166
|
+
|
|
167
|
+
async def _watch_and_refresh(self):
|
|
168
|
+
while self.running and self.supabase:
|
|
169
|
+
try:
|
|
170
|
+
res = self.supabase.table('messages').select('*').eq('room_id', self.room_id).order('created_at').execute()
|
|
171
|
+
chat_log = self.query_one("#chat-log")
|
|
172
|
+
|
|
173
|
+
now = datetime.now(timezone.utc)
|
|
174
|
+
visible_messages = []
|
|
175
|
+
for m in res.data:
|
|
176
|
+
if m.get('read_at'):
|
|
177
|
+
read_dt = datetime.fromisoformat(m['read_at'].replace('Z', '+00:00'))
|
|
178
|
+
if (now - read_dt).total_seconds() < 59:
|
|
179
|
+
visible_messages.append(m)
|
|
180
|
+
else:
|
|
181
|
+
visible_messages.append(m)
|
|
182
|
+
|
|
183
|
+
chat_log.clear()
|
|
184
|
+
|
|
185
|
+
width = chat_log.content_size.width or 80
|
|
186
|
+
max_msg_width = int(width * 0.8)
|
|
187
|
+
|
|
188
|
+
# --- Hizalanmış Davet Bloğu ---
|
|
189
|
+
invite_cmd = f"pip install -U {APP_NAME} && {CMD_NAME} --join {self.room_id}"
|
|
190
|
+
|
|
191
|
+
chat_log.write(f"[bold {COLOR_TEXT_SECONDARY}]MAGIC JOIN COMMAND:[/]")
|
|
192
|
+
box_top = "┏" + ("━" * (max_msg_width - 2)) + "┓"
|
|
193
|
+
box_mid = "┃ " + invite_cmd.ljust(max_msg_width - 4) + " ┃"
|
|
194
|
+
box_btm = "┗" + ("━" * (max_msg_width - 2)) + "┛"
|
|
195
|
+
|
|
196
|
+
chat_log.write(f"[dim]{box_top}[/]")
|
|
197
|
+
chat_log.write(f"[bold {COLOR_ACCENT}]{box_mid}[/]")
|
|
198
|
+
chat_log.write(f"[dim]{box_btm}[/]")
|
|
199
|
+
chat_log.write(f"[dim]Press Ctrl+L to copy invitation.[/]")
|
|
200
|
+
chat_log.write(f"[dim]{'─' * max_msg_width}[/]\n")
|
|
201
|
+
|
|
202
|
+
for m in visible_messages:
|
|
203
|
+
is_own = m['nickname'] == self.nickname
|
|
204
|
+
time_part = m['created_at'].split('T')[1][:5]
|
|
205
|
+
status = " [dim]○[/]"
|
|
206
|
+
if m.get('read_at'):
|
|
207
|
+
read_dt = datetime.fromisoformat(m['read_at'].replace('Z', '+00:00'))
|
|
208
|
+
rem = max(0, int(59 - (now - read_dt).total_seconds()))
|
|
209
|
+
status = f" [bold {COLOR_SUCCESS}]●[/] [bold {COLOR_ERROR}]{rem}s[/]"
|
|
210
|
+
|
|
211
|
+
user_style = f"bold {COLOR_ACCENT}" if is_own else f"bold {COLOR_TEXT_SECONDARY}"
|
|
212
|
+
indent_size = (width - max_msg_width - 4) if is_own else 0
|
|
213
|
+
indent = " " * indent_size
|
|
214
|
+
prefix = "› " if is_own else ""
|
|
215
|
+
|
|
216
|
+
header = f"{indent}[{user_style}]{prefix}{m['nickname']}[/] [dim]{time_part}[/]{status}"
|
|
217
|
+
content = f"{indent} {m['content']}"
|
|
218
|
+
underline = f"{indent}[dim]{'━' * max_msg_width}[/]]"
|
|
219
|
+
|
|
220
|
+
chat_log.write(header)
|
|
221
|
+
chat_log.write(content)
|
|
222
|
+
chat_log.write(underline)
|
|
223
|
+
chat_log.write("")
|
|
224
|
+
|
|
225
|
+
chat_log.scroll_end(animate=False)
|
|
226
|
+
await asyncio.sleep(0.5)
|
|
227
|
+
except:
|
|
228
|
+
await asyncio.sleep(1)
|
|
229
|
+
|
|
230
|
+
async def _mark_as_read(self):
|
|
231
|
+
while self.running and self.supabase:
|
|
232
|
+
try:
|
|
233
|
+
res = self.supabase.table('messages').select('id, nickname').eq('room_id', self.room_id).is_('read_at', 'null').execute()
|
|
234
|
+
unread = [m['id'] for m in res.data if m['nickname'] != self.nickname]
|
|
235
|
+
if unread:
|
|
236
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
237
|
+
for mid in unread:
|
|
238
|
+
self.supabase.table('messages').update({'read_at': now}).eq('id', mid).execute()
|
|
239
|
+
await asyncio.sleep(1)
|
|
240
|
+
except:
|
|
241
|
+
await asyncio.sleep(2)
|
|
242
|
+
|
|
243
|
+
def _send(self):
|
|
244
|
+
inp = self.query_one(Input)
|
|
245
|
+
content = inp.value.strip()
|
|
246
|
+
if not content or not self.supabase: return
|
|
247
|
+
try:
|
|
248
|
+
self.supabase.table('messages').insert({
|
|
249
|
+
'room_id': self.room_id,
|
|
250
|
+
'nickname': self.nickname,
|
|
251
|
+
'content': content
|
|
252
|
+
}).execute()
|
|
253
|
+
inp.value = ""
|
|
254
|
+
except:
|
|
255
|
+
self.notify("Send failed")
|
|
256
|
+
|
|
257
|
+
def on_input_submitted(self): self._send()
|
|
258
|
+
def on_button_pressed(self): self._send()
|
|
259
|
+
|
|
260
|
+
def action_copy_invite(self):
|
|
261
|
+
invite_cmd = f"pip install -U {APP_NAME} && {CMD_NAME} --join {self.room_id}"
|
|
262
|
+
try:
|
|
263
|
+
pyperclip.copy(invite_cmd)
|
|
264
|
+
self.notify("Invitation copied!", severity="information")
|
|
265
|
+
except:
|
|
266
|
+
self.notify("Failed to copy automatically.", severity="warning")
|
|
267
|
+
|
|
268
|
+
def action_new_room(self):
|
|
269
|
+
self.room_id = self._generate_room_id()
|
|
270
|
+
self.query_one("#chat-log").clear()
|
|
271
|
+
self._update_header()
|
|
272
|
+
self.action_copy_invite()
|
|
273
|
+
|
|
274
|
+
def on_unmount(self) -> None:
|
|
275
|
+
self.running = False
|
|
276
|
+
|
|
277
|
+
def main_func():
|
|
278
|
+
check_for_updates()
|
|
279
|
+
parser = argparse.ArgumentParser()
|
|
280
|
+
parser.add_argument("--join", help="Room ID")
|
|
281
|
+
parser.add_argument("--new", action="store_true", help="Start a new room")
|
|
282
|
+
args = parser.parse_args()
|
|
283
|
+
room = None if args.new else args.join
|
|
284
|
+
app = FiftyNineChat(room_id=room, is_new=args.new)
|
|
285
|
+
app.run()
|
|
286
|
+
|
|
287
|
+
if __name__ == "__main__":
|
|
288
|
+
main_func()
|
59chat-0.1.0/setup.cfg
ADDED
59chat-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="59chat",
|
|
5
|
+
version="0.1.0",
|
|
6
|
+
author="YourName",
|
|
7
|
+
description="59-second zero-trace terminal chat",
|
|
8
|
+
long_description=open("README.md", encoding="utf-8").read(),
|
|
9
|
+
long_description_content_type="text/markdown",
|
|
10
|
+
url="https://github.com/yourusername/59chat",
|
|
11
|
+
packages=find_packages(),
|
|
12
|
+
py_modules=["main"],
|
|
13
|
+
install_requires=[
|
|
14
|
+
"textual>=0.45.0",
|
|
15
|
+
"supabase>=2.3.0",
|
|
16
|
+
"python-dotenv>=1.0.0",
|
|
17
|
+
"pyperclip>=1.8.2",
|
|
18
|
+
],
|
|
19
|
+
entry_points={
|
|
20
|
+
"console_scripts": [
|
|
21
|
+
"59chat=main:main_func",
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
python_requires=">=3.10",
|
|
25
|
+
)
|