示例#1
0
async def manageAutoJoin(args: ManageBotArgs) -> bool:
    db: DatabaseMain
    if len(args.message) < 3:
        return False

    if len(args.message) < 4:
        return False

    bannedWithReason: Optional[str]
    async with DatabaseMain.acquire() as db:
        bannedWithReason = await db.isChannelBannedReason(
            args.message.lower[3])
    if bannedWithReason is not None:
        channel: str = args.message.lower[3]
        args.send(f'Chat {channel} is banned from joining')
        return True

    if args.message.lower[2] in ['add', 'insert', 'join']:
        async with DatabaseMain.acquire() as db:
            return await autojoin.auto_join_add(
                db, args.message.lower[3], args.send)
    if args.message.lower[2] in ['del', 'delete', 'rem', 'remove', 'part']:
        async with DatabaseMain.acquire() as db:
            return await autojoin.auto_join_delete(
                db, args.message.lower[3], args.send)
    if args.message.lower[2] in ['pri', 'priority']:
        priority: int = 0
        with suppress(ValueError, IndexError):
            priority = int(args.message[4])
        return await auto_join_priority(
            args.message.lower[3], priority, args.send)
    return False
示例#2
0
async def updateQuote(channel: str, nick: str, quoteId: int,
                      quote: str) -> bool:
    db: DatabaseMain
    cursor: aioodbc.cursor.Cursor
    async with DatabaseMain.acquire() as db, await db.cursor() as cursor:
        query: str
        if db.isPostgres:
            query = '''
UPDATE quotes SET quote=?, document=to_tsvector(?)
    WHERE quoteId=? AND broadcaster=?
            '''
            await cursor.execute(query, (quote, quote, quoteId, channel))
        else:
            query = '''
UPDATE quotes SET quote=? WHERE quoteId=? AND broadcaster=?
'''
            await cursor.execute(query, (quote, quoteId, channel))
        if cursor.rowcount == 0:
            return False

        query = '''
INSERT INTO quotes_history (quoteId, createdTime, broadcaster, quote, editor)
    VALUES (?, CURRENT_TIMESTAMP, ?, ?, ?)
'''
        await cursor.execute(query, (quoteId, channel, quote, nick))
        await db.commit()
        return True
async def commandSetAutoPurge(args: ChatCommandArgs) -> bool:
    twitchUser: str = args.message.lower[1]
    nick: str = args.message[1]
    db: DatabaseMain
    cursor: aioodbc.cursor.Cursor
    async with DatabaseMain.acquire() as db, await db.cursor() as cursor:
        query: str = '''\
SELECT 1 FROM auto_purge WHERE broadcaster=? AND twitchUser=?
'''
        await cursor.execute(query, (args.chat.channel, twitchUser))
        row: Optional[Tuple[int]] = await cursor.fetchone()
        if row is None:
            value: bool = False
            if len(args.message) >= 3:
                response: parser.Response
                value = bool(
                    parser.get_response(args.message.lower[2],
                                        default=parser.Yes))
            query = '''
INSERT INTO auto_purge (broadcaster, twitchUser, stopcommands) VALUES (?, ?, ?)
'''
            await cursor.execute(query, (args.chat.channel, twitchUser, value))
            args.chat.send(f'Enabled Auto-Purge on {nick}')
        else:
            query = '''
DELETE FROM auto_purge WHERE broadcaster=? AND twitchUser=?
'''
            await cursor.execute(query, (args.chat.channel, twitchUser))
            args.chat.send(f'Disabled Auto-Purge on {nick}')
        await db.commit()
        await library.reset_auto_purges(args.chat.channel, args.data)
    return True
示例#4
0
async def addQuote(channel: str, nick: str, quote: str) -> int:
    db: DatabaseMain
    cursor: aioodbc.cursor.Cursor
    async with DatabaseMain.acquire() as db, await db.cursor() as cursor:
        query: str
        quoteId: int
        if db.isPostgres:
            query = '''
INSERT INTO quotes (broadcaster, quote, document) VALUES (?, ?, to_tsvector(?))
'''
            await cursor.execute(query, (channel, quote, quote))
            await cursor.execute('SELECT lastval()')
            quoteId = int((await cursor.fetchone() or [0])[0])
        else:
            query = '''
INSERT INTO quotes (broadcaster, quote) VALUES (?, ?)
'''
            await cursor.execute(query, (channel, quote))
            await cursor.execute('SELECT last_insert_rowid()')
            quoteId = int((await cursor.fetchone() or [0])[0])

        query = '''
INSERT INTO quotes_history (quoteId, createdTime, broadcaster, quote, editor)
    VALUES (?, CURRENT_TIMESTAMP, ?, ?, ?)
'''
        await cursor.execute(query, (quoteId, channel, quote, nick))
        await db.commit()
        return quoteId
示例#5
0
async def insert_banned_channel(channel: str,
                                reason: str,
                                nick: str,
                                send: Send) -> bool:
    if channel == bot.config.botnick:
        send('Cannot ban the bot itself')
        return True
    result: bool
    db: DatabaseMain
    async with DatabaseMain.acquire() as db:
        bannedWithReason: Optional[str]
        bannedWithReason = await db.isChannelBannedReason(channel)
        if bannedWithReason is not None:
            send(f'{channel} is already banned for: {bannedWithReason}')
            return True
        result = await db.addBannedChannel(channel, reason, nick)
        if result:
            db.discardAutoJoin(channel)
            utils.partChannel(channel)

    msg: str
    if result:
        msg = f'Chat {channel} is now banned'
    else:
        msg = f'Chat {channel} could not be banned. Error has occured.'
    send(msg)
    return True
示例#6
0
async def commandFfzSlots(args: ChatCommandArgs) -> bool:
    if 'slotsLock' not in args.chat.sessionData:
        args.chat.sessionData['slotsLock'] = asyncio.Lock()
    lock: asyncio.Lock = args.chat.sessionData['slotsLock']

    if lock.locked():
        utils.whisper(args.nick, f'Channel cooldown (3.0 seconds)')
        return True

    db: DatabaseMain
    with await lock:
        async with DatabaseMain.acquire() as db:
            isBot: bool = await library.isSlotBots(
                db, args.chat.channel, args.nick,
                args.timestamp - library.unbotCooldown)
            lastAttempt: datetime = await library.getLastFfzSlotsUser(
                db, args.chat.channel, args.nick)
            if await library.in_cooldown(db, args.chat.channel, args.nick,
                                         args.timestamp, lastAttempt, isBot):
                return False

            lastAttempts: List[datetime]
            lastAttempts = await library.getLastFfzSlotsAttempts(
                db, args.chat.channel, args.nick,
                args.timestamp - library.logBotAttempts)
            markedBot: Optional[bool] = await library.process_bot(
                db, args.chat.channel, args.nick, args.timestamp, lastAttempt,
                isBot, lastAttempts)

            emotes: Optional[Dict[int, str]]
            emotes = await library.generate_ffz_pool(args.chat, args.data)
            length: int = 3
            emoteIds: List[int] = list(emotes.keys())
            selected: List[int] = [random.choice(emoteIds) for _
                                   in range(length)]

            numMatching: int = 0
            emoteId: int
            for emoteId in selected:
                if emoteId == selected[0]:
                    numMatching += 1
            allMatching: bool = numMatching == 3

            selectedEmotes = ' | '.join(emotes[i] for i in selected)
            args.chat.send(f'{args.nick} -> {selectedEmotes}')
            if allMatching:
                args.chat.send(f'{args.nick} has won !ffzslots')
            if markedBot is True:
                args.chat.send(f'''\
{args.nick} is now considered as a bot. His cooldown is increased to 20 \
minutes.''')
            if markedBot is False:
                args.chat.send(f'''\
{args.nick} is now considered not as a bot. His \cooldown is back to 2 \
minutes.''')

            await library.recordFfzSlots(db, args.chat.channel,
                                         args.nick, emotes, selected)
            return True
示例#7
0
async def getTagsOfQuote(quoteId: int) -> Set[str]:
    db: DatabaseMain
    cursor: aioodbc.cursor.Cursor
    async with DatabaseMain.acquire() as db, await db.cursor() as cursor:
        query: str = '''
SELECT tag FROM quotes_tags WHERE quoteId=?
'''
        return {t async for t, in await cursor.execute(query, (quoteId, ))}
示例#8
0
async def getQuoteById(channel: str, id: int) -> Optional[str]:
    db: DatabaseMain
    cursor: aioodbc.cursor.Cursor
    async with DatabaseMain.acquire() as db, await db.cursor() as cursor:
        query: str = '''
SELECT quote FROM quotes WHERE broadcaster=? AND quoteId=?
'''
        await cursor.execute(query, (channel, id))
        return (await cursor.fetchone() or [None])[0]
示例#9
0
async def getRandomQuote(channel: str) -> Optional[str]:
    db: DatabaseMain
    cursor: aioodbc.cursor.Cursor
    async with DatabaseMain.acquire() as db, await db.cursor() as cursor:
        query: str = '''
SELECT quote FROM quotes WHERE broadcaster=? ORDER BY random() LIMIT 1
'''
        await cursor.execute(query, (channel, ))
        return (await cursor.fetchone() or [None])[0]
示例#10
0
async def getAnyQuoteById(id: int) -> Tuple[Optional[str], Optional[str]]:
    db: DatabaseMain
    cursor: aioodbc.cursor.Cursor
    async with DatabaseMain.acquire() as db, await db.cursor() as cursor:
        query: str = '''
SELECT quote, broadcaster FROM quotes WHERE quoteId=?
'''
        await cursor.execute(query, (id, ))
        row: Optional[Tuple[str, str]] = await cursor.fetchone()
        return (row[0], row[1]) if row else (None, None)
示例#11
0
async def getAnyRandomQuote() -> Tuple[Optional[str], Optional[str]]:
    db: DatabaseMain
    cursor: aioodbc.cursor.Cursor
    async with DatabaseMain.acquire() as db, await db.cursor() as cursor:
        query: str = '''
SELECT quote, broadcaster FROM quotes ORDER BY random() LIMIT 1
'''
        await cursor.execute(query)
        row: Optional[Tuple[str, str]] = await cursor.fetchone()
        return (row[0], row[1]) if row else (None, None)
示例#12
0
async def deleteTagsToQuote(quoteId: int, tags: List[str]) -> bool:
    db: DatabaseMain
    cursor: aioodbc.cursor.Cursor
    async with DatabaseMain.acquire() as db, await db.cursor() as cursor:
        query: str = '''
DELETE FROM quotes_tags WHERE quoteId=? AND tag=?
'''
        await cursor.executemany(query, map(lambda t: (quoteId, t), tags))
        await db.commit()
        return bool(tags)
示例#13
0
async def deleteQuote(channel: str, quoteId: int) -> bool:
    db: DatabaseMain
    cursor: aioodbc.cursor.Cursor
    async with DatabaseMain.acquire() as db, await db.cursor() as cursor:
        query: str = '''
DELETE FROM quotes WHERE quoteId=? AND broadcaster=?
'''
        await cursor.execute(query, (quoteId, channel))
        await db.commit()
        return cursor.rowcount != 0
示例#14
0
async def addTagsToQuote(quoteId: int, tags: List[str]) -> bool:
    db: DatabaseMain
    cursor: aioodbc.cursor.Cursor
    async with DatabaseMain.acquire() as db, await db.cursor() as cursor:
        query: str = '''
INSERT INTO quotes_tags (quoteId, tag) VALUES (?, ?)
'''
        await cursor.executemany(query, map(lambda t: (quoteId, t), tags))
        await db.commit()
        return bool(tags)
示例#15
0
async def list_banned_channels(send: Send) -> bool:
    bannedChannels: Iterable[str]
    db: DatabaseMain
    async with DatabaseMain.acquire() as db:
        bannedChannels = [channel async for channel
                          in db.listBannedChannels()]
    if bannedChannels:
        send(message.messagesFromItems(bannedChannels, 'Banned Channels: '))
    else:
        send('There are no banned channels')
    return True
示例#16
0
async def auto_join_priority(channel: str,
                             priority: int,
                             send: Send) -> bool:
    result: bool
    db: DatabaseMain
    async with DatabaseMain.acquire() as db:
        result = await db.setAutoJoinPriority(channel, priority)
    if result:
        send(f'Auto join for {channel} is set to priority {priority}')
    else:
        send(f'Auto join for {channel} was never enabled')
    return True
示例#17
0
async def get_auto_purges_db(broadcaster: str) -> Dict[str, bool]:
    db: DatabaseMain
    cursor: aioodbc.cursor.Cursor
    purges: Dict[str, bool] = {}
    async with DatabaseMain.acquire() as db, await db.cursor() as cursor:
        query: str = '''
SELECT twitchUser, stopcommands FROM auto_purge WHERE broadcaster=?
'''
        user: str
        stop: bool
        async for user, stop in await cursor.execute(query, (broadcaster,)):
            purges[user] = stop
        return purges
示例#18
0
async def copyQuote(from_channel: str, to_channel: str, nick: str,
                    quoteId: int) -> Optional[int]:
    db: DatabaseMain
    cursor: aioodbc.cursor.Cursor
    async with DatabaseMain.acquire() as db, await db.cursor() as cursor:
        query: str = '''
SELECT quote FROM quotes WHERE quoteId=? AND broadcaster=?
'''
        await cursor.execute(query, (quoteId, from_channel))
        quote: Optional[str] = ((await cursor.fetchone()) or [None])[0]
        if quote is None:
            return None
        newQuoteId: int
        if db.isPostgres:
            query = '''
INSERT INTO quotes (broadcaster, quote, document) VALUES (?, ?, to_tsvector(?))
'''
            await cursor.execute(query, (to_channel, quote, quote))
            await cursor.execute('SELECT lastval()')
            newQuoteId = int((await cursor.fetchone() or [0])[0])
        else:
            query = '''
INSERT INTO quotes (broadcaster, quote) VALUES (?, ?)
'''
            await cursor.execute(query, (to_channel, quote))
            await cursor.execute('SELECT last_insert_rowid()')
            newQuoteId = int((await cursor.fetchone() or [0])[0])

        query = '''
SELECT tag FROM quotes_tags WHERE quoteId=?
'''
        quoteTagsParams: List[Tuple[int, str]]
        quoteTagsParams = [
            (newQuoteId, t)
            async for t, in await cursor.execute(query, (quoteId, ))
        ]
        if quoteTagsParams:
            query = '''
INSERT INTO quotes_tags (quoteId, tag) VALUES (?, ?)
'''
            await cursor.executemany(query, quoteTagsParams)

        query = '''
INSERT INTO quotes_history (quoteId, createdTime, broadcaster, quote, editor)
VALUES (?, CURRENT_TIMESTAMP, ?, ?, ?)
'''
        await cursor.execute(query, (newQuoteId, to_channel, quote, nick))
        await db.commit()
        return newQuoteId
示例#19
0
async def come(channel: str, send: Send) -> bool:
    bannedWithReason: Optional[str]
    priority: Union[float, int]
    db: DatabaseMain
    async with DatabaseMain.acquire() as db:
        bannedWithReason = await db.isChannelBannedReason(channel)
        if bannedWithReason is not None:
            send(f'Chat {channel} is banned from joining')
            return True
        priority = await db.getAutoJoinsPriority(channel)
    joinResult: bool = utils.joinChannel(channel, priority)
    if joinResult:
        send(f'Joining {channel}')
    else:
        send(f'I am already in {channel}')
    return True
示例#20
0
async def auto_join(channel: str, send: Send, message: Message) -> bool:
    db: DatabaseMain
    async with DatabaseMain.acquire() as db:
        bannedWithReason: Optional[str]
        bannedWithReason = await db.isChannelBannedReason(channel)
        if bannedWithReason is not None:
            send(f'Chat {channel} is banned from joining')
            return True

        if len(message) >= 2:
            removeMsgs: List[str] = [
                '0', 'false', 'no', 'remove', 'rem', 'delete', 'del', 'leave',
                'part'
            ]
            if message.lower[1] in removeMsgs:
                return await auto_join_delete(db, channel, send)
        return await auto_join_add(db, channel, send)
示例#21
0
async def join(channel: str,
               send: Send) -> bool:
    priority: Union[int, float]
    db: DatabaseMain
    async with DatabaseMain.acquire() as db:
        bannedWithReason: Optional[str]
        bannedWithReason = await db.isChannelBannedReason(channel)
        if bannedWithReason is not None:
            send(f'Chat {channel} is banned from joining')
            return True
        priority = await db.getAutoJoinsPriority(channel)

    if utils.joinChannel(channel, priority):
        send(f'Joining {channel}')
    else:
        send(f'Already joined {channel}')
    return True
示例#22
0
async def getRandomQuoteBySearch(channel: str,
                                 words: Sequence[str]) -> Optional[str]:
    db: DatabaseMain
    cursor: aioodbc.cursor.Cursor
    async with DatabaseMain.acquire() as db, await db.cursor() as cursor:
        query: str
        params: Tuple[str, ...]
        if db.isPostgres:
            query = '''
SELECT quoteId FROM quotes WHERE broadcaster=? AND document @@ to_tsquery(?)
    UNION SELECT quoteId FROM quotes q WHERE broadcaster=? AND
''' + ' AND '.join([
                '? IN (SELECT LOWER(tag) FROM quotes_tags AS t '
                'WHERE t.quoteId=q.quoteId)'
            ] * len(words))
            query = '''
SELECT quote FROM quotes
    WHERE quoteId=(
        SELECT quoteId FROM (%s) AS q ORDER BY RANDOM() LIMIT 1)''' % query
            params = ((
                channel,
                ' | '.join(words),
                channel,
            ) + tuple(w.lower() for w in words))
            await cursor.execute(query, params)
            return (await cursor.fetchone() or [None])[0]

        query = '''
SELECT quoteId FROM quotes WHERE broadcaster=? AND
''' + ' AND '.join(['quote LIKE ?'] * len(words)) + '''
    UNION SELECT quoteId FROM quotes q WHERE broadcaster=? AND
''' + ' AND '.join([
            '? IN (SELECT LOWER(tag) FROM quotes_tags AS t '
            'WHERE t.quoteId=q.quoteId)'
        ] * len(words))
        query = '''
SELECT quote FROM quotes
    WHERE quoteId=(
        SELECT quoteId FROM (%s) AS q ORDER BY RANDOM() LIMIT 1)''' % query
        params = ((channel, ) + tuple(f'%{w}%' for w in words) + (channel, ) +
                  tuple(w.lower() for w in words))
        await cursor.execute(query, params)
        return (await cursor.fetchone() or [None])[0]
示例#23
0
async def getAnyRandomQuoteBySearch(
        words: Sequence[str]) -> Tuple[Optional[str], Optional[str]]:
    db: DatabaseMain
    cursor: aioodbc.cursor.Cursor
    async with DatabaseMain.acquire() as db, await db.cursor() as cursor:
        query: str
        params: Tuple[str, ...]
        row: Optional[Tuple[str, str]]
        if db.isPostgres:
            query = '''
SELECT quoteId FROM quotes WHERE document @@ to_tsquery(?)
    UNION SELECT quoteId FROM quotes q WHERE
''' + ' AND '.join([
                '? IN (SELECT LOWER(tag) FROM quotes_tags AS t '
                'WHERE t.quoteId=q.quoteId)'
            ] * len(words))
            query = '''
SELECT quote, broadcaster FROM quotes
    WHERE quoteId=(
        SELECT quoteId FROM (%s) AS q ORDER BY RANDOM() LIMIT 1)''' % query
            params = ((' | '.join(words), ) + tuple(w.lower() for w in words))
            await cursor.execute(query, params)
            row = await cursor.fetchone()
            return (row[0], row[1]) if row else (None, None)

        query = '''
SELECT quoteId FROM quotes WHERE 1=1 AND
''' + ' AND '.join(['quote LIKE ?'] * len(words)) + '''
    UNION SELECT quoteId FROM quotes q WHERE 1=1 AND
''' + ' AND '.join([
            '? IN (SELECT LOWER(tag) FROM quotes_tags AS t '
            'WHERE t.quoteId=q.quoteId)'
        ] * len(words))
        query = f'''
SELECT quote, broadcaster FROM quotes WHERE quoteId=(
    SELECT quoteId FROM ({query}) AS q ORDER BY RANDOM() LIMIT 1)
'''
        params = (tuple(f'%{w}%' for w in words) + tuple(w.lower()
                                                         for w in words))
        await cursor.execute(query, params)
        row = await cursor.fetchone()
        return (row[0], row[1]) if row else (None, None)
示例#24
0
async def delete_banned_channel(channel: str,
                                reason: str,
                                nick: str,
                                send: Send) -> bool:
    result: bool
    db: DatabaseMain
    async with DatabaseMain.acquire() as db:
        bannedWithReason: Optional[str]
        bannedWithReason = await db.isChannelBannedReason(channel)
        if bannedWithReason is None:
            send(f'{channel} is not banned')
            return True
        result = await db.removeBannedChannel(channel, reason, nick)

    msg: str
    if result:
        msg = f'Chat {channel} is now unbanned'
    else:
        msg = f'Chat {channel} could not be unbanned. Error has occured.'
    send(msg)
    return True
示例#25
0
async def getQuoteIdsByWords(channel: str, words: Sequence[str]) -> List[int]:
    db: DatabaseMain
    cursor: aioodbc.cursor.Cursor
    async with DatabaseMain.acquire() as db, await db.cursor() as cursor:
        query: str
        params: Tuple[str, ...]
        if db.isPostgres:
            query = '''
SELECT quoteId FROM quotes WHERE broadcaster=? AND document @@ to_tsquery(?)
    UNION SELECT quoteId FROM quotes q WHERE broadcaster=? AND
''' + ' AND '.join([
                '? IN (SELECT LOWER(tag) FROM quotes_tags AS t '
                'WHERE t.quoteId=q.quoteId)'
            ] * len(words))
            query = '''
SELECT quoteId FROM (%s) AS q ORDER BY quoteId ASC
''' % query
            params = ((
                channel,
                ' | '.join(words),
                channel,
            ) + tuple(w.lower() for w in words))
            await cursor.execute(query, params)
            return [i async for i, in await cursor.execute(query, params)]

        query = '''
SELECT quoteId FROM quotes WHERE broadcaster=? AND
''' + ' AND '.join(['quote LIKE ?'] * len(words)) + '''
    UNION SELECT quoteId FROM quotes q WHERE broadcaster=? AND
''' + ' AND '.join([
            '? IN (SELECT LOWER(tag) FROM quotes_tags AS t '
            'WHERE t.quoteId=q.quoteId)'
        ] * len(words))
        query = 'SELECT quoteId FROM (' + query + ') AS q ORDER BY quoteId ASC'
        params = ((channel, ) + tuple(f'%{w}%' for w in words) + (channel, ) +
                  tuple(w.lower() for w in words))
        return [i async for i, in await cursor.execute(query, params)]
示例#26
0
async def commandSlots(args: ChatCommandArgs) -> bool:
    if 'slotsLock' not in args.chat.sessionData:
        args.chat.sessionData['slotsLock'] = asyncio.Lock()
    lock: asyncio.Lock = args.chat.sessionData['slotsLock']

    if lock.locked():
        utils.whisper(args.nick, f'Channel cooldown (3.0 seconds)')
        return True

    db: DatabaseMain
    with await lock:
        async with DatabaseMain.acquire() as db:
            isBot: bool = await library.isSlotBots(
                db, args.chat.channel, args.nick,
                args.timestamp - library.unbotCooldown)
            lastAttempt: datetime = await library.getLastTwitchSlotsUser(
                db, args.chat.channel, args.nick)
            if await library.in_cooldown(db, args.chat.channel, args.nick,
                                         args.timestamp, lastAttempt, isBot):
                return False

            lastAttempts: List[datetime]
            lastAttempts = await library.getLastTwitchSlotsAttempts(
                db, args.chat.channel, args.nick,
                args.timestamp - library.logBotAttempts)
            markedBot: Optional[bool] = await library.process_bot(
                db, args.chat.channel, args.nick, args.timestamp, lastAttempt,
                isBot, lastAttempts)

            emotes: Optional[Dict[int, str]]
            emotes = await library.generate_twitch_pool(args.data)
            length: int = 3
            emoteIds: List[int] = list(emotes.keys())
            selected: List[int] = [random.choice(emoteIds) for _
                                   in range(length)]

            matchEmoteId: int = selected[0]
            numMatching: int = 0
            emoteId: int
            for emoteId in selected:
                if emoteId == matchEmoteId:
                    numMatching += 1
            allMatching: bool = numMatching == 3

            selectedEmotes: str = ' | '.join(emotes[i] for i in selected)
            msg: str = f'{args.nick} -> {selectedEmotes}'
            args.chat.send(msg)
            if allMatching:
                args.chat.send(f'{args.nick} has won !slots')
                if matchEmoteId == 25 and args.permissions.chatModerator:
                    args.chat.send(f'.timeout {args.nick} 1')
                    args.chat.send('Thanks for winning the Kappa!')
                    dbTimeout: DatabaseTimeout
                    async with DatabaseTimeout.acquire() as dbTimeout:
                        await dbTimeout.recordTimeout(
                            args.chat.channel, args.nick, None, 'slots', None,
                            1, str(args.message), msg)
            if markedBot is True:
                args.chat.send(f'''\
{args.nick} is now considered as a bot. His cooldown is increased to 20 \
minutes.''')
            if markedBot is False:
                args.chat.send(f'''\
{args.nick} is now considered not as a bot. His cooldown is back to 2 \
minutes.''')

            await library.recordTwitchSlots(db, args.data, args.chat.channel,
                                            args.nick, emotes, selected)
            return True
async def commandMultiTwitch(args: ChatCommandArgs) -> bool:
    '''
    Example Commands:
    !multitwitch << gives a link of linked multitwitch, available to everyone
    !multitwitch kadgar << available to everyone
    !multitwitch preference kadgar << available to everyone
    !multitwitch add
    !multitwitch add kappa
    !multitwitch drop
    !multitwitch reset
    !multitwitch remove kappa
    !multitwitch remove
    !multitwitch event kappa << owner command, does not perform auto removal

    The command should automatically remove inactive streams of more than
    5 minutes or 15 minutes after the initial add if stream hasnt started
    '''

    # TODO: mypy fix after https://github.com/python/mypy/issues/1855

    currentTime: datetime = utils.now()
    db: DatabaseMain
    cursor: aioodbc.cursor
    query: str
    params: Tuple[Any, ...]
    paramsM: List[Tuple[Any, ...]]
    row: Tuple[Any, ...]
    group: str
    groupO: Optional[str]
    groups: List[Tuple[Any, ...]]
    event: Optional[bool]
    async with DatabaseMain.acquire() as db, await db.cursor() as cursor:
        if (len(args.message) < 2 or not args.permissions.moderator
                or args.message.lower[1] in library.multiUrls):
            cooldown: timedelta = timedelta(seconds=30)
            if args.permissions.moderator:
                cooldown = timedelta(seconds=10)
            if (not args.permissions.broadcaster
                    and 'multitwitch' in args.chat.sessionData):
                since: timedelta
                since = currentTime - args.chat.sessionData['multitwitch']
                if since < cooldown:
                    return False

            query = 'SELECT twitchgroup FROM multitwitch WHERE broadcaster=?'
            await cursor.execute(query, (args.chat.channel, ))
            groupO = (await cursor.fetchone() or [None])[0]
            twitches: List[str] = []
            if groupO:
                query = '''
SELECT broadcaster, addedTime, lastLive FROM multitwitch
    WHERE twitchgroup=? ORDER BY isEvent DESC, addedTime ASC
'''
                async for row in await cursor.execute(query, (groupO, )):
                    broadcaster: str
                    added: datetime
                    live: datetime
                    broadcaster, added, live = row
                    if live is None:
                        if currentTime - added > library.addedCooldown:
                            continue
                    else:
                        if currentTime - live > library.liveCooldown:
                            continue
                    twitches.append(broadcaster)
            if not twitches:
                args.chat.send(f'https://www.twitch.tv/{args.chat.channel}')
                if args.permissions.moderator:
                    args.chat.send('''\
Just do !multitwitch add <twitch user> to create/start a multitwitch link''')
                args.chat.sessionData['multitwitch'] = currentTime
                return True

            default: str = await args.data.getChatProperty(
                args.chat.channel, 'multitwitch', library.default, str)
            preference: str = await args.data.getChatProperty(
                args.nick, 'multitwitch', default, str)
            if (len(args.message) >= 2
                    and args.message.lower[1] in library.multiUrls):
                preference = args.message.lower[1]
            if len(twitches) == 1:
                args.chat.send('https://www.twitch.tv/' + twitches[0])
            elif preference in library.multiUrls:
                args.chat.send(library.multiUrls[preference](twitches))
            else:
                args.chat.send(library.multiUrls[library.default](twitches))
            args.chat.sessionData['multitwitch'] = currentTime
            return True

        if args.message.lower[1] == 'preference':
            if len(args.message) < 2:
                await args.data.setChatProperty(args.nick, 'multitwitch', None)
            elif args.message.lower[2] in library.multiUrls:
                await args.data.setChatProperty(args.nick, 'multitwitch',
                                                args.message.lower[2])
            else:
                args.chat.send('Unrecognized multitwitch site')

        if not args.permissions.moderator:
            return False

        if args.message.lower[1] == 'add':
            other: str
            if len(args.message) < 3:
                other = args.nick
            else:
                other = args.message.lower[2]
                if not await args.data.twitch_is_valid_user(other):
                    args.chat.send(f'{other} is not a valid Twitch user')
                    return True
            if other == args.chat.channel:
                args.chat.send('You cannot add yourself for multitwitch link')
                return True
            query = '''
SELECT broadcaster, twitchgroup, isEvent FROM multitwitch
    WHERE broadcaster IN (?, ?)'''
            params = args.chat.channel, other
            groups = [row async for row in await cursor.execute(query, params)]
            if len(groups) == 0:
                alphabet = ('0123456789'
                            'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
                            'abcdefghijklmnopqrstuvwxyz')
                group = ''.join(random.choice(alphabet) for i in range(7))
                query = '''
INSERT INTO multitwitch (broadcaster, twitchgroup, addedTime)
    VALUES (?, ?, ?)'''
                paramsM = [(args.chat.channel, group, currentTime),
                           (other, group, currentTime)]
                await cursor.executemany(query, paramsM)
                args.chat.send(f'''\
Created a multitwitch for {args.chat.channel} and {other}''')
            elif len(groups) == 1:
                group = groups[0][1]
                toAdd: str
                done: str
                toAdd = args.chat.channel if groups[0][0] == other else other
                done = args.chat.channel if groups[0][0] != other else other
                query = '''
INSERT INTO multitwitch (broadcaster, twitchgroup, addedTime)
    VALUES (?, ?, ?)'''
                await cursor.execute(query, (toAdd, group, currentTime))
                args.chat.send(f'''\
Added {toAdd} to the multitwitch of {done} and others''')
            else:
                group = groups[0][1]
                g: Tuple[str, str, bool]
                for g in cast(List[Tuple[str, str, bool]], groups):
                    if g[2]:
                        group = g[1]
                        break
                query = '''
UPDATE multitwitch SET twitchgroup=? WHERE twitchgroup=?'''
                paramsM = [(g[1], group) for g in groups if g[1] != group]
                if not paramsM:
                    args.chat.send(f'''\
{args.chat.channel} and {other} are already in the same multitwitch''')
                    return True
                await cursor.executemany(query, paramsM)
                args.chat.send(f'''\
Merged the multitwitches of {args.chat.channel} and {other}''')
            await db.commit()

        if args.message.lower[1] in ['drop', 'delete', 'del' 'remove', 'rem']:
            who: str
            if len(args.message) < 3:
                who = args.chat.channel
            else:
                who = args.message.lower[2]
            query = '''
SELECT twitchgroup, isEvent FROM multitwitch
    WHERE twitchgroup=(SELECT twitchgroup FROM multitwitch WHERE broadcaster=?)
        AND broadcaster=?'''
            await cursor.execute(query, (args.chat.channel, who))
            groupO, event = await cursor.fetchone() or (None, None)
            if groupO is None:
                args.chat.send(f'''\
Multitwitch of {who} does not exist or is not part of the same multitwitch of \
{args.chat.channel}''')
                return True

            query = 'SELECT COUNT(*) FROM multitwitch WHERE twitchgroup=?'
            await cursor.execute(query, (groupO, ))
            if (await cursor.fetchone())[0] <= 2:
                query = 'DELETE FROM multitwitch WHERE twitchgroup=?'
                await cursor.execute(query, (groupO, ))
                if args.chat.channel == who:
                    args.chat.send(f'''\
Reset the multitwitch of {args.chat.channel} and others''')
                else:
                    args.chat.send(f'''\
Reset the multitwitch of {args.chat.channel} and {who}''')
                await db.commit()
                return True

            if not event:
                query = 'DELETE FROM multitwitch WHERE broadcaster=?'
                await cursor.execute(query, (who, ))
                if who == args.chat.channel:
                    args.chat.send(f'''\
Removed {args.chat.channel} from a multitwitch''')
                else:
                    args.chat.send(f'''\
Removed {who} from a multitwitch with {args.chat.channel}''')
                await db.commit()
                return True

            query = '''
SELECT COUNT(*) FROM multitwitch WHERE twitchgroup=? AND isEvent=FALSE
UNION ALL
    SELECT COUNT(*) FROM multitwitch WHERE twitchgroup=? AND isEvent=TRUE'''
            await cursor.execute(query, (groupO, ) * 2)
            notEvent, = await cursor.fetchone()
            inEvent, = await cursor.fetchone()
            if notEvent > 0:
                args.chat.send(f'''\
Cannot remove {who} until all non-event users are removed''')
                return True

            query = 'DELETE FROM multitwitch WHERE broadcaster=?'
            await cursor.execute(query, (who, ))
            if who == args.chat.channel:
                args.chat.send(f'''\
Removed {args.chat.channel} from a multitwitch''')
            else:
                args.chat.send(f'''\
Removed {who} from a multitwitch with {args.chat.channel}''')
            await db.commit()

        if args.message.lower[1] == 'reset':
            query = 'SELECT twitchgroup FROM multitwitch WHERE broadcaster=?'
            await cursor.execute(query, (args.chat.channel, ))
            groupO, = await cursor.fetchone() or (None, )
            if groupO is None:
                args.chat.send(f'''\
Multitwitch of {args.chat.channel} does not exist''')
                return True

            query = '''
SELECT COUNT(*) FROM multitwitch WHERE twitchgroup=? AND isEvent=FALSE
UNION ALL
    SELECT COUNT(*) FROM multitwitch WHERE twitchgroup=? AND isEvent=TRUE'''
            await cursor.execute(query, (groupO, ) * 2)
            notEvent, = await cursor.fetchone()
            inEvent, = await cursor.fetchone()
            if notEvent > 0 and inEvent > 1:
                query = '''
DELETE FROM multitwitch WHERE twitchgroup=? AND isEvent=FALSE'''
                await cursor.execute(query, (groupO, ))
                args.chat.send('Reset the multitwitch of non-event users')
                await db.commit()
                return True

            query = 'DELETE FROM multitwitch WHERE twitchgroup=?'
            await cursor.execute(query, (groupO, ))
            args.chat.send(f'''\
Reset the multitwitch of {args.chat.channel} and others''')
            await db.commit()

        if args.message.lower[1] == 'event' and args.permissions.owner:
            if len(args.message) < 3:
                who = args.chat.channel
            else:
                who = args.message.lower[2]
            query = '''
SELECT twitchgroup, isEvent FROM multitwitch
    WHERE twitchgroup=(SELECT twitchgroup FROM multitwitch WHERE broadcaster=?)
        AND broadcaster=?'''
            await cursor.execute(query, (args.chat.channel, who))
            groupO, event = await cursor.fetchone() or (None, None)
            if groupO is not None:
                query = 'UPDATE multitwitch SET isEvent=? WHERE broadcaster=?'
                await cursor.execute(query, (
                    not event,
                    who,
                ))
                if not event:
                    args.chat.send(f'{who} is marked as an event multitwitch')
                else:
                    args.chat.send(
                        f'{who} is unmarked from an event multitwitch')
                await db.commit()
            else:
                args.chat.send(f'''\
Multitwitch of {who} does not exist or is not part of the same multitwitch of \
{args.chat.channel}''')

        return True