2019-06-13 10:53:47 +00:00
|
|
|
import re, typing, uuid
|
2019-09-17 09:21:31 +00:00
|
|
|
from src import EventManager, IRCBot, IRCBuffer, IRCLine, IRCObject, IRCServer
|
|
|
|
from src import IRCUser, utils
|
2016-03-29 11:56:58 +00:00
|
|
|
|
2019-06-13 10:53:47 +00:00
|
|
|
RE_MODES = re.compile(r"[-+]\w+")
|
2019-09-02 13:07:39 +00:00
|
|
|
SETTING_CACHE_EXPIRATION = 60.0*5.0 # 5 minutes
|
2019-06-13 10:53:47 +00:00
|
|
|
|
2018-10-01 12:48:55 +00:00
|
|
|
class Channel(IRCObject.Object):
|
2018-12-02 09:56:57 +00:00
|
|
|
name = ""
|
2018-10-30 14:58:48 +00:00
|
|
|
def __init__(self, name: str, id, server: "IRCServer.Server",
|
|
|
|
bot: "IRCBot.Bot"):
|
2019-01-24 17:12:50 +00:00
|
|
|
self.name = server.irc_lower(name)
|
2018-08-18 23:19:53 +00:00
|
|
|
self.id = id
|
2016-03-29 11:56:58 +00:00
|
|
|
self.server = server
|
|
|
|
self.bot = bot
|
|
|
|
self.topic = ""
|
2019-10-08 13:55:36 +00:00
|
|
|
self.topic_setter = None # type: typing.Optional[IRCLine.Hostmask]
|
2016-03-29 11:56:58 +00:00
|
|
|
self.topic_time = 0
|
2018-11-20 14:08:36 +00:00
|
|
|
self.users = set([]) # type: typing.Set[IRCUser.User]
|
|
|
|
self.modes = {} # type: typing.Dict[str, typing.Set]
|
2020-01-22 18:01:22 +00:00
|
|
|
self.mode_lists: typing.Dict[str, typing.Set[str]] = {}
|
2018-11-20 14:08:36 +00:00
|
|
|
self.user_modes = {} # type: typing.Dict[IRCUser.User, typing.Set]
|
2016-03-29 11:56:58 +00:00
|
|
|
self.created_timestamp = None
|
2018-09-11 09:15:16 +00:00
|
|
|
self.buffer = IRCBuffer.Buffer(bot, server)
|
2020-01-23 12:23:27 +00:00
|
|
|
self.seen_modes = False
|
2018-08-28 11:23:57 +00:00
|
|
|
|
2019-09-02 13:07:39 +00:00
|
|
|
self._setting_cache_prefix = "channelsetting%s-" % self.id
|
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def __repr__(self) -> str:
|
2018-08-28 11:23:57 +00:00
|
|
|
return "IRCChannel.Channel(%s|%s)" % (self.server.name, self.name)
|
2018-10-30 14:58:48 +00:00
|
|
|
def __str__(self) -> str:
|
2018-10-01 12:48:55 +00:00
|
|
|
return self.name
|
2018-07-15 12:39:15 +00:00
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def set_topic(self, topic: str):
|
2016-03-29 11:56:58 +00:00
|
|
|
self.topic = topic
|
2019-09-16 17:38:46 +00:00
|
|
|
def set_topic_setter(self, hostmask: IRCLine.Hostmask):
|
|
|
|
self.topic_setter = hostmask
|
2018-10-30 14:58:48 +00:00
|
|
|
def set_topic_time(self, unix_timestamp: int):
|
2016-03-29 11:56:58 +00:00
|
|
|
self.topic_time = unix_timestamp
|
2018-07-15 12:39:15 +00:00
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def add_user(self, user: IRCUser.User):
|
2016-03-29 11:56:58 +00:00
|
|
|
self.users.add(user)
|
2018-10-30 14:58:48 +00:00
|
|
|
def remove_user(self, user: IRCUser.User):
|
2016-03-29 11:56:58 +00:00
|
|
|
self.users.remove(user)
|
2018-07-02 15:34:29 +00:00
|
|
|
for mode in list(self.modes.keys()):
|
2018-10-03 15:44:00 +00:00
|
|
|
if mode in self.server.prefix_modes and user in self.modes[mode]:
|
2018-07-02 15:15:26 +00:00
|
|
|
self.modes[mode].discard(user)
|
|
|
|
if not len(self.modes[mode]):
|
|
|
|
del self.modes[mode]
|
2018-10-03 15:44:44 +00:00
|
|
|
if user in self.user_modes:
|
2018-10-04 03:41:28 +00:00
|
|
|
del self.user_modes[user]
|
2018-10-30 14:58:48 +00:00
|
|
|
def has_user(self, user: IRCUser.User) -> bool:
|
2016-04-18 15:50:19 +00:00
|
|
|
return user in self.users
|
2018-07-15 12:39:15 +00:00
|
|
|
|
2019-06-13 10:53:47 +00:00
|
|
|
def mode_str(self) -> str:
|
|
|
|
modes = [] # type: typing.List[typing.Tuple[str, typing.List[str]]]
|
|
|
|
# sorta alphanumerically by mode char
|
|
|
|
modes_iter = sorted(self.modes.items(), key=lambda mode: mode[0])
|
|
|
|
|
|
|
|
for mode, args in modes_iter:
|
|
|
|
# not list mode (e.g. +b) and not prefix mode (e.g. +o)
|
|
|
|
if (not mode in self.server.channel_list_modes and
|
|
|
|
not mode in self.server.prefix_modes):
|
|
|
|
args_list = typing.cast(typing.List[str], list(args))
|
|
|
|
modes.append((mode, args_list))
|
|
|
|
|
|
|
|
# move modes with args to the front
|
|
|
|
modes.sort(key=lambda mode: not bool(mode[1]))
|
|
|
|
|
|
|
|
out_modes = "".join(mode for mode, args in modes)
|
|
|
|
out_args = " ".join(args[0] for mode, args in modes if args)
|
|
|
|
|
2019-09-10 14:10:14 +00:00
|
|
|
if out_modes:
|
|
|
|
return "+%s%s" % (out_modes, " %s" % out_args if out_args else "")
|
|
|
|
else:
|
|
|
|
return ""
|
2019-06-13 10:53:47 +00:00
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def add_mode(self, mode: str, arg: str=None):
|
2016-03-29 11:56:58 +00:00
|
|
|
if not mode in self.modes:
|
|
|
|
self.modes[mode] = set([])
|
2018-08-30 10:12:48 +00:00
|
|
|
if arg:
|
2018-10-03 15:44:00 +00:00
|
|
|
if mode in self.server.prefix_modes:
|
2018-08-30 10:12:48 +00:00
|
|
|
user = self.server.get_user(arg)
|
2018-07-02 15:15:26 +00:00
|
|
|
if user:
|
|
|
|
self.modes[mode].add(user)
|
2018-10-03 15:44:44 +00:00
|
|
|
if not user in self.user_modes:
|
|
|
|
self.user_modes[user] = set([])
|
|
|
|
self.user_modes[user].add(mode)
|
2018-07-02 15:15:26 +00:00
|
|
|
else:
|
2018-08-30 10:12:48 +00:00
|
|
|
self.modes[mode].add(arg.lower())
|
2018-10-30 14:58:48 +00:00
|
|
|
def remove_mode(self, mode: str, arg: str=None):
|
2019-01-16 11:59:36 +00:00
|
|
|
if not arg:
|
|
|
|
if mode in self.modes:
|
|
|
|
del self.modes[mode]
|
2016-03-29 11:56:58 +00:00
|
|
|
else:
|
2018-10-03 15:44:00 +00:00
|
|
|
if mode in self.server.prefix_modes:
|
2018-08-30 10:12:48 +00:00
|
|
|
user = self.server.get_user(arg)
|
2018-07-02 15:15:26 +00:00
|
|
|
if user:
|
2018-10-18 20:07:08 +00:00
|
|
|
if mode in self.modes:
|
|
|
|
self.modes[mode].discard(user)
|
|
|
|
if user in self.user_modes:
|
|
|
|
self.user_modes[user].discard(mode)
|
|
|
|
if not self.user_modes[user]:
|
|
|
|
del self.user_modes[user]
|
2018-07-02 15:15:26 +00:00
|
|
|
else:
|
2018-08-30 10:12:48 +00:00
|
|
|
self.modes[mode].discard(arg.lower())
|
2018-11-26 13:39:26 +00:00
|
|
|
if mode in self.modes and not len(self.modes[mode]):
|
2016-03-29 11:56:58 +00:00
|
|
|
del self.modes[mode]
|
2018-10-30 14:58:48 +00:00
|
|
|
def change_mode(self, remove: bool, mode: str, arg: str=None):
|
2018-08-30 10:12:48 +00:00
|
|
|
if remove:
|
|
|
|
self.remove_mode(mode, arg)
|
|
|
|
else:
|
|
|
|
self.add_mode(mode, arg)
|
2018-07-15 12:39:15 +00:00
|
|
|
|
2019-07-03 07:13:28 +00:00
|
|
|
def parse_modes(self, modes: str, args: typing.List[str]
|
|
|
|
) -> typing.List[typing.Tuple[str, typing.Optional[str]]]:
|
|
|
|
new_modes: typing.List[typing.Tuple[str, typing.Optional[str]]] = []
|
2019-06-13 10:53:47 +00:00
|
|
|
for chunk in RE_MODES.findall(modes):
|
|
|
|
remove = chunk[0] == "-"
|
|
|
|
for mode in chunk[1:]:
|
2019-07-03 07:13:28 +00:00
|
|
|
new_arg = None
|
2019-06-13 10:53:47 +00:00
|
|
|
if mode in self.server.channel_list_modes:
|
2019-07-03 07:13:28 +00:00
|
|
|
new_arg = args.pop(0)
|
2019-07-04 16:23:36 +00:00
|
|
|
elif (mode in self.server.channel_parametered_modes or
|
2019-06-13 10:53:47 +00:00
|
|
|
mode in self.server.prefix_modes):
|
2019-07-03 07:13:28 +00:00
|
|
|
new_arg = args.pop(0)
|
|
|
|
self.change_mode(remove, mode, new_arg)
|
2019-06-13 10:53:47 +00:00
|
|
|
elif mode in self.server.channel_setting_modes:
|
|
|
|
if remove:
|
|
|
|
self.change_mode(remove, mode)
|
|
|
|
else:
|
2019-07-03 07:13:28 +00:00
|
|
|
new_arg = args.pop(0)
|
|
|
|
self.change_mode(remove, mode, new_arg)
|
2019-06-13 10:53:47 +00:00
|
|
|
elif mode in self.server.channel_modes:
|
|
|
|
self.change_mode(remove, mode)
|
|
|
|
|
2019-07-03 07:13:28 +00:00
|
|
|
mode_str = "%s%s" % ("-" if remove else "+", mode)
|
|
|
|
new_modes.append((mode_str, new_arg))
|
|
|
|
return new_modes
|
|
|
|
|
2019-09-02 13:07:39 +00:00
|
|
|
def _setting_cache_key(self, key: str) -> str:
|
|
|
|
return self._setting_cache_prefix+key
|
|
|
|
|
|
|
|
def _cache_setting(self, key: str, value: typing.Any) -> str:
|
|
|
|
return self.bot.cache.temporary_cache(key, value,
|
|
|
|
SETTING_CACHE_EXPIRATION)
|
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def set_setting(self, setting: str, value: typing.Any):
|
2018-08-18 23:19:53 +00:00
|
|
|
self.bot.database.channel_settings.set(self.id, setting, value)
|
2019-09-04 14:24:50 +00:00
|
|
|
cache_key = self._setting_cache_key(setting)
|
2019-09-02 13:07:39 +00:00
|
|
|
self._cache_setting(self._setting_cache_key(setting), value)
|
2018-10-30 14:58:48 +00:00
|
|
|
def get_setting(self, setting: str, default: typing.Any=None
|
|
|
|
) -> typing.Any:
|
2019-09-02 13:07:39 +00:00
|
|
|
cache_key = self._setting_cache_key(setting)
|
2019-09-04 14:26:36 +00:00
|
|
|
value = None
|
2019-09-02 13:07:39 +00:00
|
|
|
if self.bot.cache.has_item(cache_key):
|
2019-09-04 14:26:36 +00:00
|
|
|
value = self.bot.cache.get(cache_key)
|
|
|
|
else:
|
|
|
|
value = self.bot.database.channel_settings.get(self.id, setting, None)
|
|
|
|
self._cache_setting(cache_key, value)
|
2019-09-02 13:07:39 +00:00
|
|
|
|
2019-09-04 14:24:50 +00:00
|
|
|
if value == None:
|
|
|
|
return default
|
|
|
|
else:
|
|
|
|
return value
|
2019-09-02 13:07:39 +00:00
|
|
|
|
2019-10-07 11:46:52 +00:00
|
|
|
def find_settings(self, pattern: str=None, prefix: str=None,
|
|
|
|
default: typing.Any=[]) -> typing.List[typing.Any]:
|
|
|
|
if not pattern == None:
|
|
|
|
return self.bot.database.channel_settings.find(self.id, pattern,
|
|
|
|
default)
|
|
|
|
elif not prefix == None:
|
|
|
|
return self.bot.database.channel_settings.find_prefix(self.id,
|
|
|
|
prefix, default)
|
|
|
|
else:
|
|
|
|
raise ValueError("Please provide 'pattern' or 'prefix'")
|
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def del_setting(self, setting: str):
|
2018-08-18 23:19:53 +00:00
|
|
|
self.bot.database.channel_settings.delete(self.id, setting)
|
2018-08-05 11:52:29 +00:00
|
|
|
|
2019-09-02 13:07:39 +00:00
|
|
|
cache_key = self._setting_cache_key(setting)
|
|
|
|
if self.bot.cache.has_item(cache_key):
|
|
|
|
self.bot.cache.remove(cache_key)
|
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def set_user_setting(self, user_id: int, setting: str, value: typing.Any):
|
2018-08-18 23:19:53 +00:00
|
|
|
self.bot.database.user_channel_settings.set(user_id, self.id,
|
|
|
|
setting, value)
|
2018-10-30 14:58:48 +00:00
|
|
|
def get_user_setting(self, user_id: int, setting: str,
|
|
|
|
default: typing.Any=None) -> typing.Any:
|
2018-08-18 23:19:53 +00:00
|
|
|
return self.bot.database.user_channel_settings.get(user_id,
|
|
|
|
self.id, setting, default)
|
2019-10-07 11:51:44 +00:00
|
|
|
def find_user_settings(self, user_id: int, pattern: str=None,
|
|
|
|
prefix: str=None, default: typing.Any=[]
|
|
|
|
) -> typing.List[typing.Any]:
|
|
|
|
if not pattern == None:
|
|
|
|
return self.bot.database.user_channel_settings.find(user_id,
|
|
|
|
self.id, pattern, default)
|
|
|
|
elif not prefix == None:
|
|
|
|
return self.bot.database.user_channel_settings.find_prefix(user_id,
|
|
|
|
self.id, prefix, default)
|
|
|
|
else:
|
|
|
|
raise ValueError("Please provide 'pattern' or 'prefix'")
|
2018-10-30 14:58:48 +00:00
|
|
|
def del_user_setting(self, user_id: int, setting: str):
|
2018-08-18 23:19:53 +00:00
|
|
|
self.bot.database.user_channel_settings.delete(user_id, self.id,
|
|
|
|
setting)
|
2018-10-30 14:58:48 +00:00
|
|
|
def find_all_by_setting(self, setting: str, default: typing.Any=[]
|
|
|
|
) -> typing.List[typing.Any]:
|
2018-08-31 14:13:56 +00:00
|
|
|
return self.bot.database.user_channel_settings.find_all_by_setting(
|
|
|
|
self.id, setting, default)
|
2016-03-29 11:56:58 +00:00
|
|
|
|
2019-02-18 14:56:25 +00:00
|
|
|
def send_message(self, text: str, tags: dict={}):
|
2019-06-21 17:24:44 +00:00
|
|
|
return self.server.send_message(self.name, text, tags=tags)
|
2019-02-18 14:56:25 +00:00
|
|
|
def send_notice(self, text: str, tags: dict={}):
|
2019-06-21 17:24:44 +00:00
|
|
|
return self.server.send_notice(self.name, text, tags=tags)
|
2019-02-22 22:34:54 +00:00
|
|
|
def send_tagmsg(self, tags: dict):
|
2019-06-21 17:24:44 +00:00
|
|
|
return self.server.send_tagmsg(self.name, tags)
|
2019-02-22 22:34:54 +00:00
|
|
|
|
2020-02-17 15:16:00 +00:00
|
|
|
def _chunks(self, n: int, l: typing.List[str]):
|
|
|
|
return [l[i:i+n] for i in range(0, len(l), n)]
|
|
|
|
|
2019-02-18 14:56:25 +00:00
|
|
|
def send_mode(self, mode: str=None, target: typing.List[str]=None):
|
2019-06-21 17:24:44 +00:00
|
|
|
return self.server.send_mode(self.name, mode, target)
|
2020-02-17 10:16:43 +00:00
|
|
|
def send_modes(self, mode: str, add: bool, args: typing.List[str]):
|
2020-02-25 11:29:02 +00:00
|
|
|
chunk_n = min(6,
|
|
|
|
int(self.server.isupport.get("MODES", "3") or "6"))
|
2020-02-17 15:16:00 +00:00
|
|
|
for chunk in self._chunks(chunk_n, args):
|
2020-02-17 10:16:43 +00:00
|
|
|
mode_str = "%s%s" % ("+" if add else "-", mode*len(chunk))
|
|
|
|
self.server.send_mode(self.name, mode_str, chunk)
|
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_kick(self, target: str, reason: str=None):
|
2019-06-21 17:24:44 +00:00
|
|
|
return self.server.send_kick(self.name, target, reason)
|
2020-02-17 15:16:00 +00:00
|
|
|
def send_kicks(self, targets: typing.List[str], reason: str=None):
|
2020-02-25 11:29:02 +00:00
|
|
|
chunk_n = min(4, self.server.targmax.get("KICK", 1))
|
2020-02-17 15:16:00 +00:00
|
|
|
for chunk in self._chunks(chunk_n, targets):
|
|
|
|
chan_str = ",".join([self.name]*len(chunk))
|
|
|
|
self.server.send_kick(chan_str, ",".join(chunk), reason)
|
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_ban(self, hostmask: str):
|
2019-06-21 17:24:44 +00:00
|
|
|
return self.server.send_mode(self.name, "+b", [hostmask])
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_unban(self, hostmask: str):
|
2019-06-21 17:24:44 +00:00
|
|
|
return self.server.send_mode(self.name, "-b", [hostmask])
|
2020-02-17 10:16:43 +00:00
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_topic(self, topic: str):
|
2019-06-21 17:24:44 +00:00
|
|
|
return self.server.send_topic(self.name, topic)
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_part(self, reason: str=None):
|
2019-06-21 17:24:44 +00:00
|
|
|
return self.server.send_part(self.name, reason)
|
2019-09-12 09:24:02 +00:00
|
|
|
def send_invite(self, target: str):
|
|
|
|
return self.server.send_invite(self.name, target)
|
2016-04-18 15:50:19 +00:00
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def mode_or_above(self, user: IRCUser.User, mode: str) -> bool:
|
2018-10-03 15:44:00 +00:00
|
|
|
mode_orders = list(self.server.prefix_modes)
|
2016-03-29 11:56:58 +00:00
|
|
|
mode_index = mode_orders.index(mode)
|
|
|
|
for mode in mode_orders[:mode_index+1]:
|
2018-07-02 15:15:26 +00:00
|
|
|
if user in self.modes.get(mode, []):
|
2016-03-29 11:56:58 +00:00
|
|
|
return True
|
|
|
|
return False
|
2017-01-27 21:39:51 +00:00
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def has_mode(self, user: IRCUser.User, mode: str) -> bool:
|
2018-10-11 10:20:53 +00:00
|
|
|
return user in self.modes.get(mode, [])
|
|
|
|
|
2019-09-12 21:39:21 +00:00
|
|
|
def get_user_modes(self, user: IRCUser.User) -> typing.Set:
|
2018-11-20 14:08:36 +00:00
|
|
|
return self.user_modes.get(user, set([]))
|