textchat 0.0.1__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.
- textchat-0.0.1/PKG-INFO +10 -0
- textchat-0.0.1/pyproject.toml +15 -0
- textchat-0.0.1/setup.cfg +4 -0
- textchat-0.0.1/src/__init__.py +0 -0
- textchat-0.0.1/src/textchat/chat/__init__.py +0 -0
- textchat-0.0.1/src/textchat/chat/client.py +145 -0
- textchat-0.0.1/src/textchat/chat/commands/__init__.py +0 -0
- textchat-0.0.1/src/textchat/chat/ircchat.py +170 -0
- textchat-0.0.1/src/textchat/chat/screens/__init__.py +0 -0
- textchat-0.0.1/src/textchat/chat/screens/irc.py +39 -0
- textchat-0.0.1/src/textchat/chat/screens/quit.py +22 -0
- textchat-0.0.1/src/textchat/chat/screens/settings.py +52 -0
- textchat-0.0.1/src/textchat/chat/widgets/__init__.py +0 -0
- textchat-0.0.1/src/textchat/chat/widgets/address.py +7 -0
- textchat-0.0.1/src/textchat/chat/widgets/channels.py +4 -0
- textchat-0.0.1/src/textchat/chat/widgets/nickname.py +4 -0
- textchat-0.0.1/src/textchat/chat/widgets/password.py +4 -0
- textchat-0.0.1/src/textchat/db/base.py +47 -0
- textchat-0.0.1/src/textchat/db/db.py +47 -0
- textchat-0.0.1/src/textchat/db/model.py +1 -0
- textchat-0.0.1/src/textchat/utils/channels.py +5 -0
- textchat-0.0.1/src/textchat.egg-info/PKG-INFO +10 -0
- textchat-0.0.1/src/textchat.egg-info/SOURCES.txt +25 -0
- textchat-0.0.1/src/textchat.egg-info/dependency_links.txt +1 -0
- textchat-0.0.1/src/textchat.egg-info/entry_points.txt +2 -0
- textchat-0.0.1/src/textchat.egg-info/requires.txt +5 -0
- textchat-0.0.1/src/textchat.egg-info/top_level.txt +2 -0
textchat-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: textchat
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Author-email: Matthew Blau <mrb1105@gmail.com>
|
|
5
|
+
Requires-Python: >=3.8
|
|
6
|
+
Requires-Dist: textual>=0.66.0
|
|
7
|
+
Requires-Dist: irc>=20.4.0
|
|
8
|
+
Requires-Dist: textual-autocomplete>=2.1.0b0
|
|
9
|
+
Requires-Dist: SQLAlchemy>=2.0.30
|
|
10
|
+
Requires-Dist: aiosqlite>=0.20.0
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "textchat"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
authors = [
|
|
5
|
+
{ name = "Matthew Blau", email = "mrb1105@gmail.com" },
|
|
6
|
+
]
|
|
7
|
+
dependencies = ["textual >=0.66.0", "irc >=20.4.0", "textual-autocomplete >=2.1.0b0","SQLAlchemy >=2.0.30", "aiosqlite >=0.20.0"]
|
|
8
|
+
requires-python = ">=3.8"
|
|
9
|
+
|
|
10
|
+
[build-system]
|
|
11
|
+
requires = ["setuptools>=61.0"]
|
|
12
|
+
build-backend = "setuptools.build_meta"
|
|
13
|
+
|
|
14
|
+
[project.scripts]
|
|
15
|
+
textchat = "textchat.chat:ircchat"
|
textchat-0.0.1/setup.cfg
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
3
|
+
import sys
|
|
4
|
+
import threading
|
|
5
|
+
from textual import work
|
|
6
|
+
from textual.widgets import Label, TabbedContent, TabPane
|
|
7
|
+
from irc.client import SimpleIRCClient
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
import time
|
|
10
|
+
import ib3.auth
|
|
11
|
+
import ib3.connection
|
|
12
|
+
import irc.bot
|
|
13
|
+
from ..utils.channels import load_channels
|
|
14
|
+
from ..db.db import add_channel_to_list
|
|
15
|
+
|
|
16
|
+
#channels = load_channels()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class IRCApp(SimpleIRCClient):
|
|
21
|
+
def __init__(self,app, server_list, nickname, realname, ident_password, channels=load_channels()):
|
|
22
|
+
super().__init__()
|
|
23
|
+
self.app = app
|
|
24
|
+
self.server_list = server_list
|
|
25
|
+
self.server_name = server_list[0][0]
|
|
26
|
+
self.server_port = server_list[0][1]
|
|
27
|
+
self.nickname = nickname
|
|
28
|
+
self.realname= realname
|
|
29
|
+
self.ident_password = ident_password
|
|
30
|
+
self.channels = channels
|
|
31
|
+
self._thread_pool = ThreadPoolExecutor(max_workers=1)
|
|
32
|
+
|
|
33
|
+
def start_event_loop(self):
|
|
34
|
+
print("connecting..")
|
|
35
|
+
self._thread_pool.submit(self._irc_event_loop)
|
|
36
|
+
print("actually connecting")
|
|
37
|
+
|
|
38
|
+
def _irc_event_loop(self):
|
|
39
|
+
self.start()
|
|
40
|
+
print("really connecting!!!!")
|
|
41
|
+
def stop(self):
|
|
42
|
+
self._thread_pool.shutdown(wait=False)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def start(self):
|
|
46
|
+
self.connect(
|
|
47
|
+
self.server_name,
|
|
48
|
+
self.server_port,
|
|
49
|
+
self.nickname,
|
|
50
|
+
self.ident_password,
|
|
51
|
+
self.realname,
|
|
52
|
+
)
|
|
53
|
+
super().start()
|
|
54
|
+
|
|
55
|
+
def on_welcome(self, connection, event):
|
|
56
|
+
#self.connection.privmsg("nickserv", f'identify {self.password}')
|
|
57
|
+
for channel in self.channels:
|
|
58
|
+
connection.join(channel)
|
|
59
|
+
#await add_channel_to_list(channel)
|
|
60
|
+
time.sleep(7)
|
|
61
|
+
self.app.notify("Connected!")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async def _intercept_join(self, message):
|
|
65
|
+
self.tab = self.app.query_one(TabbedContent).active_pane
|
|
66
|
+
if message.startswith("/join"):
|
|
67
|
+
channel = message.split()[1]
|
|
68
|
+
if not channel in self.channels:
|
|
69
|
+
self.connection.join(channel)
|
|
70
|
+
self.channels.append(channel)
|
|
71
|
+
await add_channel_to_list(channel)
|
|
72
|
+
self.app.query_one(TabbedContent).add_pane(TabPane(channel,
|
|
73
|
+
Label(),
|
|
74
|
+
name=channel,
|
|
75
|
+
id=f'{channel.replace("#", "").lower()}'))
|
|
76
|
+
else:
|
|
77
|
+
self.app.notify(f"Already in {channel}!")
|
|
78
|
+
return True
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
def _intercept_part(self, message):
|
|
82
|
+
self.tab = self.app.query_one(TabbedContent).active_pane
|
|
83
|
+
if message.startswith("/part"):
|
|
84
|
+
channel = message.split()[1]
|
|
85
|
+
self.connection.part(channel)
|
|
86
|
+
self.channels.remove(channel)
|
|
87
|
+
self.app.query_one(TabbedContent).remove_pane(f'{channel.replace("#", "").lower()}')
|
|
88
|
+
return True
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
def on_pubmsg(self, connection, event):
|
|
92
|
+
print(event)
|
|
93
|
+
sender = event.source.nick
|
|
94
|
+
message = event.arguments[0]
|
|
95
|
+
channel = event.target
|
|
96
|
+
now = datetime.now()
|
|
97
|
+
if now.minute <= 9:
|
|
98
|
+
self.app.handle_irc_message(f'{now.hour}:0{now.minute}', channel, sender, message, classes=None)
|
|
99
|
+
else:
|
|
100
|
+
self.app.handle_irc_message(f'{now.hour}:{now.minute}', channel, f'{sender}', message, classes=None)
|
|
101
|
+
|
|
102
|
+
def on_privmsg(self, connection, event):
|
|
103
|
+
message = event.arguments[0]
|
|
104
|
+
user = event.source.nick
|
|
105
|
+
now = datetime.now()
|
|
106
|
+
if now.minute <= 9:
|
|
107
|
+
self.app.handle_private_message(f'{now.hour}:0{now.minute}', user, message, classes=None)
|
|
108
|
+
else:
|
|
109
|
+
self.app.handle_private_message(f'{now.hour}:{now.minute}', user, message, classes=None)
|
|
110
|
+
|
|
111
|
+
def on_ctcp(self, connection, event):
|
|
112
|
+
sender = event.source.nick
|
|
113
|
+
message = event.arguments[1]
|
|
114
|
+
channel = event.target
|
|
115
|
+
now = datetime.now()
|
|
116
|
+
classes = "italics"
|
|
117
|
+
if now.minute <= 9:
|
|
118
|
+
|
|
119
|
+
self.app.handle_irc_message(f'{now.hour}:0{now.minute}', channel, f'{sender}', message, classes)
|
|
120
|
+
else:
|
|
121
|
+
self.app.handle_irc_message(f'{now.hour}:{now.minute}', channel, f'{sender}', message, classes)
|
|
122
|
+
|
|
123
|
+
def on_namreply(self, connection, event):
|
|
124
|
+
channel = event.arguments[1]
|
|
125
|
+
user_list = event.arguments[2].split()
|
|
126
|
+
self.app.add_to_tree(channel, user_list)
|
|
127
|
+
|
|
128
|
+
def on_join(self, connection, event):
|
|
129
|
+
sender = event.source.nick
|
|
130
|
+
try:
|
|
131
|
+
message = event.arguments[0]
|
|
132
|
+
except IndexError:
|
|
133
|
+
message = "has joined"
|
|
134
|
+
channel = event.target
|
|
135
|
+
now = datetime.now()
|
|
136
|
+
classes = "italics"
|
|
137
|
+
if now.minute <= 9 and self.nickname != sender:
|
|
138
|
+
self.app.handle_irc_message(f'{now.hour}:0{now.minute}', channel, f'{sender}', message, classes)
|
|
139
|
+
elif self.nickname != sender:
|
|
140
|
+
self.app.handle_irc_message(f'{now.hour}:{now.minute}', channel, f'{sender}', message, classes)
|
|
141
|
+
|
|
142
|
+
def on_disconnect(self):
|
|
143
|
+
self._stop_event.set()
|
|
144
|
+
|
|
145
|
+
|
|
File without changes
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import time
|
|
4
|
+
try:
|
|
5
|
+
from .screens.quit import QuitScreen
|
|
6
|
+
except:
|
|
7
|
+
from textchat.chat.screens.quit import QuitScreen
|
|
8
|
+
try:
|
|
9
|
+
from .screens.settings import SettingsScreen
|
|
10
|
+
except:
|
|
11
|
+
from textchat.chat.screens.settings import SettingsScreen
|
|
12
|
+
from textchat.db.db import get_password, get_port, get_server_address, get_username
|
|
13
|
+
from textual import work
|
|
14
|
+
from textual import on
|
|
15
|
+
from textual.app import App
|
|
16
|
+
from textual.command import Command
|
|
17
|
+
from textual.widgets import Input, Label, TabbedContent, TabPane,Tree
|
|
18
|
+
from textual.worker import get_current_worker
|
|
19
|
+
try:
|
|
20
|
+
from .client import IRCApp
|
|
21
|
+
except:
|
|
22
|
+
from textchat.chat.client import IRCApp
|
|
23
|
+
try:
|
|
24
|
+
from .screens.irc import IRCScreen
|
|
25
|
+
except:
|
|
26
|
+
from textchat.chat.screens.irc import IRCScreen
|
|
27
|
+
try:
|
|
28
|
+
from .screens.settings import SettingsScreen
|
|
29
|
+
except:
|
|
30
|
+
from textchat.chat.screens.settings import SettingsScreen
|
|
31
|
+
try:
|
|
32
|
+
from ..db.base import create_table
|
|
33
|
+
except:
|
|
34
|
+
from textchat.db.base import create_table
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
from ..utils.channels import load_channels
|
|
38
|
+
except:
|
|
39
|
+
from textchat.utils.channels import load_channels
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TextChat(App):
|
|
43
|
+
def __init__(self):
|
|
44
|
+
super().__init__()
|
|
45
|
+
CSS_PATH = 'irc.tcss'
|
|
46
|
+
SCREENS = {"irc": IRCScreen(), "settings":SettingsScreen() }
|
|
47
|
+
BINDINGS = [("ctrl+h", "return_home", "Home"),
|
|
48
|
+
("ctrl+s", "open_settings", "Settings"),
|
|
49
|
+
("ctrl+q", "request_quit", "Quit"),
|
|
50
|
+
("ctrl+d", "toggle_dark", "Toggle dark mode")
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async def on_mount(self) -> None:
|
|
56
|
+
self.channel_list = None
|
|
57
|
+
self.users = set()
|
|
58
|
+
self.username = await get_username()
|
|
59
|
+
self.port = await get_port()
|
|
60
|
+
self.server_address = await get_server_address()
|
|
61
|
+
self.password = await get_password()
|
|
62
|
+
await create_table()
|
|
63
|
+
|
|
64
|
+
existing_channels = await load_channels()
|
|
65
|
+
if not existing_channels:
|
|
66
|
+
self.app.push_screen(self.SCREENS['settings'])
|
|
67
|
+
else:
|
|
68
|
+
self.app.push_screen(self.SCREENS['irc'])
|
|
69
|
+
self.irc_client = IRCApp(self,
|
|
70
|
+
server_list=[(self.server_address, self.port)],
|
|
71
|
+
nickname=self.username,
|
|
72
|
+
realname=self.username,
|
|
73
|
+
ident_password=self.password,
|
|
74
|
+
channels=existing_channels,
|
|
75
|
+
)
|
|
76
|
+
self.irc_client.start_event_loop()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def action_toggle_dark(self) -> None:
|
|
81
|
+
"""An action to toggle dark mode."""
|
|
82
|
+
self.dark = not self.dark
|
|
83
|
+
|
|
84
|
+
def action_request_quit(self) -> None:
|
|
85
|
+
|
|
86
|
+
def check_quit(quit: bool) -> None:
|
|
87
|
+
if quit:
|
|
88
|
+
self.app.exit()
|
|
89
|
+
self.irc_client.stop()
|
|
90
|
+
|
|
91
|
+
self.push_screen(QuitScreen(), check_quit)
|
|
92
|
+
|
|
93
|
+
def action_open_settings(self) -> None:
|
|
94
|
+
self.push_screen(SettingsScreen())
|
|
95
|
+
|
|
96
|
+
def action_return_home(self) -> None:
|
|
97
|
+
self.push_screen(self.SCREENS['irc'])
|
|
98
|
+
|
|
99
|
+
@on(Input.Submitted)
|
|
100
|
+
async def send_message(self, event) -> None:
|
|
101
|
+
self.query_one(Input).value = ""
|
|
102
|
+
self.tab = self.query_one(TabbedContent).active_pane
|
|
103
|
+
now = datetime.now()
|
|
104
|
+
if now.minute <= 9 and not await self.irc_client._intercept_join(event.value):
|
|
105
|
+
self.irc_client.connection.privmsg(self.tab.name, event.value)
|
|
106
|
+
self.tab.mount(Label(f'{now.hour}:0{now.minute} <{self.irc_client.nickname}> {event.value}'))
|
|
107
|
+
elif not await self.irc_client._intercept_join(event.value):
|
|
108
|
+
self.irc_client.connection.privmsg(self.tab.name, event.value)
|
|
109
|
+
self.tab.mount(Label(f'{now.hour}:{now.minute} <{self.irc_client.nickname}> {event.value}'))
|
|
110
|
+
|
|
111
|
+
def get_channel_list(self):
|
|
112
|
+
if self.app.channel_list is None:
|
|
113
|
+
return None
|
|
114
|
+
else:
|
|
115
|
+
return self.app.channel_list
|
|
116
|
+
|
|
117
|
+
def irc_message(self, time, channel, sender, message,classes):
|
|
118
|
+
self.tab = self.app.query_one(TabbedContent).get_pane(channel.replace("#", "").lower())
|
|
119
|
+
if self.irc_client.nickname in message:
|
|
120
|
+
self.app.notify(f"{time} <{sender}> {message}", title=channel)
|
|
121
|
+
self.tab.mount(Label(f"{time} <{sender}> {message}", classes="highlight"))
|
|
122
|
+
else:
|
|
123
|
+
self.tab.mount(Label(f'{time} <{sender}> {message}', classes=classes))
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def received_private_message(self, time, sender, message, classes):
|
|
127
|
+
self.tab = self.app.query_one(TabbedContent).add_pane(TabPane(sender,
|
|
128
|
+
Label(),
|
|
129
|
+
name=sender,
|
|
130
|
+
id=f'{sender}'))
|
|
131
|
+
self.tab = self.app.query_one(TabbedContent).get_pane(f'{sender}')
|
|
132
|
+
self.tab.mount(Label(f'{time} <{sender}> {message}', classes=classes))
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def add_to_tree(self, channel, user_list):
|
|
136
|
+
tree = self.app.query_one(Tree)
|
|
137
|
+
self.app.channel_list = self.app.get_channel_list()
|
|
138
|
+
if self.app.channel_list is not None and channel != self.app.channel_list.data:
|
|
139
|
+
channels_list = tree.root.add(channel, data=channel)
|
|
140
|
+
self.app.channel_list = channels_list
|
|
141
|
+
elif self.channel_list is None:
|
|
142
|
+
channels_list = tree.root.add(channel, data=channel)
|
|
143
|
+
self.app.channel_list = channels_list
|
|
144
|
+
for user in user_list:
|
|
145
|
+
self.app.channel_list.add_leaf(user)
|
|
146
|
+
for user in user_list:
|
|
147
|
+
self.app.users.add(user)
|
|
148
|
+
else:
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
@work(exclusive=True)
|
|
152
|
+
async def handle_irc_message(self, time, channel, sender, message, classes):
|
|
153
|
+
worker = get_current_worker()
|
|
154
|
+
if not worker.is_cancelled:
|
|
155
|
+
self.irc_message(time, channel, sender, message, classes)
|
|
156
|
+
else:
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
@work(exclusive=True)
|
|
160
|
+
async def handle_private_message(self, time, sender, message, classes):
|
|
161
|
+
worker = get_current_worker()
|
|
162
|
+
if not worker.is_cancelled:
|
|
163
|
+
self.received_private_message(time, sender, message, classes)
|
|
164
|
+
else:
|
|
165
|
+
pass
|
|
166
|
+
async def on_shutdown(self):
|
|
167
|
+
self.irc_client.on_disconnect()
|
|
168
|
+
|
|
169
|
+
app = TextChat()
|
|
170
|
+
app.run()
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from textchat.utils.channels import load_channels
|
|
3
|
+
from textual.app import ComposeResult
|
|
4
|
+
from textual.widgets import Input, Tree, Footer, TabbedContent, TabPane, Label
|
|
5
|
+
from textual.screen import Screen
|
|
6
|
+
from textual_autocomplete import DropdownItem, InputState
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class IRCScreen(Screen):
|
|
10
|
+
try:
|
|
11
|
+
channels = asyncio.run(load_channels())
|
|
12
|
+
except:
|
|
13
|
+
channels = False
|
|
14
|
+
def compose(self) -> ComposeResult:
|
|
15
|
+
def get_items(input_state: InputState):
|
|
16
|
+
items = []
|
|
17
|
+
for user in self.app.users:
|
|
18
|
+
items.append(
|
|
19
|
+
DropdownItem(user.replace("+", "").replace("@", ""))
|
|
20
|
+
)
|
|
21
|
+
matches = [c for c in items if input_state.value.lower() in c.main.plain.lower()]
|
|
22
|
+
ordered = sorted(matches, key=lambda v: input_state.value.lower() in v.main.plain.lower(), reverse=True)
|
|
23
|
+
return ordered
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
with TabbedContent() as self.tabbed_content:
|
|
27
|
+
if not self.channels:
|
|
28
|
+
with TabPane("uh oh!"):
|
|
29
|
+
yield(Label("Please visit the settings tab and restart when finished to see your channels here!"))
|
|
30
|
+
else:
|
|
31
|
+
for name in self.channels:
|
|
32
|
+
tab_id = f'{name.replace("#", "")}'
|
|
33
|
+
with TabPane(name, name=name, id=tab_id.lower()):
|
|
34
|
+
yield(Label())
|
|
35
|
+
tree: Tree[dict] = Tree("Channels", id="sidebar")
|
|
36
|
+
yield tree
|
|
37
|
+
yield Input(id="chat-input")
|
|
38
|
+
#yield AutoComplete(Input(placeholder=f"Enter your message", id="chat-input"), Dropdown(items=get_items))
|
|
39
|
+
yield Footer()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from textual.app import ComposeResult
|
|
2
|
+
from textual.containers import Grid
|
|
3
|
+
from textual.screen import ModalScreen
|
|
4
|
+
from textual.widgets import Button, Label
|
|
5
|
+
|
|
6
|
+
class QuitScreen(ModalScreen[bool]):
|
|
7
|
+
"""Screen with a dialog to quit."""
|
|
8
|
+
|
|
9
|
+
def compose(self) -> ComposeResult:
|
|
10
|
+
yield Grid(
|
|
11
|
+
Label("Are you sure you want to quit?", id="question"),
|
|
12
|
+
Button("Quit", variant="error", id="quit"),
|
|
13
|
+
Button("Cancel", variant="primary", id="cancel"),
|
|
14
|
+
id="dialog",
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
18
|
+
if event.button.id == "quit":
|
|
19
|
+
self.dismiss(True)
|
|
20
|
+
|
|
21
|
+
else:
|
|
22
|
+
self.dismiss(False)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from textchat.chat.client import IRCApp
|
|
2
|
+
from textchat.db.base import create_table
|
|
3
|
+
from textual.app import ComposeResult
|
|
4
|
+
from textual.screen import Screen
|
|
5
|
+
from textual.widgets import Input, Footer, Button
|
|
6
|
+
from textual.containers import Vertical
|
|
7
|
+
from ..widgets.address import ServerAddress, ServerPort
|
|
8
|
+
from ..widgets.nickname import Nickname
|
|
9
|
+
from ..widgets.channels import Channels
|
|
10
|
+
from ..widgets.password import Password
|
|
11
|
+
from ...db.db import add_channel_to_list, add_server_info
|
|
12
|
+
from ...utils.channels import load_channels
|
|
13
|
+
|
|
14
|
+
class SettingsScreen(Screen):
|
|
15
|
+
|
|
16
|
+
def compose(self) -> ComposeResult:
|
|
17
|
+
yield Vertical(
|
|
18
|
+
ServerAddress(placeholder="Server address"),
|
|
19
|
+
ServerPort(placeholder="port", type="integer"),
|
|
20
|
+
Nickname(placeholder="nickname"),
|
|
21
|
+
Channels(placeholder="channels to join (seperate by comma)"),
|
|
22
|
+
Password(placeholder="password", password=True),
|
|
23
|
+
Button(label="Save", id="save"),
|
|
24
|
+
classes="column"
|
|
25
|
+
)
|
|
26
|
+
yield Footer()
|
|
27
|
+
async def on_mount(self):
|
|
28
|
+
await create_table()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def on_button_pressed(self, event: Button.Pressed):
|
|
32
|
+
if event.button.id =="save":
|
|
33
|
+
server_address = self.query_one(ServerAddress).value
|
|
34
|
+
server_port = self.query_one(ServerPort).value
|
|
35
|
+
nickname = self.query_one(Nickname).value
|
|
36
|
+
channels = self.query_one(Channels).value
|
|
37
|
+
password = self.query_one(Password).value
|
|
38
|
+
await add_server_info(server_address, server_port, nickname, password)
|
|
39
|
+
existing_channnels = await load_channels()
|
|
40
|
+
for channel in channels.split(","):
|
|
41
|
+
if channel not in existing_channnels:
|
|
42
|
+
await add_channel_to_list(channel)
|
|
43
|
+
else:
|
|
44
|
+
pass
|
|
45
|
+
self.irc_client = IRCApp(self,
|
|
46
|
+
server_list=[(server_address, server_port)],
|
|
47
|
+
nickname=nickname,
|
|
48
|
+
realname=nickname,
|
|
49
|
+
ident_password=password,
|
|
50
|
+
channels=channels,
|
|
51
|
+
)
|
|
52
|
+
self.irc_client.start_event_loop()
|
|
File without changes
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from sqlalchemy import Column, Integer, String
|
|
2
|
+
from sqlalchemy.ext.asyncio import create_async_engine
|
|
3
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
4
|
+
from sqlalchemy.ext.asyncio import async_sessionmaker
|
|
5
|
+
from sqlalchemy.sql.schema import MetaData
|
|
6
|
+
import asyncio
|
|
7
|
+
engine = create_async_engine(
|
|
8
|
+
"sqlite+aiosqlite:///textchat.db", echo=False,)
|
|
9
|
+
Session = async_sessionmaker(
|
|
10
|
+
bind=engine, expire_on_commit=False)
|
|
11
|
+
metadata = MetaData()
|
|
12
|
+
from sqlalchemy.orm import DeclarativeBase
|
|
13
|
+
|
|
14
|
+
class Base(DeclarativeBase):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Channels(Base):
|
|
19
|
+
__tablename__ = 'channels'
|
|
20
|
+
|
|
21
|
+
id = Column(Integer, primary_key=True,
|
|
22
|
+
autoincrement=True)
|
|
23
|
+
channel_name = Column(String, unique=True)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def __init__(self, channel_name):
|
|
27
|
+
self.channel_name = channel_name
|
|
28
|
+
|
|
29
|
+
class ServerInfo(Base):
|
|
30
|
+
__tablename__ = 'server'
|
|
31
|
+
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
32
|
+
server_address = Column(String, unique=True)
|
|
33
|
+
port = Column(Integer, unique=False)
|
|
34
|
+
nickname = Column(String, unique=False)
|
|
35
|
+
password = Column(String, unique=False)
|
|
36
|
+
|
|
37
|
+
def __init__(self, server_address, port, nickname, password):
|
|
38
|
+
self.server_address = server_address
|
|
39
|
+
self.port = port
|
|
40
|
+
self.nickname = nickname
|
|
41
|
+
self.password = password
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def create_table():
|
|
45
|
+
async with engine.begin() as conn:
|
|
46
|
+
await conn.run_sync(Base.metadata.create_all)
|
|
47
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from sqlalchemy import select
|
|
2
|
+
from .base import Session, Channels, ServerInfo
|
|
3
|
+
|
|
4
|
+
async def get_channels():
|
|
5
|
+
async with Session() as session:
|
|
6
|
+
channels = await session.execute(select(Channels))
|
|
7
|
+
await session.commit()
|
|
8
|
+
channel_names = channels.scalars().all()
|
|
9
|
+
return channel_names
|
|
10
|
+
|
|
11
|
+
async def add_channel_to_list(channel):
|
|
12
|
+
async with Session() as session:
|
|
13
|
+
channel_name = Channels(channel_name=channel)
|
|
14
|
+
session.add(channel_name)
|
|
15
|
+
await session.commit()
|
|
16
|
+
return channel_name
|
|
17
|
+
|
|
18
|
+
async def add_server_info(server_address, port, nickname, password):
|
|
19
|
+
async with Session() as session:
|
|
20
|
+
server_info = ServerInfo(server_address, port, nickname, password)
|
|
21
|
+
session.add(server_info)
|
|
22
|
+
await session.commit()
|
|
23
|
+
return server_info
|
|
24
|
+
|
|
25
|
+
async def get_username():
|
|
26
|
+
async with Session() as session:
|
|
27
|
+
username = await session.execute(select(ServerInfo.nickname))
|
|
28
|
+
await session.commit()
|
|
29
|
+
return username.scalars().first()
|
|
30
|
+
|
|
31
|
+
async def get_password():
|
|
32
|
+
async with Session() as session:
|
|
33
|
+
password = await session.execute(select(ServerInfo.password))
|
|
34
|
+
await session.commit()
|
|
35
|
+
return password.scalars().first()
|
|
36
|
+
|
|
37
|
+
async def get_port():
|
|
38
|
+
async with Session() as session:
|
|
39
|
+
port = await session.execute(select(ServerInfo.port))
|
|
40
|
+
await session.commit()
|
|
41
|
+
return port.scalars().first()
|
|
42
|
+
|
|
43
|
+
async def get_server_address():
|
|
44
|
+
async with Session() as session:
|
|
45
|
+
server_address = await session.execute(select(ServerInfo.server_address))
|
|
46
|
+
await session.commit()
|
|
47
|
+
return server_address.scalars().first()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: textchat
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Author-email: Matthew Blau <mrb1105@gmail.com>
|
|
5
|
+
Requires-Python: >=3.8
|
|
6
|
+
Requires-Dist: textual>=0.66.0
|
|
7
|
+
Requires-Dist: irc>=20.4.0
|
|
8
|
+
Requires-Dist: textual-autocomplete>=2.1.0b0
|
|
9
|
+
Requires-Dist: SQLAlchemy>=2.0.30
|
|
10
|
+
Requires-Dist: aiosqlite>=0.20.0
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
pyproject.toml
|
|
2
|
+
src/__init__.py
|
|
3
|
+
src/textchat.egg-info/PKG-INFO
|
|
4
|
+
src/textchat.egg-info/SOURCES.txt
|
|
5
|
+
src/textchat.egg-info/dependency_links.txt
|
|
6
|
+
src/textchat.egg-info/entry_points.txt
|
|
7
|
+
src/textchat.egg-info/requires.txt
|
|
8
|
+
src/textchat.egg-info/top_level.txt
|
|
9
|
+
src/textchat/chat/__init__.py
|
|
10
|
+
src/textchat/chat/client.py
|
|
11
|
+
src/textchat/chat/ircchat.py
|
|
12
|
+
src/textchat/chat/commands/__init__.py
|
|
13
|
+
src/textchat/chat/screens/__init__.py
|
|
14
|
+
src/textchat/chat/screens/irc.py
|
|
15
|
+
src/textchat/chat/screens/quit.py
|
|
16
|
+
src/textchat/chat/screens/settings.py
|
|
17
|
+
src/textchat/chat/widgets/__init__.py
|
|
18
|
+
src/textchat/chat/widgets/address.py
|
|
19
|
+
src/textchat/chat/widgets/channels.py
|
|
20
|
+
src/textchat/chat/widgets/nickname.py
|
|
21
|
+
src/textchat/chat/widgets/password.py
|
|
22
|
+
src/textchat/db/base.py
|
|
23
|
+
src/textchat/db/db.py
|
|
24
|
+
src/textchat/db/model.py
|
|
25
|
+
src/textchat/utils/channels.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|