From 0b251c0671624d3f4e5decebc81a3109d9bf3908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0sa=20Mert=20G=C3=BCrb=C3=BCz?= Date: Sat, 11 Jul 2020 19:07:37 +0300 Subject: [PATCH] add `:renameContact` and `:reload` commands, fixes #74 --- README.md | 2 ++ scli | 96 +++++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 71 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index b33a8d0..5088c83 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,8 @@ There are some basic commands that you can use. Hit `:` to enter command mode (o - `:toggleNotifications` or `:n` toggles desktop notifications. - `:edit` or `:e` lets you edit your message in your `$EDITOR`. - `:toggleAutohide` or `:h` toggles autohide property of the contacts pane. +- `:renameContact [NUMBER] NEW_NAME` renames the `NUMBER`s name to `NEW_NAME`, if `NUMBER` is skipped, simply current contacts name is renamed to `NEW_NAME`. +- `:reload` re-reads the `signal-cli`s data file. (Updates contacts list etc.) Examples: ``` diff --git a/scli b/scli index 0079ac0..72752c0 100755 --- a/scli +++ b/scli @@ -141,6 +141,9 @@ def mk_call(cmd, rmap=None, disown=False, use_pipe=True): return out +def is_number(number): + return bool(re.match('^\+?[0-9]*$', number)) + class ObservableConfig: signals = ['changed'] @@ -489,6 +492,8 @@ class Commands: (['openUrl', 'u'], self.open_last_url), (['toggleNotifications', 'n'], self.toggle_notifications), (['toggleAutohide', 'h'], self.toggle_autohide), + (['renameContact'], self.rename_contact), + (['reload'], self.reload), (['quit', 'q'], self.quit)] self.map = {cmd.lower(): fn for cmds, fn in cmd_mapping for cmd in cmds} @@ -653,6 +658,22 @@ class Commands: if self.state.cfg.enable_notifications: mk_call(self.state.cfg.notification_command, {'%s': sender, '%m': message}) + def rename_contact(self, args): + # :renameNumber +NUMBER new name here -> use +NUMBER number + # :renameNumber new name here -> use current contacts number + try: + number, new_name = args.split(' ', 1) + if not is_number(number): + raise ValueError + except ValueError: + number = self.state.current_contact.get('number') + new_name = args + + self.state.signal.rename_contact(number, new_name) + + def reload(self): + self.state.reload() + def quit(self): raise urwid.ExitMainLoop() @@ -677,24 +698,24 @@ class Signal: self.loop = None self.sending_procs = {} - self.reload_data() + self.reload() + + def reload(self): + with open(self._path) as f: + self._data = json.load(f) self.contacts = sorted( self._data['contactStore']['contacts'], key = lambda i: get_contact_name(i)) self.groups = [g for g in self._data["groupStore"]['groups'] - if g['name'] - # Older versions of `signal-cli` use `active` key while newer ones use `archived`. - and (g.get('active') or not g.get('archived'))] + if g['name'] + # Older versions of `signal-cli` use `active` key while newer ones use `archived`. + and (g.get('active') or not g.get('archived'))] self.contacts_map = {c['number']: c for c in self.contacts} self.contacts_map.update({g['groupId']: g for g in self.groups}) self.formatted_contacts = self.make_formatted_contacts_list() - def reload_data(self): - with open(self._path) as f: - self._data = json.load(f) - def start_daemon(self): fd = self.loop.watch_pipe(self.daemon_handler) return Popen(['signal-cli', '-u', self.user, 'daemon', '--json'], stdout=fd, stderr=fd, close_fds=True) @@ -730,12 +751,15 @@ class Signal: # TODO: display error to user continue + def rename_contact(self, number, new_name): + args = ["org.asamk.Signal.setContactName", "string:" + number, "string:" + new_name] + self.run_cmd('rename_contact', args) + def send_message(self, contact, message="", attachments=None): + args = [] if not attachments: attachments = [] - args = ['dbus-send', '--session', '--type=method_call', '--print-reply', '--dest=org.asamk.Signal', '/org/asamk/Signal'] - contact_number = contact.get('number') if contact_number is not None: target = contact_number @@ -769,29 +793,36 @@ class Signal: 'attachments': attachments, 'timestamp': ts}} - watchpipe_fd = self.loop.watch_pipe(self.send_watchpipe_handler) - # Make all `dbus-send` output go to stderr, and write the process PID and exit status to the watch pipe. - sh_command = " ".join(args) + " 1>&2; echo $$ $?" - send_proc = Popen(sh_command, shell=True, stdout=watchpipe_fd, stderr=PIPE, universal_newlines=True) - self.sending_procs[send_proc.pid] = (send_proc, watchpipe_fd, envelope) + self.run_cmd('send_message', args, envelope) logging.info('send_message:%s', envelope) urwid.emit_signal(self, 'send_message', envelope) - def send_watchpipe_handler(self, line): + def run_cmd(self, cmd, args, data=None): + watchpipe_fd = self.loop.watch_pipe(self.watchpipe_handler) + # Make all `dbus-send` output go to stderr, and write the process PID and exit status to the watch pipe. + + sh_command = " ".join(['dbus-send', '--session', '--type=method_call', '--print-reply', '--dest=org.asamk.Signal', '/org/asamk/Signal', *args]) + " 1>&2; echo $$ $?" + send_proc = Popen(sh_command, shell=True, stdout=watchpipe_fd, stderr=PIPE, universal_newlines=True) + self.sending_procs[send_proc.pid] = (cmd, send_proc, watchpipe_fd, data) + + def watchpipe_handler(self, line): # The line printed to watch pipe is of the form "b' \n'" proc_pid, return_code = [int(i) for i in line.decode().split()] - send_proc, watchpipe_fd, envelope = self.sending_procs.pop(proc_pid) + cmd, send_proc, watchpipe_fd, data = self.sending_procs.pop(proc_pid) send_proc.wait() # reap the child process, to prevent zombies - for attachment in envelope['dataMessage']['attachments']: - if attachment.startswith( - os.path.join(tempfile.gettempdir(), clip.tempfile_prefix)): - os.remove(attachment) + + if cmd == "send_message": + # Remove temproary attachments + for attachment in data['dataMessage']['attachments']: + if attachment.startswith(os.path.join(tempfile.gettempdir(), clip.tempfile_prefix)): + os.remove(attachment) + if return_code != 0: - logging.error('send_message: exit code=%d:err=%s', return_code, send_proc.stderr.read()) - # https://github.com/AsamK/signal-cli/issues/73 - os.close(watchpipe_fd) # Close the write end of urwid's watch pipe. - return False # Close the read end of urwid's watch pipe and remove the watch from event_loop. + logging.error('%s: exit code=%d:err=%s', cmd, return_code, send_proc.stderr.read()) + + os.close(watchpipe_fd) # Close the write end of urwid's watch pipe. + return False # Close the read end of urwid's watch pipe and remove the watch from event_loop. def get_contact(self, number_or_id): return self.contacts_map.get(number_or_id) @@ -829,11 +860,14 @@ class ContactsWindow(urwid.ListBox): self.notify_count = 0 super().__init__(urwid.SimpleFocusListWalker(self._body)) - self.set_contacts(self.state.signal.formatted_contacts) + self.reload() urwid.connect_signal(self.state.signal, 'receive_message', self.on_receive_message) urwid.connect_signal(self.state, 'current_contact_changed', self.on_current_contact_changed) + def reload(self): + self.set_contacts(self.state.signal.formatted_contacts) + def set_contact_notify_count(self, w, count): if not w: return @@ -1262,11 +1296,16 @@ class MainWindow(urwid.Frame): # signals urwid.connect_signal(self.state, 'current_contact_changed', self.on_current_contact_changed) urwid.connect_signal(self.state, 'status_changed', self.on_status_changed) + urwid.connect_signal(self.state, 'reload_request', self.reload) urwid.connect_signal(self.state, 'notification_changed', self.on_notification_changed) urwid.connect_signal(self.state, 'error_changed', self.on_error_changed) urwid.connect_signal(self.state.cfg, 'changed', self.on_cfg_changed) urwid.connect_signal(self._wleft._wcontacts, 'notify_count_changed', self.on_notify_count_changed) + def reload(self): + self.state.signal.reload() + self._wleft._wcontacts.reload() + def set_status(self, txt): self._wstatus.set_text(txt) @@ -1384,7 +1423,7 @@ class MainWindow(urwid.Frame): # ############################################################################# class State: - signals = ['current_contact_changed', 'status_changed', 'notification_changed', 'error_changed', 'dialog_requested', 'dialog_finished'] + signals = ['current_contact_changed', 'status_changed', 'notification_changed', 'error_changed', 'dialog_requested', 'dialog_finished', 'reload_request'] def __init__(self, cfg): self.signal = Signal(cfg.username) @@ -1403,6 +1442,9 @@ class State: urwid.connect_signal(self.signal, 'receive_message', self.on_receive_message) urwid.connect_signal(self.signal, 'send_message', self.on_send_message) + def reload(self): + urwid.emit_signal(self, 'reload_request') + def set_current_contact(self, contact, focus=False): old = self.current_contact self.current_contact = contact