]> jfr.im git - irc/weechat/scripts.git/commitdiff
Remove script chanstat.py
authorSébastien Helleu <redacted>
Tue, 25 Jan 2022 06:11:45 +0000 (07:11 +0100)
committerSébastien Helleu <redacted>
Tue, 25 Jan 2022 18:21:08 +0000 (19:21 +0100)
Script does not work with Python 3.

python/chanstat.py [deleted file]

diff --git a/python/chanstat.py b/python/chanstat.py
deleted file mode 100644 (file)
index 550dd69..0000000
+++ /dev/null
@@ -1,686 +0,0 @@
-# -*- coding: utf-8 -*-
-###
-# Copyright (c) 2010 by Elián Hanisch <lambdae2@gmail.com>
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-###
-
-###
-# Shows highest and lowest user count for joined channels,
-# and an average (for a period of a month)
-#
-#
-#   Commands:
-#   * /chanstat
-#     Prints current channel stats, see /help chanstat
-#
-#
-#   Settings:
-#   * plugins.var.python.chanstat.path:
-#     path where to store stat files, default '%h/chanstat'
-#
-#   * plugins.var.python.chanstat.averge_period:
-#     Period of time for calculate the average stats. This means the avegare will be calculated with
-#     the users present in the last x days. Default is 30 days.
-#
-#   * plugins.var.python.chanstat.show_peaks:
-#     If 'on' it will display a message when there's a user peak in any channel.
-#     Valid values: on, off
-#
-#   * plugins.var.python.chanstat.show_lows:
-#     If 'on' it will display a message when there's a user low in any channel.
-#     Valid values: on, off
-#
-#
-#   History:
-#   2010-06-08
-#   version 0.1: initial release.
-#   2021-05-02
-#   version 0.2: add compatibility with WeeChat >= 3.2 (XDG directories)
-#
-###
-
-SCRIPT_NAME    = "chanstat"
-SCRIPT_AUTHOR  = "Elián Hanisch <lambdae2@gmail.com>"
-SCRIPT_VERSION = "0.2"
-SCRIPT_LICENSE = "GPL3"
-SCRIPT_DESC    = "Channel statistics"
-
-try:
-    import weechat
-    WEECHAT_RC_OK = weechat.WEECHAT_RC_OK
-    import_ok = True
-except ImportError:
-    print "This script must be run under WeeChat."
-    print "Get WeeChat now at: http://weechat.flashtux.org/"
-    import_ok = False
-
-import time
-now = lambda : int(time.time())
-
-time_hour = 3600
-time_day  = 86400
-time_year = 31536000
-
-### messages
-def debug(s, args=(), prefix='', name_suffix='debug', level=1):
-    """Debug msg"""
-    l = weechat.config_get_plugin('debug')
-    if not (l and int(l) >= level): return
-    buffer_name = '%s_%s' %(SCRIPT_NAME, name_suffix)
-    buffer = weechat.buffer_search('python', buffer_name)
-    if not buffer:
-        buffer = weechat.buffer_new(buffer_name, '', '', '', '')
-        weechat.buffer_set(buffer, 'nicklist', '0')
-        weechat.buffer_set(buffer, 'localvar_set_no_log', '1')
-    weechat.prnt(buffer, '%s\t%s' %(prefix, s %args))
-
-def error(s, prefix=SCRIPT_NAME, buffer=''):
-    """Error msg"""
-    prefix = prefix or script_nick
-    weechat.prnt(buffer, '%s%s %s' %(weechat.prefix('error'), prefix, s))
-
-def say(s, prefix=None, buffer=''):
-    """Normal msg"""
-    prefix = prefix or script_nick
-    weechat.prnt(buffer, '%s\t%s' %(prefix, s))
-
-### config and value validation
-boolDict = {'on':True, 'off':False}
-def get_config_boolean(config):
-    value = weechat.config_get_plugin(config)
-    try:
-        return boolDict[value]
-    except KeyError:
-        default = settings[config]
-        error("Error while fetching config '%s'. Using default value '%s'." %(config, default))
-        error("'%s' is invalid, allowed: 'on', 'off'" %value)
-        return boolDict[default]
-
-def get_config_int(config):
-    value = weechat.config_get_plugin(config)
-    try:
-        return int(value)
-    except ValueError:
-        default = settings[config]
-        error("Error while fetching config '%s'. Using default value '%s'." %(config, default))
-        error("'%s' is not a number." %value)
-        return int(default)
-
-def get_dir(filename):
-    import os
-    options = {
-        'directory': 'data',
-    }
-    basedir = weechat.string_eval_path_home(weechat.config_get_plugin('path'), {}, {}, options)
-    if not os.path.isdir(basedir):
-        os.makedirs(basedir)
-    return os.path.join(basedir, filename.lower())
-
-
-class CaseInsensibleString(str):
-    def __init__(self, s=''):
-        self.lowered = s.lower()
-
-    def __eq__(self, s):
-        try:
-            return self.lowered == s.lower()
-        except:
-            return False
-
-    def __ne__(self, s):
-        return not self == s
-
-    def __hash__(self):
-        return hash(self.lowered)
-
-def caseInsensibleKey(k):
-    if isinstance(k, str):
-        return CaseInsensibleString(k)
-    elif isinstance(k, tuple):
-        return tuple([ caseInsensibleKey(v) for v in k ])
-    return k
-
-class CaseInsensibleDict(dict):
-    key = staticmethod(caseInsensibleKey)
-
-    def __setitem__(self, k, v):
-        dict.__setitem__(self, self.key(k), v)
-
-    def __getitem__(self, k):
-        return dict.__getitem__(self, self.key(k))
-
-    def __delitem__(self, k):
-        dict.__delitem__(self, self.key(k))
-
-    def __contains__(self, k):
-        return dict.__contains__(self, self.key(k))
-
-
-class Channel(object):
-    def __init__(self, max=None, min=None, max_date=None, min_date=None, avrg_date=None,
-            avrg_period=None, average=None, count=0):
-        if not max:
-            max = count
-        if not min:
-            min = 0
-        if not average:
-            average = float(count)
-        if not max_date:
-            max_date = now()
-        if not min_date:
-            min_date = now()
-        if not avrg_date:
-            avrg_date = now()
-        if not avrg_period:
-            avrg_period = 0
-        self.max = max
-        self.min = min
-        self.max_date = max_date
-        self.min_date = min_date
-        self.avrg_date = avrg_date
-        self.avrg_period = avrg_period
-        self.average = average
-
-    def __iter__(self):
-        return iter((self.max, self.min, self.max_date, self.min_date, self.avrg_date,
-            self.avrg_period, self.average))
-
-    def __str__(self):
-        return 'Channel(max=%s, average=%s, min_delta=%s)' %(self.max, self.average, self.min)
-
-
-class StatLog(object):
-    def __init__(self):
-        self.writers = CaseInsensibleDict()
-
-    @staticmethod
-    def make_log(key):
-        return get_dir('%s_%s.cvs' %key)
-
-    def log(self, key, *args):
-        if key in self.writers:
-            writer = self.writers[key]
-        else:
-            import csv
-            filename = self.make_log(key)
-            writer = csv.writer(open(filename, 'ab'))
-            self.writers[key] = writer
-
-        writer.writerow(args)
-
-    def get_reader(self, key):
-        if key in self.writers:
-            del self.writers[key]
-        import csv
-        return csv.reader(open(self.make_log(key)))
-
-    def close(self):
-        self.writers = {}
-
-
-class ChanStatDB(CaseInsensibleDict):
-    def __init__(self):
-        self.logger = StatLog()
-
-    def __setitem__(self, key, value):
-        if not value:
-            return
-        debug(' ** stats update for %s', args=key[1])
-        _now = now()
-        avrg = 0
-        if key in self:
-            chan = self[key]
-            if value > chan.max:
-                debug('PEAK, %s: %s', args=(key[1], value))
-                chan.max = value
-                new_channel_peak(key, value, chan.max_date)
-                chan.max_date = _now
-            elif (chan.max - value) > chan.min:
-                # we save the difference between max and min rather the min absolute value, because
-                # the minimum min value for a channel would be 1, and that's isn't interesting.
-                min_delta = chan.max - value
-                debug('LOW, %s: %s', args=(key[1], value))
-                chan.min = min_delta
-                new_channel_low(key, value, chan.min_date)
-                chan.min_date = _now
-            # calculate average aproximation
-            diff = _now - chan.avrg_date
-            #period = 30 * time_day
-            period = get_config_int('average_period') * time_day
-            if not period:
-                period = time_day
-            avrg_period = chan.avrg_period
-            avrg_period += diff
-            if avrg_period > period:
-                avrg_period = period
-            if diff > avrg_period // 1000 and diff > 600:
-                # calc average after 1000th part of the period (10 min minimum)
-                max = period // 100
-                if diff > max:
-                    # too much time have passed since last check, average will be skewed by current
-                    # user count, so we reduce the average period.
-                    excess = diff - max
-                    debug('avrg period correction %s e:%.4f%%', args=(key[1],
-                        excess*100.0/avrg_period))
-                    diff = max
-                    avrg_period -= excess
-                    if avrg_period < diff:
-                        avrg_period = diff
-                avrg = chan.average
-                avrg = (avrg * (avrg_period - diff) + value * diff) / avrg_period
-                chan.avrg_date = _now
-                chan.avrg_period = avrg_period
-                # make sure avrg is between max and 1
-                if avrg > chan.max:
-                    avrg = chan.max
-                elif avrg < 1:
-                    avrg = 1
-                debug('avrg %s %.2f → %.2f (%.4f%% %.4f)', args=(key[1], chan.average, avrg,
-                    diff*100.0/avrg_period, avrg - chan.average))
-                chan.average = avrg
-        else:
-            CaseInsensibleDict.__setitem__(self, key, Channel(count=value))
-
-        #if avrg:
-        #    self.logger.log(key, _now, value, avrg)
-        #else:
-        #    self.logger.log(key, _now, value)
-
-    def initchan(self, key, *args):
-        CaseInsensibleDict.__setitem__(self, key, Channel(*args))
-
-    def iterchan(self):
-        def generator():
-            for key in self.keys():
-                chan = self[key]
-                row = list(key)
-                row.extend(chan)
-                yield row
-
-        return generator()
-
-    def keys(self):
-        """Returns keys sorted"""
-        L = dict.keys(self)
-        L.sort()
-        return L
-
-    def close(self):
-        self.logger.close()
-
-channel_stats = ChanStatDB()
-
-
-def write_database():
-    import csv
-    filename = get_dir('peak_data.csv')
-    try:
-        writer = csv.writer(open(filename, 'wb'))
-        writer.writerows(channel_stats.iterchan())
-    except IOError:
-        error('Failed to write chanstat database in %s' %file)
-
-def load_database():
-    import csv
-    filename = get_dir('peak_data.csv')
-    try:
-        reader = csv.reader(open(filename, 'rb'))
-    except IOError:
-        return
-    channel_stats.clear()
-    for row in reader:
-        key = tuple(row[0:2])
-        values = row[2:-1]
-        values = map(int, values)
-        average = row[-1]
-        average = float(average)
-        values.append(average)
-        channel_stats.initchan(key, *values)
-
-def update_user_count(server=None, channel=None):
-    if isinstance(channel, str):
-        channel = set((channel, ))
-    elif channel:
-        channel = set(channel)
-
-    def update_channel(server, channel=None):
-        channel_infolist = weechat.infolist_get('irc_channel', '', server)
-        while weechat.infolist_next(channel_infolist):
-            _channel = weechat.infolist_string(channel_infolist, 'name')
-            if channel:
-                _channel = caseInsensibleKey(_channel)
-                if _channel not in channel:
-                    continue
-            channel_stats[server, _channel] = weechat.infolist_integer(channel_infolist, 'nicks_count')
-        weechat.infolist_free(channel_infolist)
-
-    if not server:
-        server_infolist = weechat.infolist_get('irc_server', '', '')
-        while weechat.infolist_next(server_infolist):
-            server = weechat.infolist_string(server_infolist, 'name')
-            update_channel(server)
-        weechat.infolist_free(server_infolist)
-    else:
-        update_channel(server, channel)
-
-def time_elapsed(elapsed, ret=None, level=2):
-    if ret is None:
-        ret = []
-
-    if not elapsed:
-        return ''
-
-    if elapsed > time_year:
-        years, elapsed = elapsed // time_year, elapsed % time_year
-        ret.append('%s%s' %(years, 'y'))
-    elif elapsed > time_day:
-        days, elapsed = elapsed // time_day, elapsed % time_day
-        ret.append('%s%s' %(days, 'd'))
-    elif elapsed > time_hour:
-        hours, elapsed = elapsed // time_hour, elapsed % time_hour
-        ret.append('%s%s' %(hours, 'h'))
-    elif elapsed > 60:
-        mins, elapsed = elapsed // 60, elapsed % 60
-        ret.append('%s%s' %(mins, 'm'))
-    else:
-        secs, elapsed = elapsed, 0
-        ret.append('%s%s' %(secs, 's'))
-
-    if len(ret) >= level or not elapsed:
-        return ' '.join(ret)
-
-    ret = time_elapsed(elapsed, ret, level)
-    return ret
-
-channel_peak_hooks = CaseInsensibleDict()
-msg_queue_timeout = 15
-def new_channel_peak(key, count, time=0):
-    if not get_config_boolean('show_peaks'):
-        return
-    if key in channel_peak_hooks:
-        weechat.unhook(channel_peak_hooks[key][0])
-        time = channel_peak_hooks[key][1]
-    else:
-        time -= 60 * msg_queue_timeout # add delay in showing the msg
-
-    if time:
-        elapsed = time_elapsed(now() - time)
-        if elapsed:
-            elapsed = '(last peak was %s ago)' %elapsed
-    else:
-        elapsed = ''
-
-    # hook it for show msg 10 min later
-    channel_peak_hooks[key] = (weechat.hook_timer(60000 * msg_queue_timeout, 0, 1, 'new_channel_peak_cb',
-            '%s,%s,New user peak: %s users %s' %(key[0], key[1], count, elapsed)), time)
-
-def new_channel_peak_cb(data, count):
-    debug(data)
-    server, channel, s = data.split(',', 2)
-    buffer = weechat.info_get('irc_buffer', '%s,%s' %(server, channel))
-    if buffer:
-        say('%s%s' %(color_peak, s), buffer=buffer)
-    else:
-        debug('XX falied to get buffer: %s.%s', args=(server, channel))
-    del channel_peak_hooks[server, channel]
-    return WEECHAT_RC_OK
-
-channel_low_hooks = CaseInsensibleDict()
-def new_channel_low(key, count, time=0):
-    if not get_config_boolean('show_lows'):
-        return
-    if key in channel_low_hooks:
-        weechat.unhook(channel_low_hooks[key][0])
-        time = channel_low_hooks[key][1]
-    else:
-        time -= 60 * msg_queue_timeout # add delay in showing the msg
-
-    if time:
-        elapsed = time_elapsed(now() - time)
-        if elapsed:
-            elapsed = '(last low was %s ago)' %elapsed
-    else:
-        elapsed = ''
-
-    # hook it for show msg 10 min later
-    channel_low_hooks[key] = (weechat.hook_timer(60000 * msg_queue_timeout, 0, 1, 'new_channel_low_cb',
-            '%s,%s,New user low: %s %s' %(key[0], key[1], count, elapsed)), time)
-
-def new_channel_low_cb(data, count):
-    debug(data)
-    server, channel, s = data.split(',', 2)
-    buffer = weechat.buffer_search('irc', '%s.%s' %(server, channel))
-    if buffer:
-        say('%s%s' %(color_low, s), buffer=buffer)
-    else:
-        debug('XX falied to get buffer: %s.%s', args=(server, channel))
-    del channel_low_hooks[server, channel]
-    return WEECHAT_RC_OK
-
-# chanstat command
-def chanstat_cmd(data, buffer, args):
-    if args == '--save':
-        write_database()
-        channel_stats.close()
-        say('Channel statistics saved.')
-        return WEECHAT_RC_OK
-    elif args == '--load':
-        load_database()
-        say('Channel statistics loaded.')
-        return WEECHAT_RC_OK
-#    elif args == '--print':
-#        prnt = weechat.command
-
-    channel = weechat.buffer_get_string(buffer, 'localvar_channel')
-    server = weechat.buffer_get_string(buffer, 'localvar_server')
-    key = (server, channel)
-
-    update_user_count(server, channel)
-    # clear any update in queue
-    if key in update_channel_hook:
-        weechat.unhook(update_channel_hook[key][0])
-        del update_channel_hook[key]
-
-    try:
-        chan = channel_stats[server, channel]
-        _now = now()
-        peak_time = time_elapsed(_now - chan.max_date)
-        low_time = time_elapsed(_now - chan.min_date)
-        if peak_time:
-            peak_time = ' (%s ago)' %peak_time
-        if low_time:
-            low_time = ' (%s ago)' %low_time
-        if chan.avrg_period > time_hour:
-            average = ' average: %s%.2f%s users (%s period)' %(color_avg, chan.average,
-                    color_reset, time_elapsed(chan.avrg_period, level=1))
-        else:
-            average = ' (no average yet)'
-        say('%s%s%s user peak: %s%s%s%s lowest: %s%s%s%s%s' %(
-            color_bold, channel, color_reset,
-            color_peak, chan.max, color_reset,
-            peak_time,
-            color_low, chan.max - chan.min, color_reset,
-            low_time, average), buffer=buffer)
-
-        # clear any new peak or low msg in queue
-        if key in channel_peak_hooks:
-            weechat.unhook(channel_peak_hooks[key][0])
-            del channel_peak_hooks[key]
-        if key in channel_low_hooks:
-            weechat.unhook(channel_low_hooks[key][0])
-            del channel_low_hooks[key]
-    except KeyError:
-        say('No statistics available.', buffer=buffer)
-
-    return WEECHAT_RC_OK
-
-def cmd_debug(data, buffer, args):
-    dbg = lambda s, a: debug(s, args=a, name_suffix='dump')
-    dbg('\nStats DB: %s', len(channel_stats))
-    for key in channel_stats.keys():
-        dbg('%s %s - %s', (key[0], key[1], channel_stats[key]))
-
-    dbg('\nUsers: %s', len(domain_list))
-    return WEECHAT_RC_OK
-
-class Queue(dict):
-    """User queue, for ignore many joins from same host (clones)"""
-    def __contains__(self, key):
-        self.clear()
-        return dict.__contains__(self, key)
-
-    def clear(self):
-        _now = now()
-        for key, time in self.items():
-            if (_now - time) > 60:
-                #debug('clearing domain %s from list (count: %s)' %(key, len(self)))
-                del self[key]
-
-domain_list = Queue()
-
-
-# signal callbacks
-def join_cb(data, signal, signal_data):
-    #debug('%s  %s\n%s' %(data, signal, signal_data), 'SIGNAL')
-    global netsplit
-    if netsplit:
-        debug('ignoring, netsplit')
-        if (now() - netsplit) > 30*60: # wait 30 min
-            netsplit = 0
-        return WEECHAT_RC_OK
-
-    server = signal[:signal.find(',')]
-    signal_data = signal_data.split()
-    channel = signal_data[2].strip(':')
-    host = signal_data[0].strip(':')
-    domain = '%s,%s' %(channel, host[host.find('@')+1:])
-    if domain in domain_list:
-        #debug('ignoring %s', args=domain)
-        return WEECHAT_RC_OK
-    else:
-        domain_list[domain] = now()
-    debug(' -- ping %s (%s)', args=(channel,signal[-4:]))
-    add_update_user_hook(server, channel)
-    return WEECHAT_RC_OK
-
-netsplit = 0
-def quit_cb(data, signal, signal_data):
-    #debug('%s  %s\n%s' %(data, signal, signal_data), 'SIGNAL')
-    global netsplit
-    if netsplit:
-        return WEECHAT_RC_OK
-    quit_msg = signal_data[signal_data.rfind(':')+1:]
-    if quit_msg_is_split(quit_msg):
-        netsplit = now()
-        for hook, when in update_channel_hook.itervalues():
-            weechat.unhook(hook)
-        update_channel_hook.clear()
-        debug('NETSPLIT')
-
-    return WEECHAT_RC_OK
-
-def quit_msg_is_split(s):
-    #if 'peer' in s: return True
-    if s.count(' ') == 1:
-        sp = s.find(' ')
-        d1 = s.find('.')
-        d2 = s.rfind('.')
-        if 0 < d1 and 4 < d2 and d1 < sp < d2 and d2 + 1 < len(s):
-            return True
-    return False
-
-update_channel_hook = CaseInsensibleDict()
-update_queue_timeout = 120
-def add_update_user_hook(server, channel):
-    key = (server, channel)
-    if key in update_channel_hook:
-        hook, when = update_channel_hook[key]
-        if (now() - when) > update_queue_timeout//2:
-            debug(' vv rescheduling %s', args=key[1])
-            weechat.unhook(hook)
-        else:
-            return
-    else:
-        debug(' >> scheduling %s', args=key[1])
-
-    # we schedule the channel check for later so we can filter quick joins/parts and netsplits
-    update_channel_hook[key] = (weechat.hook_timer(update_queue_timeout * 1000, 0, 1, 'update_user_count_cb',
-            ','.join(key)), now())
-
-def update_user_count_cb(data, count):
-    server, channel = data.split(',', 1)
-    channels = [ chan for serv, chan in update_channel_hook if server == serv ]
-    if channels:
-        update_user_count(server, channels)
-        for chan in channels:
-            hook, when = update_channel_hook[server, chan]
-            weechat.unhook(hook)
-            del update_channel_hook[server, chan]
-    return WEECHAT_RC_OK
-
-def script_load():
-    load_database()
-    update_user_count()
-
-def script_unload():
-    write_database()
-    channel_stats.close()
-    return WEECHAT_RC_OK
-
-# default settings
-settings = {
-    'path'   :'%h/chanstat',
-    'average_period':'30',
-    'show_peaks' :'on',
-    'show_lows'  :'on',
-    }
-
-if __name__ == '__main__' and import_ok and \
-        weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE,
-        SCRIPT_DESC, 'script_unload', ''):
-    
-    # colors
-    color_delimiter = weechat.color('chat_delimiters')
-    color_chat_nick = weechat.color('chat_nick')
-    color_reset     = weechat.color('reset')
-    color_peak      = weechat.color('green')
-    color_low       = weechat.color('red')
-    color_avg       = weechat.color('brown')
-    color_bold      = weechat.color('white')
-
-    # pretty [chanop]
-    script_nick = '%s[%s%s%s]%s' %(color_delimiter, color_chat_nick, SCRIPT_NAME, color_delimiter,
-            color_reset)
-
-    for opt, val in settings.iteritems():
-               if not weechat.config_is_set_plugin(opt):
-                       weechat.config_set_plugin(opt, val)
-
-    script_load()
-
-    weechat.hook_signal('*,irc_in2_join', 'join_cb', '')
-    weechat.hook_signal('*,irc_in2_part', 'join_cb', '')
-    weechat.hook_signal('*,irc_in2_quit', 'quit_cb', '')
-
-    weechat.hook_command('chanstat', "Display channel's statistics.", '[--save | --load]',
-            "Displays channel peak, lowest and average users for current channel.\n"
-            "  --save: forces saving the stats database.\n"
-            "  --load: forces loading the stats database (Overwriting actual values).\n",
-            #" --print: sends /chanstat output to the current channel.",
-            '--save|--load', 'chanstat_cmd', '')
-
-    weechat.hook_command('chanstat_debug', '', '', '', '', 'cmd_debug', '')
-
-# vim:set shiftwidth=4 tabstop=4 softtabstop=4 expandtab textwidth=100: