import weechat as w import os import subprocess import time import collections import datetime import struct SCRIPT_NAME = 'mosh_away' SCRIPT_AUTHOR = 'Shiz ' SCRIPT_VERSION = '0.1' SCRIPT_LICENSE = 'BSD' SCRIPT_DESC = 'Set mosh away status.' SCRIPT_CONFIG = { 'message': ('Detached from mosh', 'Away message'), 'time_format': ('since %Y-%m-%d %H:%M:%S%z', 'time format append to away message'), 'interval': ('5', 'How often in seconds to check mosh status'), 'away_suffix': ('', 'What to append to your nick when you\'re away.'), 'command_on_attach': ('', 'Commands to execute on attach, separated by semicolon'), 'command_on_detach': ('', 'Commands to execute on detach, separated by semicolon'), 'ignore': ('', 'Comma-separated list of servers to ignore.'), 'set_away': ('on', 'Set user as away.'), 'ignore_relays': ('off', 'Only check screen status and ignore relay interfaces'), } TIMER = None AWAY = False RELAYED = False UTMP_FILE = '/var/run/utmp' def update_timer(): """ Update check timer hook with new interval. """ global TIMER if TIMER: w.unhook(TIMER) TIMER = w.hook_timer(int(w.config_get_plugin('interval')) * 1000, 0, 0, 'mosh_away_check', '') def mosh_away_on_config(data, option, value): """ Update config. """ if option.endswith('.interval'): update_timer() return w.WEECHAT_RC_OK def get_servers(): """ Get the servers that are not away, or were set away by this script. """ ignores = w.config_get_plugin('ignore').split(',') infolist = w.infolist_get('irc_server','','') buffers = [] while w.infolist_next(infolist): if not w.infolist_integer(infolist, 'is_connected') == 1 or w.infolist_string(infolist, 'name') in ignores: continue if not w.config_string_to_boolean(w.config_get_plugin('set_away')) or not w.infolist_integer(infolist, 'is_away') or w.config_get_plugin('message') in w.infolist_string(infolist, 'away_message'): buffers.append((w.infolist_pointer(infolist, 'buffer'), w.infolist_string(infolist, 'nick'))) w.infolist_free(infolist) return buffers def mosh_away_check(buffer, args): """ Check away status. """ global AWAY, RELAYED set_away = w.config_string_to_boolean(w.config_get_plugin('set_away')) check_relays = not w.config_string_to_boolean(w.config_get_plugin('ignore_relays')) suffix = w.config_get_plugin('away_suffix') # Hacky hacky way to figure out if mosh is attached. try: # First, find our proper TTY. /usr/bin/tty lies under tmux. tmux = os.getenv('TMUX') if tmux: _, _, tmux_sess = tmux.split(',') ttyline = subprocess.check_output(['tmux', 'list-clients', '-t', tmux_sess]).decode('utf-8') tty, _, _ = ttyline.partition(':') else: tty = subprocess.check_output(['tty']).decode('utf-8').strip() tty = tty.replace('/dev/', '') # Then, read the utmp entry and see if the format is either "X.Y.Z.W via mosh [pid]" or "mosh [pid]". You can guess which one means attached. with open(UTMP_FILE, 'rb') as f: for entry in utmp_read(f.read()): if entry.line == tty: if 'mosh' not in entry.host: continue attached = not entry.host.startswith('mosh ') break else: # Don't meddle in affairs not of our making if we can't even figure out our mosh status. return w.WEECHAT_RC_OK except Exception as e: w.prnt('', e) return w.WEECHAT_RC_OK # Check wether a client is connected on relay or not. RELAYED = False if check_relays: infolist = w.infolist_get('relay', '', '') if infolist: while w.infolist_next(infolist): status = w.infolist_string(infolist, 'status_string') if status == 'connected': RELAYED = True break w.infolist_free(infolist) # Set away status if applicable. if AWAY and (attached or RELAYED): w.prnt('', '%s: mosh attached. Clearing away status' % SCRIPT_NAME) for server, nick in get_servers(): if set_away: w.command(server, "/away") if suffix and nick.endswith(suffix): nick = nick[:-len(suffix)] w.command(server, "/nick %s" % nick) AWAY = False for cmd in w.config_get_plugin("command_on_attach").split(";"): w.command("", cmd) elif not attached and not AWAY and not RELAYED: w.prnt('', '%s: mosh detached. Setting away status' % SCRIPT_NAME) for server, nick in get_servers(): if suffix and not nick.endswith(suffix): w.command(server, "/nick %s%s" % (nick, suffix)); if set_away: w.command(server, "/away %s %s" % (w.config_get_plugin('message'), time.strftime(w.config_get_plugin('time_format')))) AWAY = True for cmd in w.config_get_plugin("command_on_detach").split(";"): w.command("", cmd) return w.WEECHAT_RC_OK if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, '', ''): version = w.info_get('version_number', '') or 0 for option, default_desc in SCRIPT_CONFIG.iteritems(): if not w.config_is_set_plugin(option): w.config_set_plugin(option, default_desc[0]) if int(version) >= 0x00030500: w.config_set_desc_plugin(option, default_desc[1]) update_timer() w.hook_config('plugins.var.python.' + SCRIPT_NAME + '.*', 'mosh_away_on_config', '') # Shamelessly copied from https://github.com/hjacobs/utmp/. class UTMPRecord(collections.namedtuple('UTMPRecord', 'type pid line id user host exit0 exit1 session sec usec addr0 addr1 addr2 addr3 unused')): UTMPSTRUCT = struct.Struct('hi32s4s32s256shhiii4i20s') def utmp_read(buf): def convert_string(val): if isinstance(val, bytes): return val.rstrip(b'\0').decode() return val offset = 0 while offset < len(buf): yield UTMPRecord._make(map(convert_string, UTMPSTRUCT.unpack_from(buf, offset))) offset += UTMPSTRUCT.size