Start to implement ranger like keybindings and ui logic

Add notifications
Use correct colors
This commit is contained in:
Paul Nameless 2019-02-17 22:26:20 +01:00
parent c557fcadbb
commit 4986d39987
4 changed files with 199 additions and 53 deletions

View file

@ -1,4 +1,5 @@
import logging import logging
import os
import threading import threading
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -23,42 +24,117 @@ class Controller:
self.model.get_chats() self.model.get_chats()
) )
msgs = self.model.get_current_msgs() msgs = self.model.get_current_msgs()
self.view.draw_msgs(msgs) self.view.draw_msgs(self.model.get_current_msg(), msgs)
def run(self): 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: 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) 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 return
elif key == '/j': elif key == 'l':
rc = self.handle_msgs()
if rc == 'QUIT':
return
elif key == 'j':
is_changed = self.model.next_chat() is_changed = self.model.next_chat()
logger.info('Is changed: %s', is_changed)
if is_changed: if is_changed:
self.view.draw_chats( self.refresh_chats()
self.model.current_chat,
self.model.get_chats() elif key == 'k':
)
msgs = self.model.get_current_msgs()
self.view.draw_msgs(msgs)
elif key == '/k':
is_changed = self.model.prev_chat() is_changed = self.model.prev_chat()
if is_changed: if is_changed:
self.view.draw_chats( self.refresh_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)
def update_handler(self, update): def update_handler(self, update):
logger.debug('===============Received: %s', update) logger.debug('===============Received: %s', update)
@ -70,7 +146,12 @@ class Controller:
self.model.msgs.msgs[chat_id].append(update['message']) self.model.msgs.msgs[chat_id].append(update['message'])
msgs = self.model.get_current_msgs() msgs = self.model.get_current_msgs()
self.view.draw_msgs(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. # we need this because of different message types: photos, files, etc.
# message_text = message_content.get('text', '').lower() # message_text = message_content.get('text', '').lower()
@ -81,3 +162,18 @@ class Controller:
# chat_id=chat_id, # chat_id=chat_id,
# text='pong', # 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
View file

@ -3,6 +3,7 @@ import logging.handlers
import os import os
import threading import threading
from curses import wrapper from curses import wrapper
from functools import partial
from telegram.client import Telegram from telegram.client import Telegram
@ -15,7 +16,7 @@ logging.basicConfig(
format='%(asctime)s %(message)s', format='%(asctime)s %(message)s',
handlers=[ handlers=[
logging.handlers.RotatingFileHandler( logging.handlers.RotatingFileHandler(
'./debug.log', './tg.log',
backupCount=1, backupCount=1,
maxBytes=1024*256 maxBytes=1024*256
), ),
@ -30,16 +31,7 @@ if PHONE is None:
raise Exception('Environment variables did not provided') raise Exception('Environment variables did not provided')
def main(stdscr): def run(tg, stdscr):
logger.debug('#' * 64)
tg = Telegram(
api_id=API_ID,
api_hash=API_HASH,
phone=PHONE,
database_encryption_key='changeme1234',
)
tg.login()
view = View(stdscr) view = View(stdscr)
model = Model(tg) model = Model(tg)
controller = Controller(model, view) controller = Controller(model, view)
@ -54,5 +46,18 @@ def main(stdscr):
t.join() 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__': if __name__ == '__main__':
wrapper(main) main()

View file

@ -15,6 +15,9 @@ class Model:
def get_current_chat_id(self): def get_current_chat_id(self):
return self.chats.chat_ids[self.current_chat] 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): def next_chat(self):
if self.current_chat < len(self.chats.chats): if self.current_chat < len(self.chats.chats):
self.current_chat += 1 self.current_chat += 1
@ -27,6 +30,14 @@ class Model:
return True return True
return False 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): def get_chats(self, offset=0, limit=10):
return self.chats.get_chats(offset=offset, limit=limit) return self.chats.get_chats(offset=offset, limit=limit)
@ -118,6 +129,19 @@ class MsgModel:
def __init__(self, tg): def __init__(self, tg):
self.tg = tg self.tg = tg
self.msgs = defaultdict(list) # Dict[int, list] 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): def get_msgs(self, chat_id, offset=0, limit=10):
if offset + limit < len(self.msgs[chat_id]): if offset + limit < len(self.msgs[chat_id]):

47
view.py
View file

@ -11,7 +11,13 @@ class View:
def __init__(self, stdscr): def __init__(self, stdscr):
curses.start_color() 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.stdscr = stdscr
self.chats = ChatView(stdscr) self.chats = ChatView(stdscr)
@ -21,17 +27,23 @@ class View:
def draw_chats(self, current, chats): def draw_chats(self, current, chats):
self.chats.draw(current, chats) self.chats.draw(current, chats)
def draw_msgs(self, msgs): def draw_msgs(self, current, msgs):
self.msgs.draw(msgs) self.msgs.draw(current, msgs)
def get_key(self): def get_key(self, y, x):
# return self.stdscr.getkey() # 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.stdscr.addstr(self.msgs.h, self.chats.w, ' ' * self.msgs.w-10)
# self.chats.win.addstr(self.msgs.h, self.chats.w + # self.chats.win.addstr(self.msgs.h, self.chats.w +
# 5, ' ' * self.msgs.w-10) # 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( emoji_pattern = re.compile(
@ -53,12 +65,14 @@ class ChatView:
def draw(self, current, chats): def draw(self, current, chats):
self.win.clear() 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): 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] msg = emoji_pattern.sub(r'', msg)[:self.w-1]
if len(msg) < self.w:
msg += ' ' * (self.w - len(msg) - 1)
if i == current: if i == current:
self.win.addstr(i, 0, msg, curses.color_pair(1)) self.win.addstr(i, 0, msg, curses.A_REVERSE)
continue continue
self.win.addstr(i, 0, msg) self.win.addstr(i, 0, msg)
@ -73,18 +87,25 @@ class MsgView:
self.win = stdscr.subwin(self.h, self.w, 0, self.s) self.win = stdscr.subwin(self.h, self.w, 0, self.s)
self.lines = 0 self.lines = 0
def draw(self, msgs): def draw(self, current, msgs):
logger.info('Dwaring msgs')
self.win.clear() self.win.clear()
count = 0 count = 0
current = len(msgs) - current - 1
for msg in msgs: for i, msg in enumerate(msgs):
s = self._parse_msg(msg) s = self._parse_msg(msg)
s = s.replace('\n', ' ') s = s.replace('\n', ' ')
if len(s) < self.w:
s += ' ' * (self.w - len(s) - 1)
offset = math.ceil(len(s) / self.w) offset = math.ceil(len(s) / self.w)
if count + offset > self.h-1: if count + offset > self.h-1:
logger.warning('Reched end of lines') logger.warning('Reched end of lines')
break 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 count += offset
self.lines = count self.lines = count