Skip to content

Commit

Permalink
add :renameContact and :reload commands, fixes #74
Browse files Browse the repository at this point in the history
  • Loading branch information
isamert committed Jul 11, 2020
1 parent 79ad2dc commit 0b251c0
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 27 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
```
Expand Down
96 changes: 69 additions & 27 deletions scli
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand Down Expand Up @@ -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}

Expand Down Expand Up @@ -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()

Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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'<PID> <RETURN_CODE>\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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down

0 comments on commit 0b251c0

Please sign in to comment.