mirror of
https://github.com/paul-nameless/tg
synced 2024-11-22 03:43:19 +00:00
Start to implement ranger like keybindings and ui logic
Add notifications Use correct colors
This commit is contained in:
parent
c557fcadbb
commit
4986d39987
4 changed files with 199 additions and 53 deletions
152
controller.py
152
controller.py
|
@ -1,4 +1,5 @@
|
|||
import logging
|
||||
import os
|
||||
import threading
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -23,42 +24,117 @@ class Controller:
|
|||
self.model.get_chats()
|
||||
)
|
||||
msgs = self.model.get_current_msgs()
|
||||
self.view.draw_msgs(msgs)
|
||||
self.view.draw_msgs(self.model.get_current_msg(), msgs)
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self.handle()
|
||||
except Exception as e:
|
||||
logger.exception('Error happened on run')
|
||||
|
||||
# def send_msg(self):
|
||||
# import curses
|
||||
# # curses.nocbreak()
|
||||
# curses.echo()
|
||||
# curses.curs_set(1)
|
||||
|
||||
# buff = ''
|
||||
# while True:
|
||||
# key = self.view.get_key(
|
||||
# self.view.chats.h, self.view.chats.w)
|
||||
# logger.info('Pressed in send msg: %s', key)
|
||||
# if key == '^J':
|
||||
# break
|
||||
# elif key == '^G':
|
||||
# # curses.cbreak()
|
||||
# # curses.noecho()
|
||||
# # self.view.chats.win.refresh()
|
||||
# buff = ''
|
||||
# break
|
||||
# buff += key
|
||||
|
||||
# logger.info('Sending msg: %s', buff)
|
||||
# curses.cbreak()
|
||||
# curses.noecho()
|
||||
# curses.curs_set(0)
|
||||
|
||||
# chat_id = self.model.get_current_chat_id()
|
||||
# self.model.send_msg(chat_id, buff)
|
||||
# self.view.draw_chats(
|
||||
# self.model.current_chat,
|
||||
# self.model.get_chats()
|
||||
# )
|
||||
# msgs = self.model.get_current_msgs()
|
||||
# self.view.draw_msgs(msgs)
|
||||
|
||||
def handle_msgs(self):
|
||||
while True:
|
||||
key = self.view.get_key()
|
||||
|
||||
key = self.view.get_key(self.view.chats.h, self.view.chats.w)
|
||||
logger.info('Pressed key: %s', key)
|
||||
if key == '/q':
|
||||
if key == 'q':
|
||||
return 'QUIT'
|
||||
elif key == ']':
|
||||
if self.model.next_chat():
|
||||
self.refresh_chats()
|
||||
elif key == '[':
|
||||
if self.model.prev_chat():
|
||||
self.refresh_chats()
|
||||
elif key == 'j':
|
||||
if self.model.next_msg():
|
||||
self.refresh_msgs()
|
||||
elif key == 'k':
|
||||
if self.model.prev_msg():
|
||||
self.refresh_msgs()
|
||||
elif key == 'e':
|
||||
# edit msg
|
||||
pass
|
||||
elif key == 'r':
|
||||
# reply to this msg
|
||||
# print to status line
|
||||
pass
|
||||
elif key == 'i':
|
||||
# write new message
|
||||
pass
|
||||
elif key == 'h':
|
||||
return 'BACK'
|
||||
|
||||
def handle(self):
|
||||
self.handle_chats()
|
||||
|
||||
def refresh_chats(self):
|
||||
self.view.draw_chats(
|
||||
self.model.current_chat,
|
||||
self.model.get_chats()
|
||||
)
|
||||
msgs = self.model.get_current_msgs()
|
||||
self.view.draw_msgs(self.model.get_current_msg(), msgs)
|
||||
|
||||
def refresh_msgs(self):
|
||||
msgs = self.model.get_current_msgs()
|
||||
self.view.draw_msgs(self.model.get_current_msg(), msgs)
|
||||
|
||||
def handle_chats(self):
|
||||
while True:
|
||||
|
||||
key = self.view.get_key(self.view.chats.h, self.view.chats.w)
|
||||
logger.info('Pressed key: %s', key)
|
||||
if key == 'q':
|
||||
return
|
||||
elif key == '/j':
|
||||
elif key == 'l':
|
||||
rc = self.handle_msgs()
|
||||
if rc == 'QUIT':
|
||||
return
|
||||
|
||||
elif key == 'j':
|
||||
is_changed = self.model.next_chat()
|
||||
logger.info('Is changed: %s', is_changed)
|
||||
if is_changed:
|
||||
self.view.draw_chats(
|
||||
self.model.current_chat,
|
||||
self.model.get_chats()
|
||||
)
|
||||
msgs = self.model.get_current_msgs()
|
||||
self.view.draw_msgs(msgs)
|
||||
elif key == '/k':
|
||||
self.refresh_chats()
|
||||
|
||||
elif key == 'k':
|
||||
is_changed = self.model.prev_chat()
|
||||
if is_changed:
|
||||
self.view.draw_chats(
|
||||
self.model.current_chat,
|
||||
self.model.get_chats()
|
||||
)
|
||||
msgs = self.model.get_current_msgs()
|
||||
self.view.draw_msgs(msgs)
|
||||
elif not key.startswith('/'):
|
||||
chat_id = self.model.get_current_chat_id()
|
||||
self.model.send_msg(chat_id, key)
|
||||
self.view.draw_chats(
|
||||
self.model.current_chat,
|
||||
self.model.get_chats()
|
||||
)
|
||||
msgs = self.model.get_current_msgs()
|
||||
self.view.draw_msgs(msgs)
|
||||
self.refresh_chats()
|
||||
|
||||
def update_handler(self, update):
|
||||
logger.debug('===============Received: %s', update)
|
||||
|
@ -70,7 +146,12 @@ class Controller:
|
|||
self.model.msgs.msgs[chat_id].append(update['message'])
|
||||
msgs = self.model.get_current_msgs()
|
||||
self.view.draw_msgs(msgs)
|
||||
# message_content = update['message']['content'].get('text', {})
|
||||
try:
|
||||
notify(update['message']['content']['text']['text'])
|
||||
except Exception:
|
||||
logger.exception(
|
||||
'Error happened on notify: %s', update['message'])
|
||||
# message_content = update['message']['content'].get('text', {})
|
||||
# we need this because of different message types: photos, files, etc.
|
||||
# message_text = message_content.get('text', '').lower()
|
||||
|
||||
|
@ -81,3 +162,18 @@ class Controller:
|
|||
# chat_id=chat_id,
|
||||
# text='pong',
|
||||
# )
|
||||
|
||||
|
||||
def notify(msg, subtitle='New message', title='Telegram'):
|
||||
msg = '-message {!r}'.format(msg)
|
||||
subtitle = '-subtitle {!r}'.format(subtitle)
|
||||
title = '-title {!r}'.format(title)
|
||||
sound = '-sound default'
|
||||
icon_path = os.path.join(os.path.dirname(__file__), 'tg.png')
|
||||
icon = f'-appIcon {icon_path}'
|
||||
cmd = '/usr/local/bin/terminal-notifier'
|
||||
|
||||
logger.debug('####: %s', f'{cmd} {icon} {sound} {title} {subtitle} {msg}')
|
||||
os.system(
|
||||
f'{cmd} {icon} {sound} {title} {subtitle} {msg}'
|
||||
)
|
||||
|
|
29
main.py
29
main.py
|
@ -3,6 +3,7 @@ import logging.handlers
|
|||
import os
|
||||
import threading
|
||||
from curses import wrapper
|
||||
from functools import partial
|
||||
|
||||
from telegram.client import Telegram
|
||||
|
||||
|
@ -15,7 +16,7 @@ logging.basicConfig(
|
|||
format='%(asctime)s %(message)s',
|
||||
handlers=[
|
||||
logging.handlers.RotatingFileHandler(
|
||||
'./debug.log',
|
||||
'./tg.log',
|
||||
backupCount=1,
|
||||
maxBytes=1024*256
|
||||
),
|
||||
|
@ -30,16 +31,7 @@ if PHONE is None:
|
|||
raise Exception('Environment variables did not provided')
|
||||
|
||||
|
||||
def main(stdscr):
|
||||
logger.debug('#' * 64)
|
||||
tg = Telegram(
|
||||
api_id=API_ID,
|
||||
api_hash=API_HASH,
|
||||
phone=PHONE,
|
||||
database_encryption_key='changeme1234',
|
||||
)
|
||||
tg.login()
|
||||
|
||||
def run(tg, stdscr):
|
||||
view = View(stdscr)
|
||||
model = Model(tg)
|
||||
controller = Controller(model, view)
|
||||
|
@ -54,5 +46,18 @@ def main(stdscr):
|
|||
t.join()
|
||||
|
||||
|
||||
def main():
|
||||
logger.debug('#' * 64)
|
||||
tg = Telegram(
|
||||
api_id=API_ID,
|
||||
api_hash=API_HASH,
|
||||
phone=PHONE,
|
||||
database_encryption_key='changeme1234',
|
||||
)
|
||||
tg.login()
|
||||
|
||||
wrapper(partial(run, tg))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
wrapper(main)
|
||||
main()
|
||||
|
|
24
model.py
24
model.py
|
@ -15,6 +15,9 @@ class Model:
|
|||
def get_current_chat_id(self):
|
||||
return self.chats.chat_ids[self.current_chat]
|
||||
|
||||
def get_current_msg(self):
|
||||
return self.msgs.current_msgs[self.get_current_chat_id()]
|
||||
|
||||
def next_chat(self):
|
||||
if self.current_chat < len(self.chats.chats):
|
||||
self.current_chat += 1
|
||||
|
@ -27,6 +30,14 @@ class Model:
|
|||
return True
|
||||
return False
|
||||
|
||||
def next_msg(self):
|
||||
chat_id = self.chats.chat_ids[self.current_chat]
|
||||
return self.msgs.next_msg(chat_id)
|
||||
|
||||
def prev_msg(self):
|
||||
chat_id = self.chats.chat_ids[self.current_chat]
|
||||
return self.msgs.prev_msg(chat_id)
|
||||
|
||||
def get_chats(self, offset=0, limit=10):
|
||||
return self.chats.get_chats(offset=offset, limit=limit)
|
||||
|
||||
|
@ -118,6 +129,19 @@ class MsgModel:
|
|||
def __init__(self, tg):
|
||||
self.tg = tg
|
||||
self.msgs = defaultdict(list) # Dict[int, list]
|
||||
self.current_msgs = defaultdict(int)
|
||||
|
||||
def next_msg(self, chat_id):
|
||||
if self.current_msgs[chat_id] > 0:
|
||||
self.current_msgs[chat_id] -= 1
|
||||
return True
|
||||
return False
|
||||
|
||||
def prev_msg(self, chat_id):
|
||||
if self.current_msgs[chat_id] < len(self.msgs[chat_id]):
|
||||
self.current_msgs[chat_id] += 1
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_msgs(self, chat_id, offset=0, limit=10):
|
||||
if offset + limit < len(self.msgs[chat_id]):
|
||||
|
|
47
view.py
47
view.py
|
@ -11,7 +11,13 @@ class View:
|
|||
|
||||
def __init__(self, stdscr):
|
||||
curses.start_color()
|
||||
curses.echo()
|
||||
curses.noecho()
|
||||
curses.cbreak()
|
||||
stdscr.keypad(True)
|
||||
curses.curs_set(0)
|
||||
|
||||
curses.start_color()
|
||||
curses.use_default_colors()
|
||||
|
||||
self.stdscr = stdscr
|
||||
self.chats = ChatView(stdscr)
|
||||
|
@ -21,17 +27,23 @@ class View:
|
|||
def draw_chats(self, current, chats):
|
||||
self.chats.draw(current, chats)
|
||||
|
||||
def draw_msgs(self, msgs):
|
||||
self.msgs.draw(msgs)
|
||||
def draw_msgs(self, current, msgs):
|
||||
self.msgs.draw(current, msgs)
|
||||
|
||||
def get_key(self):
|
||||
def get_key(self, y, x):
|
||||
# return self.stdscr.getkey()
|
||||
_input = self.stdscr.getstr(
|
||||
self.msgs.h, self.chats.w, self.max_read).decode()
|
||||
|
||||
ch = self.stdscr.getch(y, x)
|
||||
logger.info('raw ch without unctrl: %s', ch)
|
||||
return curses.unctrl(ch).decode()
|
||||
|
||||
# self.stdscr.addstr(self.msgs.h, self.chats.w, ' ' * self.msgs.w-10)
|
||||
# self.chats.win.addstr(self.msgs.h, self.chats.w +
|
||||
# 5, ' ' * self.msgs.w-10)
|
||||
return _input
|
||||
|
||||
# _input = self.stdscr.getstr(
|
||||
# self.msgs.h, self.chats.w, self.max_read).decode()
|
||||
# return _input
|
||||
|
||||
|
||||
emoji_pattern = re.compile(
|
||||
|
@ -53,12 +65,14 @@ class ChatView:
|
|||
|
||||
def draw(self, current, chats):
|
||||
self.win.clear()
|
||||
self.win.vline(0, self.w-1, curses.ACS_VLINE, self.h)
|
||||
# self.win.vline(0, self.w-1, curses.ACS_VLINE, self.h)
|
||||
for i, chat in enumerate(chats):
|
||||
msg = f'{i:>2} {get_date(chat)} {chat["title"]} {chat["unread_count"]}: {get_last_msg(chat)}'
|
||||
msg = f'{get_date(chat)} {chat["title"]} {chat["unread_count"]}: {get_last_msg(chat)}'
|
||||
msg = emoji_pattern.sub(r'', msg)[:self.w-1]
|
||||
if len(msg) < self.w:
|
||||
msg += ' ' * (self.w - len(msg) - 1)
|
||||
if i == current:
|
||||
self.win.addstr(i, 0, msg, curses.color_pair(1))
|
||||
self.win.addstr(i, 0, msg, curses.A_REVERSE)
|
||||
continue
|
||||
self.win.addstr(i, 0, msg)
|
||||
|
||||
|
@ -73,18 +87,25 @@ class MsgView:
|
|||
self.win = stdscr.subwin(self.h, self.w, 0, self.s)
|
||||
self.lines = 0
|
||||
|
||||
def draw(self, msgs):
|
||||
def draw(self, current, msgs):
|
||||
logger.info('Dwaring msgs')
|
||||
self.win.clear()
|
||||
count = 0
|
||||
current = len(msgs) - current - 1
|
||||
|
||||
for msg in msgs:
|
||||
for i, msg in enumerate(msgs):
|
||||
s = self._parse_msg(msg)
|
||||
s = s.replace('\n', ' ')
|
||||
if len(s) < self.w:
|
||||
s += ' ' * (self.w - len(s) - 1)
|
||||
offset = math.ceil(len(s) / self.w)
|
||||
if count + offset > self.h-1:
|
||||
logger.warning('Reched end of lines')
|
||||
break
|
||||
self.win.addstr(count, 0, s)
|
||||
if i == current:
|
||||
self.win.addstr(count, 0, s, curses.A_REVERSE)
|
||||
else:
|
||||
self.win.addstr(count, 0, s)
|
||||
count += offset
|
||||
|
||||
self.lines = count
|
||||
|
|
Loading…
Reference in a new issue