Beispiel #1
0
    def __launch(self):
        try:
            # noinspection PyUnresolvedReferences
            from asyncio import WindowsSelectorEventLoopPolicy  # it can be found in windows, sometimes
            set_event_loop_policy(WindowsSelectorEventLoopPolicy()
                                  )  # prevents an error on closing the loop
        except Exception as e:
            debug_ex(e,
                     'set a less error prone event loop policy',
                     LOG,
                     silent=True)

        try:
            self.own_eventloop = True
            self.eventloop = new_event_loop()
            self.eventloop.set_exception_handler(self.__handle_exception)
            self.run(
                hostname=TWITCH_IRC,
                port=TWITCH_IRC_PORT,
                password=self.__token,
                channels=self.__channels,
                tls=True,
                tls_verify=False,
            )
        except Exception as e:
            LOG.warning('IRC bot launch failed.')
            debug_ex(e, 'launching IRC bot', LOG)
Beispiel #2
0
 def __handle_exception(self, loop: AbstractEventLoop, context: dict):
     message = context['message']
     e = context.get('exception', None)
     e = e if e else RuntimeError('Unspecified exception in event loop')
     debug_ex(
         e, f'do something in event loop that ended like this: <{message}>',
         LOG)
def read_dynamic_config(section: str,
                        ignore: List[T],
                        convert_key: Callable[[str], T] = identity,
                        convert_value: Callable[[str], V] = identity,
                        fallback: V = None) -> Dict[T, V]:
    result = {}

    if config.has_section(section):
        for key, value in config.items(section):
            try:
                key = convert_key(key.strip())
            except Exception as e:
                debug_ex(e,
                         f'convert config key [{section}]->{key}: {value}',
                         silent=True)
                key = None

            if key in ignore:
                continue

            try:
                value = convert_value(value.strip())
            except Exception as e:
                debug_ex(e,
                         f'convert config value [{section}]->{key}: {value}',
                         silent=True)
                value = fallback

            if key and value:
                result[key] = value

    return result
Beispiel #4
0
 def __check_elastic(self, nick: str, user_id: int) -> Optional[UserRank]:
     if user_id and self.use_elastic:
         try:
             manual = ManualUserRank.get(user_id, ignore=[404])
             if manual:
                 return manual.rank
         except Exception as e:
             debug_ex(e, f'get manual rank for <{nick}>', LOG)
Beispiel #5
0
 def close(self):
     """
     Revokes token if it was acquired. Intended to be invoked when shutting down the application.
     """
     try:
         self.__revoke_token()
     except Exception as e:
         debug_ex(e, 'revoke token', LOG, silent=True)
    def try_write(self, cdlc: dict) -> Any:
        cdlc_id = cdlc.get('id', None)
        art = cdlc.get('art', '')
        try:
            CustomDLC(_id=cdlc_id).update(art=art)
        except NotFoundError as e:
            LOG.warning('Art update failed for CDLC #%s, probably because it has not been loaded yet.', cdlc_id)
            debug_ex(e, 'fix my sins', LOG, silent=True)

        return None
    def __enter__(self):
        try:
            f = open(self.__file, 'rb')
        except Exception as e:
            debug_ex(e, f'read file <{self.__file}>', LOG, silent=True)
        else:
            with f:
                self.__contents = json.load(f)
                self.__contents.sort(key=lambda c: c.get('snapshot_timestamp', 0))

        self.__writer.start()
Beispiel #8
0
    def __parse_config(self, s: str) -> Tuple[int, int]:
        left, colon, right = s.partition(':')
        try:
            left = max(int(left), 0)
        except Exception as e:
            debug_ex(e, f'convert {left} to int', silent=True)
            return 0, 0

        try:
            return left, max(int(right), 0) if right else 0
        except Exception as e:
            debug_ex(e, f'convert {right} to int', silent=True)
            return left, 0
Beispiel #9
0
 def __with_cookie_jar(self,
                       options: str,
                       on_file: Callable[[IO], T] = identity,
                       trying_to: str = None) -> T:
     if self.__cookie_jar_file:
         try:
             f = open(self.__cookie_jar_file, options)
         except Exception as e:
             if trying_to:
                 debug_ex(e, trying_to, LOG)
         else:
             with f:
                 return on_file(f)
Beispiel #10
0
    def execute(self, sender: str, message: str, respond: ResponseHook) -> bool:
        """
        Parses a message by some sender. If it is an available command, executes it.
        Sender starting with underscore is considered admin (e.g., _console, _test). Such names are not allowed
        by twitch by default.

        Before execution, the command will be checked against user rights and timeouts.

        Hook will be used to send a response, if any.
        Most generic errors (e.g. no such command, no rights, global timeout) will simply be ignored.

        :returns true if execution succeeded, false or None if it failed or never executed in the first place
        """
        if self.__is_command(message):
            name, space, args = message[1:].partition(' ')
            name = name.lower()

            command = self.__commands.get(name, None)
            if not command:
                return LOG.warning('No command with alias <%s> exists.', name)

            if not command.is_available():
                return LOG.warning('Command !%s is missing a required module and thus cannot be executed.', name)

            user = self._users.admin() if self.__is_console_like(sender) else self._users.user(sender)
            required_rank = self.__required_rank(command)
            if not user.has_right(required_rank):
                if self.__just_needs_to_follow(user, required_rank):
                    respond.to_sender(f'Please follow the channel to use !{name}')

                return LOG.warning('<%s> is not authorized to use !%s.', user, name)

            if self._downtime and user.is_limited:
                time_to_wait = self._downtime.remaining(command, user)
                if time_to_wait:
                    time_words = naturaldelta(time_to_wait)
                    if not self._downtime.is_global(command):
                        respond.to_sender(f'You can use !{name} again in {time_words}')

                    return LOG.warning('<%s> has to wait %s before using !%s again.', user, time_words, name)

            try:
                failure = command.execute(user, name, args, respond)
                if not failure and self._downtime and user.is_limited:
                    self._downtime.remember_use(command, user)

                return not failure
            except Exception as e:
                respond.to_sender('Unexpected error occurred. Please try later')
                debug_ex(e, f'executing <{command}>', LOG)
def read_config(section: str,
                key: str,
                convert: Callable[[str], T] = identity,
                fallback: T = None,
                allow_empty: bool = False) -> Optional[T]:
    value = config.get(section, key, fallback=fallback)
    if value is None or value is fallback or not allow_empty and not value:
        return fallback

    try:
        return convert(value.strip())
    except Exception as e:
        debug_ex(e,
                 f'convert config value [{section}]->{key}: {value}',
                 silent=True)
        return fallback
Beispiel #12
0
    def __lazy_all(self,
                   convert: Callable[[Any], Iterator[T]],
                   skip: int = 0,
                   batch: int = None,
                   **call_params) -> Iterator[T]:
        batch = batch if Verify.batch_size(batch) else self.__batch_size

        while True:
            params = call_params.setdefault('params', {})
            params['skip'] = skip
            params['take'] = batch

            r = self.__call(**call_params)
            if not r or not r.text:
                break

            try:
                it = convert(r.json())
                first = next(it, NON_EXISTENT)
                if first is NON_EXISTENT:
                    break

                yield first
                yield from it
            except Exception as e:
                trying_to = call_params['trying_to']
                return debug_ex(e, f'parse response of <{trying_to}> as JSON',
                                LOG)

            skip += batch
Beispiel #13
0
    def __call(self,
               trying_to: str,
               call: Callable[..., Response],
               url: str,
               try_login: bool = True,
               **kwargs) -> Optional[Response]:
        kwargs.setdefault('cookies', self.__cookies)

        try:
            r = call(url=url,
                     timeout=self.__timeout,
                     allow_redirects=False,
                     **kwargs)
        except Exception as e:
            return debug_ex(e, trying_to, LOG)

        if not try_login or not r.is_redirect or not r.headers.get(
                'Location', '') == LOGIN_PAGE:
            return r

        if not self.login():
            LOG.debug('Cannot %s: automatic login to customsforge failed.',
                      trying_to)
            return None

        kwargs.pop('cookies', None)
        return self.__call(trying_to, call, url, try_login=False, **kwargs)
Beispiel #14
0
    def __collect_detailed_response(self, r: Response, lines: List[str]):
        lines.append(f'~ {r.elapsed}s elapsed')
        lines.append('')

        lines.append(f'< {r.status_code} {r.reason}')
        for key, value in r.headers.items():
            lines.append(f'< {key}: {value}')

        if not r.text:
            return lines.append('< EMPTY >')

        try:
            if r.text.strip()[:1] in ['[', '{']:
                return lines.append(json.dumps(r.json(), indent=4))
        except Exception as e:
            if 'json' in r.headers.get('Content-Type', ''):
                lines.append('< COULD NOT PARSE JSON BODY >')
                return debug_ex(e,
                                'parsing JSON response body',
                                HTTP_DUMP,
                                silent=True)

        size = len(r.text)
        lines.append(r.text if size <= self.__max_dump else
                     f'< RESPONSE BODY TOO LARGE ({size} bytes) >')
def _with_elastic(do: str, action: Callable[[Elasticsearch], None]) -> bool:
    try:
        action(get_connection())
        return True
    except Exception as e:
        LOG.warning('Could not %s elastic. Perhaps client is down?', do)
        return debug_ex(e, f'{do} elastic', LOG, silent=True)
Beispiel #16
0
    def __check_twitch(self, nick: str, user_id: int) -> Optional[UserRank]:
        if user_id:
            with self.__rank_lock:
                try:
                    cached = self.__rank_cache.get(str(user_id))
                    if cached:
                        return cached['rank']

                    is_follower = self.__tw.is_following(
                        self.__streamer, user_id)
                    live = UserRank.FLWR if is_follower else UserRank.VWR
                    self.__rank_cache.set(
                        str(user_id), {'rank': live},
                        duration=self.__cache_duration(is_follower))
                    return live
                except Exception as e:
                    debug_ex(e, f'get live twitch rank for <{nick}>', LOG)
Beispiel #17
0
    def __create_and_cache(self, command_class, **beans):
        try:
            command = command_class(tc=self, commands=self.__commands, **beans)
        except Exception as e:
            LOG.warning('Command <!%s> (and aliases) not available. See logs.', command_class.__name__.lower())
            return debug_ex(e, f'create command for class {command_class}', LOG, silent=True)

        self._add_command(command)
    def __write_from_queue(self):
        while not self.__is_done.is_set() or not self.__write_queue.empty():
            try:
                cdlc = self.__write_queue.get(timeout=1)
            except Empty:
                continue

            try:
                f = self.__get_temp_file()
                if self.__first_dump:
                    self.__first_dump = False
                else:
                    f.write(',')

                f.write(json.dumps(cdlc, indent=4))
            except Exception as e:
                self.__is_broken.set()
                debug_ex(e, 'write to temp file', LOG)
                break
Beispiel #19
0
    def remove_manual(self, nick: str) -> bool:
        """
        Removes manual rank for given nick. Only possible if twitch & elastic index are available.

        :returns true if rank was removed, false otherwise
        """
        user_id = self.id(nick)
        if self.use_elastic and user_id:
            try:
                return ManualUserRank(_id=user_id).delete()
            except Exception as e:
                return debug_ex(e, f'remove rank for <{nick}>', LOG)
Beispiel #20
0
    def set_manual(self, nick: str, rank: UserRank) -> bool:
        """
        Sets manual rank for given nick. Only possible if twitch & elastic index are available.

        :returns true if rank was updated, false otherwise
        """
        user_id = self.id(nick)
        if self.use_elastic and user_id:
            try:
                return ManualUserRank(_id=user_id).set_rank(rank)
            except Exception as e:
                return debug_ex(e, f'set <{nick}> to rank {rank.name}', LOG)
Beispiel #21
0
    def get_id(self, nick_or_id: Union[str, int]) -> Optional[int]:
        """
        Coerces given nick or twitch id into twitch id, if possible
        """
        try:
            with self.__user_id_lock:
                user = self.__user_cache.get(nick_or_id)
                if not user:
                    user = self.__call(self.__get_user, nick_or_id)
                    self.__user_cache.update({int(user.id): user, user.login: user})

                return int(user.id)
        except Exception as e:
            return debug_ex(e, f'find twitch user id for <{nick_or_id}>', LOG)
    def close(self):
        self.__is_done.set()
        self.__writer.join()

        if self.__is_broken.is_set():
            LOG.error('An error occurred while trying to write to temp file.')

        if self.__temp_dump:
            try:
                with self.__temp_dump:
                    self.__temp_dump.write(']')

                if not self.__is_broken.is_set():
                    shutil.copy(self.__temp_dump.name, self.__file)
                    LOG.warning('CDLC JSON dump file ready: %s.', self.__file)
            except Exception as e:
                LOG.error('Writing to file <%s> failed. Please check temp file if it still exists: %s',
                          self.__file, self.__temp_dump.name)
                return debug_ex(e, f'write from temp to file <{self.__file}>', LOG, silent=True)

            try:
                os.remove(self.__temp_dump.name)
            except Exception as e:
                debug_ex(e, f'clean up temp file {self.__temp_dump.name}', LOG, silent=True)
Beispiel #23
0
def prepare_doc(es, doc):
    try:
        bulk(es, (d.to_dict(True) for d in prepare_index(doc)), refresh=True)
    except Exception as e:
        debug_ex(e, 'prepare elasticsearch index for testing')
        pytest.skip('Elasticsearch setup failed. See logs for exception.')