# -*- coding: utf-8 -*- ####################################################################################### SOF ####################################################################################### SOF from irc3.plugins.command import command import irc3 import os import re from datetime import datetime, timedelta import timeago ########################################################################################### ########################################################################################### D="\x02\x0315" G="\x02\x0314" S="\x02\x0304" P="\x02\x0305" X="\x02\x0307" R="\x02\x0302" B="\x02\x0312" ########################################################################################### ########################################################################################### class DBIO(): ####################################################################################### ####################################################################################### threshold=30 negated=['matrixbot','van'] lurkers=[] verified=[] hardchatters=[] seen_users=[] search_users=[] channel_users=[] channel=[] LOG=[] ########################################################################################### ########################################################################################### @irc3.plugin class Plugin: def __init__(self,bot): self.bot=bot self.bot.dbio=DBIO() ################################################################################### ################################################################################### try: self.bot.dbio.hardchatters=self.bot.db.getlist("hardchatters", []) print('<<< loaded database >>> hardchatters') except Exception as e: print(f'error: dsa_plugin > __init__ > hardchatter storage database: {e}') self.bot.dbio.hardchatters=[] ################################################################################### ################################################################################### try: self.bot.dbio.lurkers=self.bot.db.getlist("lurkers", []) print('<<< loaded database >>> lurkers') except Exception as e: print(f'error: dsa_plugin > __init__ > lurkers storage database: {e}') self.bot.dbio.lurkers=[] ####################################################################################### ####################################################################################### def log(self,s): self.bot.dbio.LOG.append(s) ####################################################################################### ####################################################################################### def log_play(self,target): for _ in self.bot.dbio.LOG: self.bot.privmsg(target,_); print(_) self.bot.dbio.LOG=[] ####################################################################################### ####################################################################################### def dbentry_add(self,database,record): buffer=self.bot.db.getlist(database,[]) buffer.append(record) buffer=list(set(buffer)) buffer.sort() exec(f'self.bot.dbio.{database}=buffer') self.bot.db.setlist(database,buffer) print(f'added {record} entry to {database} database') ####################################################################################### ####################################################################################### def dbentry_remove(self,database,record): buffer=self.bot.db.getlist(database,[]) status=False if record in buffer: buffer.remove(record) status=True buffer=list(set(buffer)) buffer.sort() exec(f'self.bot.dbio.{database}=buffer') self.bot.db.setlist(database,buffer) print(f'removed {record} entry from {database} database - status: {status}') ####################################################################################### ####################################################################################### def epochtimestamp(self,timestamp): year,month,day=timestamp.split('T')[0].split('-') hour,minute,second=timestamp.split('T')[1].split(':') second,millisecond=second.split('.') year,month,day,hour,minute,second,millisecond=[int(x) for x in [year,month,day,hour,minute,second,millisecond]] epoch_timestamp_past=datetime(year,month,day,hour,minute,second,millisecond).timestamp() return epoch_timestamp_past ####################################################################################### ####################################################################################### def epochdelta(self,timestamp): year,month,day=timestamp.split('T')[0].split('-') hour,minute,second=timestamp.split('T')[1].split(':') second,millisecond=second.split('.') year,month,day,hour,minute,second,millisecond=[int(x) for x in [year,month,day,hour,minute,second,millisecond]] epoch_timestamp_past=datetime(year,month,day,hour,minute,second,millisecond).timestamp() epoch_timestamp_present=datetime.now().timestamp() epochdelta=epoch_timestamp_present-epoch_timestamp_past distance=timeago.format(timedelta(seconds=epochdelta),datetime.now()) return distance ####################################################################################### ####################################################################################### @irc3.event(irc3.rfc.NEW_NICK) def on_nick_change_for_dsa_hardchatradar(self, nick, new_nick): for _ in self.bot.channels.keys(): if _[0]=="#": USER=str(new_nick).lower() self.dsa_hardchatradar(USER,_) ####################################################################################### ####################################################################################### @irc3.event(irc3.rfc.JOIN) def on_channel_join_for_dsa_hardchatradar(self,mask,channel,**kw): USER=str(mask.nick).lower self.dsa_hardchatradar(mask.nick,channel) ####################################################################################### ####################################################################################### def dsa_hardchatradar(self, USER, channel): """radar for hardchatters who join a channel""" ############################################################################### ############################################################################### try: if USER != self.bot.nick: ####################################################################### key=self.bot.db.getlist(f'last_msg_for_{USER}') ####################################################################### if key==None: if USER in self.bot.dbio.negated: if not USER in self.bot.dbio.hardchatters: self.dbentry_add('hardchatters',USER) print(f'{USER}:{channel} {B}>>> {X}whitelisted user {G}- {X}negated') else: print(f'{USER}:{channel} {B}>>> {X}whitelisted user {G}- {X}negated - already in db') else: if not USER in self.bot.dbio.lurkers: self.dbentry_add('lurkers',USER) self.log(f'{G}{USER}:{channel} {S}> rando - set lurker status - no interaction history') else: print(f'join {USER}:{channel} >>> lurker - already in db') ####################################################################### else: records_timestamps=[] records_privmsgs=[] records_newnicks=[] for _key in key: timestamp=self.epochdelta(_key['time']) records_timestamps.append(_key['time']) if _key['type']=='privmsg': records_privmsgs.append(timestamp) else: records_newnicks.append(timestamp) ####################################################################### records_timestamps.sort() newest_timestamp=self.epochdelta(records_timestamps[-1]) records_timestamps.reverse() oldest_timestamp=self.epochdelta(records_timestamps[-1]) oldest_timestamp_epoch=self.epochtimestamp(records_timestamps[-1]) history_timestamp=datetime.now().timestamp()-oldest_timestamp_epoch ####################################################################### lurker_status=False hardchat_status=False history_status=False if history_timestamp>=self.bot.dbio.threshold: history_status=True if not USER in self.bot.dbio.hardchatters: self.dbentry_add('hardchatters',USER) print(f'join {USER}:{channel} > history match - new entry added to hardchatters database') if USER in self.bot.dbio.hardchatters: hardchat_status=True if USER in self.bot.dbio.lurkers: lurker_status=True ####################################################################### if records_privmsgs==0: self.dbentry_add('lurkers',USER) lurker_status=True else: if history_status or hardchat_status and not lurker_status: cmd=f"MODE {channel} +v {USER}" self.bot.send(cmd) print(f'join {USER}:{channel} > pass - history_status: {history_status} - hardchat_status: {hardchat_status} - lurker_status: {lurker_status}') else: print(f'join {USER}:{channel} > fail - history_status: {history_status} - hardchat_status: {hardchat_status} - lurker_status: {lurker_status}') except Exception as e: msg=f'error: dsa_plugin:hardchat_radar - {e}' print(msg); self.bot.privmsg(channel,self.bot.emo(msg)) ####################################################################################### ####################################################################################### @command(permission='view') def dsa_hardchatter(self, mask, target, args): """dsa_hardchatter %%dsa_hardchatter ... """ try: OP_FLAG=False channel = self.bot.channels[target] for _, nick in channel.modes.items(): for __ in _: modes=["%","@","&","~"] if __ in modes: if mask.nick in nick: OP_FLAG=True if OP_FLAG: user = ''.join(args['']) self.dbentry_add('hardchatters',user) self.bot.privmsg(target, self.bot.emo(f'hardchatter: {user}')) cmd=f"MODE {target} +v {user}" self.bot.send(cmd) else: return except Exception as e: msg=f'error: dsa_plugin:hardchatter > {e}' print(msg); self.bot.privmsg(target,self.bot.emo(msg)) ####################################################################################### ####################################################################################### @command(permission='view') def dsa_softchatter(self, mask, target, args): """dsa_softchatter %%dsa_softchatter ... """ user = ''.join(args['']) try: OP_FLAG=False channel = self.bot.channels[target] for _, nick in channel.modes.items(): for __ in _: modes=["%","@","&","~"] if __ in modes: if mask.nick in nick: OP_FLAG=True if OP_FLAG: self.dbentry_remove('hardchatters',user) self.bot.privmsg(target, self.bot.emo(f'softchatted: {user}')) cmd=f"MODE {target} -v {user}" self.bot.send(cmd) else: return except Exception as e: msg=f'error: dsa_plugin:softchatter > {e}' print(msg); self.bot.privmsg(target,self.bot.emo(msg)) ####################################################################################### ####################################################################################### @command(permission='view') def dsa_hardchatters(self, mask, target, args): """dsa_hardchatters %%dsa_hardchatters """ try: OP_FLAG=False channel = self.bot.channels[target] for _, nick in channel.modes.items(): for __ in _: modes=["%","@","&","~"] if __ in modes: if mask.nick in nick: OP_FLAG=True if OP_FLAG: if self.bot.dbio.hardchatters: _msg = re.findall(r'.{1,400}(?:\s+|$)', f'{B}hardchatters: {X}{self.bot.dbio.hardchatters}') for _ in _msg: self.bot.privmsg(target,self.bot.emo(_)) else: self.bot.privmsg(target,self.bot.emo(f"{R}no hardchatters")) else: return except Exception as e: msg=f'error: dsa_plugin:hardchatters > {e}' self.bot.privmsg(target,self.bot.emo(msg)); print(msg) ####################################################################################### ####################################################################################### @command(permission='view') def dsa_lurkers(self, mask, target, args): """dsa_lurkers %%dsa_lurkers """ try: OP_FLAG=False channel = self.bot.channels[target] for _, nick in channel.modes.items(): for __ in _: modes=["%","@","&","~"] if __ in modes: if mask.nick in nick: OP_FLAG=True if OP_FLAG: if self.bot.dbio.lurkers: _msg = re.findall(r'.{1,400}(?:\s+|$)', f'{B}lurkers: {X}{self.bot.dbio.lurkers}') for _ in _msg: self.bot.privmsg(target, self.bot.emo(_)) else: self.bot.privmsg(target, self.bot.emo(f"{R}no lurkers")) ########################################################################### except Exception as e: msg=f'error: dsa_plugin:lurkers > {e}' self.bot.privmsg(target,self.bot.emo(msg)); print(msg) ####################################################################################### ####################################################################################### @command(permission='admin') def dsa_analyze(self,mask,target,args): """dsa_analyze %%dsa_analyze ... """ data=''.join(args['']).strip() ################################################################################### db_info=self.bot.config['storage'] base_dir=f"{os.getcwd()}/{db_info.split('/')[-2]}" db_pathname=f"{base_dir}/{db_info.split('/')[-1]}" f=open(db_pathname,'r');l=f.read();f.close() ################################################################################### db=l.splitlines() for _ in db: if not _.find('last_msg_for_')==-1: self.bot.dbio.seen_users.append(_.split('last_msg_for_')[1].split('"')[0]) ################################################################################### nicks=[]; key=''; USER=data.lower(); SEARCH_FLAG=False ################################################################################### try: key=self.bot.db.getlist(f'last_msg_for_{USER}') except Exception as e: print(f'error: dsa:nicks -> pulling db key for last_msg_for: {e}') if not key: SEARCH_FLAG=True ################################################################################### self.bot.dbio.search_users=[] IS_RUNNING=True ################################################################################### LOOP=1 if SEARCH_FLAG: for _ in db: if not _.find('last_msg_for_')==-1: if not _.find(USER)==-1: self.bot.dbio.search_users.append(_.split('last_msg_for_')[-1].split('":')[0]) LOOP=len(self.bot.dbio.search_users) ################################################################################### for i in range(0,LOOP): if SEARCH_FLAG: try: USER=self.bot.dbio.search_users[i] key=self.bot.db.getlist(f'last_msg_for_{USER}') except: IS_RUNNING=False msg=f"{B}< {R}no {G}{USER} {R}ircnick(s) or similar were found {B}>" self.bot.privmsg(target,msg) ############################################################################### newnicks=0; privmsgs=0; lastmsg=[]; lasttime=[] ############################################################################### try: for _ in key: if _['type']=='newnick': newnicks+=1 elif _['type']=='privmsg': privmsgs+=1 lastmsg.append(_['msg']) lasttime.append(_['time']) except Exception as e: print(f'error: dsa:nicks:search -> no items of a key means no db operation: {e}') ############################################################################### self.log(f'{"#"*69}') ############################################################################### if newnicks>0 or privmsgs>0: self.log(f"{D}<<< {R}{USER} {G}- {R}ircnicks{B}: {R}{int(newnicks)+1} {G}- {R}messages{B}: {R}{privmsgs} {D}>>>") ############################################################################### if privmsgs>0: for i in range(0,privmsgs): self.log(f'{"#"*69}') self.log(f'{D}{lasttime[i]} {B}> {R}{USER} {B}> {D}{lastmsg[i]}') ############################################################################### if newnicks==0: self.log(f"!!! no alternate ircnicks and not enough data -- softchats !!!") self.log_play(target) if i==0 and not SEARCH_FLAG: IS_RUNNING=False return elif i==LOOP and SEARCH_FLAG: IS_RUNNING=False return ############################################################################### self.log(f'{"#"*69}') ############################################################################### while IS_RUNNING: ########################################################################### if not SEARCH_FLAG: msg=f"{D}<<< {R}near region{D} >>>" else: msg=f"{D}<<< {R}near region - issues{D} >>>" self.log(msg); self.log(f'{"#"*69}') ########################################################################### for _ in key: if _['type']=='newnick': f,t=_['msg'].split(' changed nick to ') self.log(f'{D}{_["time"]} {B}> {R}from ircnick {G}{f} {R}to ircnick {G}{t}') nicks.append(f) ########################################################################### nicks=list(set(nicks)) ########################################################################### self.log(f'{"#"*69}') ########################################################################### if not SEARCH_FLAG: msg=f'{D}[ {G}{USER} {D}- {R}alternate ircnicks - direct referfence{D}]{B}: {R}{" ".join(nicks)}' else: msg=f'{D}[ {G}{USER} {D}- {R}alternate ircnicks - keyworded{D}]{B}: {R}{" ".join(nicks)}' ########################################################################### self.log(msg); self.log(f'{"#"*69}'); self.log(f"{D}<<< {R}distant region{D} >>>"); self.log(f'{"#"*69}') for _ in nicks: self.log(f'[{_}]') try: key=self.bot.db.getlist(f'last_msg_for_{_}') for __ in key: ############################################################### if __['type']=='privmsg': self.log(f'\t{D}{__["time"]} {B}> {R}{_} {B}> {D}{__["msg"]}"') ############################################################### elif __['type']=='newnick': f,t=__['msg'].split(' changed nick to ') self.log(f'\t{D}{__["time"]} {B}> {R}to ircnick {B}{t} {R}from ircnick {G}{f}') oldkey="" IS_OK=True while IS_OK==True: try: keysub=self.bot.db.getlist(f'last_msg_for_{f}') if oldkey==keysub: break oldkey=keysub for ___ in keysub: if ___['type']=='newnick': f,t=___['msg'].split(' changed nick to ') self.log(f'\t{D}{___["time"]} {B}> {R}to ircnick {G}{t} {R}from ircnick {G}{f}"') elif __['type']=='privmsg': self.log(f'\t{D}{__["time"]} {B}> {R}{_} {B}> {D}{__["msg"]}"') except Exception as e: IS_OK=False print(f'error: dsa:dirties:IS_OK -> pulling db key for last_msg_for_{f}: {e}') ############################################################### except Exception as e: self.log(f'\t{D}<{R}none{D}>') print(f'error: dsa:dirties -> pulling db key for last_msg_for_{_}: {e}') ########################################################################### if SEARCH_FLAG: self.log(f'{B}<<< {R}total instances found{X}: {R}{LOOP} {B}>>>') self.log_play(target) if SEARCH_FLAG: try: self.search_users.reverse() self.pop() self.search_users.reverse() except: IS_RUNNING=False else: IS_RUNNING=False ####################################################################################### ####################################################################################### @command(permission='admin') def dsa_scan(self,mask,target,args): """tool to scan and designate %%dsa_scan """ OP_FLAG=False channel=self.bot.channels[target] for _, nick in channel.modes.items(): for __ in _: modes=["%","@","&","~"] if __ in modes: if mask.nick in nick: OP_FLAG=True if OP_FLAG: self.log(f'{B}<<< {X}analyzing {target} users for lurkers {B}>>>') ############################################################################### ############################################################################### if not target in self.bot.dbio.channel: self.bot.dbio.channel.append(target) self.bot.dbio.channel_users.append(channel) else: index=self.bot.dbio.channel.index(target) self.bot.dbio.channel_users[index]=self.bot.channels[target] ############################################################################### ############################################################################### for _ in channel: USER=_.lower() try: key=self.bot.db.getlist(f'last_msg_for_{USER}') if key==None: if USER in self.bot.dbio.negated: if not USER in self.bot.dbio.hardchatters: self.dbentry_add('hardchatters',USER) self.log(f'{B}{USER} {B}>>> {X}whitelisted user {G}- {X}negated') else: print(f'already in db - {B}{USER} {B}>>> {X}whitelisted user {G}- {X}negated') else: if not USER in self.bot.dbio.lurkers: self.dbentry_add('lurkers',USER) self.log(f'{G}{USER} {S}>>> {G}- {S}given a lurker status - no record') else: print(f'already in db - {G}{USER} {S}>>> {S}lurker') cmd=f"MODE {target} -v {USER}" self.bot.send(cmd) else: ################################################################### ################################################################### records=len(key) records_timestamps=[] records_privmsgs=[] records_newnicks=[] for _key in key: timestamp=self.epochdelta(_key['time']) records_timestamps.append(_key['time']) if _key['type']=='privmsg': records_privmsgs.append(timestamp) else: records_newnicks.append(timestamp) ################################################################### ################################################################### records_timestamps.sort() idle_timestamp=self.epochtimestamp(records_timestamps[-1]) newest_timestamp=self.epochdelta(records_timestamps[-1]) records_timestamps.reverse() oldest_timestamp=self.epochdelta(records_timestamps[-1]) oldest_timestamp_epoch=self.epochtimestamp(records_timestamps[-1]) history_timestamp=datetime.now().timestamp()-oldest_timestamp_epoch ################################################################### ################################################################### idle_status=False lurker_status=False history_status=False hardchat_status=False idle_threshold=24*(60*60) idle_time=datetime.now().timestamp()-idle_timestamp if history_timestamp>=self.bot.dbio.threshold: history_status=True if idle_time >= idle_threshold: idle_status=True if USER in self.bot.dbio.hardchatters: hardchat_status=True if USER in self.bot.dbio.lurkers: lurker_status=True ################################################################### ################################################################### if records_privmsgs==0: self.dbentry_add('lurkers',USER) lurker_status=True self.log(f'{B}{USER} {X}>>> {R}interactions: {records} - {X}last time spoken: never - {R}oldest/newest interaction: {oldest_timestamp}{B}/{R}{newest_timestamp} - {X}idle: {idle_status} - {R}hardchatter: {hardchat_status} - {X}lurker: {lurker_status}') else: try: if USER in self.bot.dbio.lurkers: self.log(f'{G}{USER} {B}- {S}has a lurker status') cmd=f"MODE {target} -v {USER}" self.bot.send(cmd) else: if not USER in self.bot.dbio.hardchatters: self.dbentry_add('hardchatters',USER) cmd=f"MODE {target} +v {USER}" self.bot.send(cmd) self.log(f'{B}{USER} {X}>>> {R}interactions: {records} - {X}last time spoken: {records_privmsgs[-1]} - {R}oldest/newest interaction: {oldest_timestamp}{B}/{R}{newest_timestamp} - {X}idle: {idle_status} - {R}hardchatter: {hardchat_status} - {X}lurker: {lurker_status} - {R}history: {history_status}') except Exception as e: msg=f'error: dsa_plugin:hardchattting > {e}' print(msg); self.bot.privmsg(target,self.bot.emo(msg)) ####################################################################### ####################################################################### except Exception as e: print(f'error: dsa:lurkers > {e}') ############################################################################### ############################################################################### self.log_play(target) ####################################################################################### ####################################################################################### @command(permission='admin') def dsa_set_threshold(self,mask,target,args): """dsa_set_threshold - sets a threshold on distance in days - if there is no record within that amount of time then they fall below the automated system. default value is 30 days. 90 day threshold example: ?dsa_set_threshold 90 %%dsa_set_threshold ... """ ################################################################################### ################################################################################### data=''.join(args['']).strip() ################################################################################### ################################################################################### try: self.bot.dbio.threshold=int(data)*24*(60*60) msg=f'{R}{mask.nick}{B}: {R}dsa_plugin:dsa_threshold set to {B}{data} days' except Exception as e: msg=f'{mask.nick}: error - dsa_plugin:dsa_threshold > {e}' print(msg); self.bot.privmsg(target,self.bot.emo(msg)) ####################################################################################### ####################################################################################### #### EOF CLASS INSTANCE EOF CLASS INSTANCE EOF CLASS INSTANCE EOF CLASS INSTANCE #### EOF CLASS INSTANCE EOF CLASS INSTANCE EOF CLASS INSTANCE EOF CLASS INSTANCE ####################################################################################### EOF ####################################################################################### EOF