]> PatchyGit - angel.git/commitdiff
Use regex search
authorCzar <czar@kalli.st>
Fri, 16 Feb 2024 00:56:08 +0000 (01:56 +0100)
committerCzar <czar@kalli.st>
Fri, 16 Feb 2024 00:56:08 +0000 (01:56 +0100)
Closes #11

main.py

diff --git a/main.py b/main.py
index 0d00eb3..48def4b 100644 (file)
--- a/main.py
+++ b/main.py
-import requests\r
-import bs4\r
-import yt_dlp as youtube_dl\r
-import random\r
-import configparser\r
-import re\r
-import io\r
-import os\r
-import asyncio\r
-from collections import defaultdict\r
-from PythonSed import Sed\r
-from slixmpp import ClientXMPP\r
-from urllib.parse import urlparse, parse_qs, urlunparse\r
-from pantomime import normalize_mimetype\r
-import cgi\r
-\r
-sed_parse = re.compile("(?<!\\\\)[/#]")\r
-sed_cmd = re.compile("^s[/#].*[/#].*[/#]")\r
-\r
-parser = "html.parser"\r
-user_agent = "Mozilla/5.0 (X11; Linux x86_64; rv:10.0)"\r
-" Gecko/20100101 Firefox/10.0"\r
-accept_lang = "en-US"\r
-data_limit = 100000000  # 100MB\r
-\r
-headers = {\r
-    "user-agent": user_agent,\r
-    "Accept-Language": accept_lang,\r
-    "Cache-Control": "no-cache",\r
-}\r
-\r
-youtube_links = ["www.youtube.com", "m.youtube.com"]\r
-\r
-youtube_link = "youtu.be"\r
-\r
-ydl = youtube_dl.YoutubeDL()\r
-\r
-invidious_instances = ["invidious.kalli.st"]\r
-\r
-block_list = ("localhost", "127.0.0.1", "0.0.0.0")\r
-\r
-req_list = ("http://", "https://")\r
-\r
-html_files = ("text/html", "application/xhtml+xml")\r
-\r
-\r
-class Lifo(list):\r
-    """Limited size LIFO array to store messages and urls."""\r
-\r
-    def __init__(self, size):\r
-        """Initialize the LIFO array."""\r
-        super().__init__()\r
-        self.size = size\r
-\r
-    def add(self, item):\r
-        """Add an item to the LIFO array."""\r
-        self.insert(0, item)\r
-        if len(self) > self.size:\r
-            self.pop()\r
-\r
-\r
-def get_youtube_title(url):\r
-    """Get the title of a youtube video."""\r
-    try:\r
-        info = ydl.extract_info(url, download=False)\r
-        return info["title"]\r
-    except Exception as e:\r
-        print(e)\r
-        return None\r
-\r
-\r
-def get_invidious_link(yurl):\r
-    """Get an invidious link from a youtube link."""\r
-    video = yurl.split("/")[-1]\r
-    instance = random.choice(invidious_instances)\r
-    return f"https://{instance}/watch?v={video}"\r
-\r
-\r
-def get_yurl(path):\r
-    """Get a youtube link from a path."""\r
-    yurl = f"https://youtu.be/{path}"\r
-    return yurl\r
-\r
-\r
-class AngelBot(ClientXMPP):\r
-    """AngelBot class."""\r
-\r
-    messages = defaultdict(\r
-        lambda: {\r
-            "messages": Lifo(100),\r
-            "links": Lifo(10),\r
-            "previews": Lifo(10),\r
-        }\r
-    )\r
-\r
-    def get_urls(self, msg):\r
-        """Get urls from a message."""\r
-        str_list = msg["body"].strip().split()\r
-        urls = [u for u in str_list if any(r in u for r in req_list)]\r
-        return urls\r
-\r
-    def send_youtube_info(self, uri, sender, mtype):\r
-        """Send youtube info to the sender."""\r
-        yurl = None\r
-        if uri.netloc == youtube_link:\r
-            yurl = get_yurl(uri.path)\r
-        elif "v" in (query := parse_qs(uri.query)):\r
-            if v := query["v"]:\r
-                yurl = get_yurl(v[0])\r
-            else:\r
-                return\r
-\r
-        invidious = get_invidious_link(yurl)\r
-\r
-        if output := get_youtube_title(invidious):\r
-            if output in self.messages[sender]["previews"]:\r
-                return\r
-            self.messages[sender]["previews"].add(output)\r
-\r
-            self.send_message(mto=sender, mbody=f"*{output}*", mtype=mtype)\r
-            self.send_message(mto=sender, mbody=invidious, mtype=mtype)\r
-\r
-    async def parse_uri(self, uri, sender, mtype):\r
-        """Parse a uri and send the result to the sender."""\r
-        netloc = uri.netloc\r
-        if netloc in (youtube_links + [youtube_link]):\r
-            self.send_youtube_info(uri, sender, mtype)\r
-        elif netloc.split(":")[0] in block_list:\r
-            return\r
-        else:\r
-            await self.process_link(uri, sender, mtype)\r
-\r
-    async def process_link(self, uri, sender, mtype):\r
-        """Process a link and send the result to the sender."""\r
-        url = urlunparse(uri)\r
-        r = requests.get(url, stream=True, headers=headers, timeout=6)\r
-        if not r.ok:\r
-            return\r
-\r
-        ftype = normalize_mimetype(r.headers.get("content-type"))\r
-\r
-        if not ftype:\r
-            return\r
-\r
-        if ftype in html_files:\r
-            data = ""\r
-            for i in r.iter_content(chunk_size=1024, decode_unicode=False):\r
-                data += i.decode("utf-8", errors="ignore")\r
-                if len(data) > data_limit or "</head>" in data.lower():\r
-                    break\r
-            soup = bs4.BeautifulSoup(data, parser)\r
-            if title := soup.find("title"):\r
-                output = title.text.strip()\r
-                if output:\r
-                    output = f"*{output}*" if ("\n" not in output) else output\r
-                    if output in self.messages[sender]["previews"]:\r
-                        return\r
-\r
-                    self.messages[sender]["previews"].add(output)\r
-                    if r.history:\r
-                        self.send_message(mto=sender, mbody=r.url, mtype=mtype)\r
-                    self.send_message(mto=sender, mbody=output, mtype=mtype)\r
-\r
-        else:\r
-            try:\r
-                lenght = 0\r
-                outfile = io.BytesIO()\r
-                for chunk in r.iter_content(\r
-                    chunk_size=512,\r
-                    decode_unicode=False,\r
-                ):\r
-                    lenght += 512\r
-                    if lenght >= data_limit:\r
-                        return\r
-                    outfile.write(chunk)\r
-\r
-                content_disposition = r.headers.get("content-disposition")\r
-                filename = None\r
-                if content_disposition:\r
-                    _, params = cgi.parse_header(content_disposition)\r
-                    filename = params.get("filename")\r
-                else:\r
-                    filename = os.path.basename(uri.path)\r
-\r
-                ext = os.path.splitext(filename)[1] if filename else ".txt"\r
-                fname = filename if filename else f"file{ext}"\r
-                await self.embed_file(url, sender, mtype, ftype, fname, outfile)\r
-            except Exception as e:\r
-                print(e)\r
-\r
-    async def embed_file(self, url, sender, mtype, ftype, fname, outfile):\r
-        """Embed a file and send the result to the sender."""\r
-        furl = await self.plugin["xep_0363"].upload_file(\r
-            fname, content_type=ftype, input_file=outfile\r
-        )\r
-        self.messages[sender]["links"].add(furl)\r
-        message = self.make_message(sender)\r
-        message["body"] = furl\r
-        message["type"] = mtype\r
-        message["oob"]["url"] = furl\r
-        message.send()\r
-\r
-    async def parse_urls(self, msg, urls, sender, mtype):\r
-        """Parse urls and send the result to the sender."""\r
-        body = msg["body"].lower()\r
-        if "nsfw" in body: return\r
-        if "nsfl" in body: return\r
-        for u in urls:\r
-            if u in self.messages[sender]["links"]:\r
-                continue\r
-            else:\r
-                self.messages[sender]["links"].add(u)\r
-\r
-            uri = urlparse(u)\r
-            await self.parse_uri(uri, sender, mtype)\r
-\r
-    def sed_command(self, msg, sender, mtype):\r
-        """Process sed command."""\r
-        try:\r
-            text = msg["body"]\r
-            if not sed_cmd.match(text):\r
-                self.messages[sender]["messages"].add(text)\r
-                return\r
-            sed_args = sed_parse.split(text)\r
-\r
-            if len(sed_args) < 4:\r
-                return\r
-\r
-            sed = Sed()\r
-            sed.load_string(text)\r
-\r
-            for message in self.messages[sender]["messages"]:\r
-                if not re.match(sed_args[1], message):\r
-                    continue\r
-                msg = io.StringIO(message)\r
-                res = "\n".join(sed.apply(msg, None))\r
-                self.messages[sender]["messages"].add(res)\r
-                return self.send_message(\r
-                    mto=sender,\r
-                    mbody=res,\r
-                    mtype=mtype,\r
-                )\r
-\r
-        except Exception as e:\r
-            print(e)\r
-\r
-    def __init__(self, jid, password, nick="angel", autojoin=None):\r
-        """Initialize the bot."""\r
-        ClientXMPP.__init__(self, jid, password)\r
-        self.jid = jid\r
-        self.nick = nick\r
-        self.autojoin = autojoin or []\r
-        self.register_plugin("xep_0030")\r
-        self.register_plugin("xep_0060")\r
-        self.register_plugin("xep_0054")\r
-        self.register_plugin("xep_0045")\r
-        self.register_plugin("xep_0066")\r
-        self.register_plugin("xep_0084")\r
-        self.register_plugin("xep_0153")\r
-        self.register_plugin("xep_0363")\r
-\r
-        self.add_event_handler("session_start", self.session_start)\r
-        self.add_event_handler("message", self.message)\r
-        self.add_event_handler("groupchat_message", self.muc_message)\r
-        # self.add_event_handler("vcard_avatar_update", self.debug_event)\r
-        # self.add_event_handler("stream_error", self.debug_event)\r
-        self.add_event_handler("disconnected", lambda _: self.connect())\r
-\r
-    async def session_start(self, event):\r
-        """Start the bot."""\r
-        self.send_presence()\r
-        await self.get_roster()\r
-        await self.update_info()\r
-        for channel in self.autojoin:\r
-            try:\r
-                self.plugin["xep_0045"].join_muc(channel, self.nick)\r
-            except Exception as e:\r
-                print(e)\r
-\r
-    async def update_info(self):\r
-        """Update the bot info."""\r
-        with open("angel.png", "rb") as avatar_file:\r
-            avatar = avatar_file.read()\r
-\r
-        avatar_type = "image/png"\r
-        avatar_id = self.plugin["xep_0084"].generate_id(avatar)\r
-        avatar_bytes = len(avatar)\r
-\r
-        asyncio.gather(self.plugin["xep_0084"].publish_avatar(avatar))\r
-\r
-        asyncio.gather(\r
-            self.plugin["xep_0153"].set_avatar(\r
-                avatar=avatar,\r
-                mtype=avatar_type,\r
-            )\r
-        )\r
-\r
-        info = {\r
-            "id": avatar_id,\r
-            "type": avatar_type,\r
-            "bytes": avatar_bytes,\r
-        }\r
-\r
-        asyncio.gather(self.plugin["xep_0084"].publish_avatar_metadata([info]))\r
-\r
-        vcard = self.plugin["xep_0054"].make_vcard()\r
-\r
-        vcard["URL"] = "https://wiki.kalli.st/Angel"\r
-        vcard["DESC"] = "Angel is a bot that can do link previews and embeds."\r
-        vcard["NICKNAME"] = "Angel"\r
-        vcard["FN"] = "Angel"\r
-\r
-        asyncio.gather(self.plugin["xep_0054"].publish_vcard(vcard))\r
-\r
-    async def message(self, msg):\r
-        """Process a message."""\r
-        if msg["type"] in ("chat", "normal"):\r
-            edit = "urn:xmpp:message-correct:0" in str(msg)\r
-            if edit:\r
-                return\r
-\r
-            mtype = msg["type"]\r
-            sender = msg["from"].bare\r
-\r
-            try:\r
-                if not msg["oob"]["url"]:\r
-                    if urls := self.get_urls(msg):\r
-                        await self.parse_urls(msg, urls, sender, mtype)\r
-            except Exception as e:\r
-                print(e)\r
-\r
-            self.sed_command(msg, sender, mtype)\r
-\r
-    async def muc_message(self, msg):\r
-        """Process a groupchat message."""\r
-        if msg["type"] in ("groupchat", "normal"):\r
-            edit = "urn:xmpp:message-correct:0" in str(msg)\r
-\r
-            if edit:\r
-                return\r
-\r
-            if msg["mucnick"] == self.nick:\r
-                return\r
-\r
-            mtype = msg["type"]\r
-            sender = msg["from"].bare\r
-\r
-\r
-            try:\r
-                if not msg["oob"]["url"]:\r
-                    if urls := self.get_urls(msg):\r
-                        await self.parse_urls(msg, urls, sender, mtype)\r
-            except Exception as e:\r
-                print(e)\r
-\r
-            self.sed_command(msg, sender, mtype)\r
-\r
-\r
-if __name__ == "__main__":\r
-    config = configparser.ConfigParser()\r
-    config.read("config.ini")\r
-    jid = config["angel"]["jid"]\r
-    password = config["angel"]["password"]\r
-    autojoin = config["angel"]["autojoin"].split()\r
-    nick = config["angel"]["nick"]\r
-    bot = AngelBot(jid, password, nick=nick, autojoin=autojoin)\r
-\r
-    bot.connect()\r
-    bot.process(forever=True)\r
+import requests
+import bs4
+import yt_dlp as youtube_dl
+import random
+import configparser
+import re
+import io
+import os
+import asyncio
+from collections import defaultdict
+from PythonSed import Sed
+from slixmpp import ClientXMPP
+from urllib.parse import urlparse, parse_qs, urlunparse
+from pantomime import normalize_mimetype
+import cgi
+
+sed_parse = re.compile("(?<!\\\\)[/#]")
+sed_cmd = re.compile("^s[/#].*[/#].*[/#]")
+
+parser = "html.parser"
+user_agent = "Mozilla/5.0 (X11; Linux x86_64; rv:10.0)"
+" Gecko/20100101 Firefox/10.0"
+accept_lang = "en-US"
+data_limit = 100000000  # 100MB
+
+headers = {
+    "user-agent": user_agent,
+    "Accept-Language": accept_lang,
+    "Cache-Control": "no-cache",
+}
+
+youtube_links = ["www.youtube.com", "m.youtube.com"]
+
+youtube_link = "youtu.be"
+
+ydl = youtube_dl.YoutubeDL()
+
+invidious_instances = ["invidious.kalli.st"]
+
+block_list = ("localhost", "127.0.0.1", "0.0.0.0")
+
+req_list = ("http://", "https://")
+
+html_files = ("text/html", "application/xhtml+xml")
+
+
+class Lifo(list):
+    """Limited size LIFO array to store messages and urls."""
+
+    def __init__(self, size):
+        """Initialize the LIFO array."""
+        super().__init__()
+        self.size = size
+
+    def add(self, item):
+        """Add an item to the LIFO array."""
+        self.insert(0, item)
+        if len(self) > self.size:
+            self.pop()
+
+
+def get_youtube_title(url):
+    """Get the title of a youtube video."""
+    try:
+        info = ydl.extract_info(url, download=False)
+        return info["title"]
+    except Exception as e:
+        print(e)
+        return None
+
+
+def get_invidious_link(yurl):
+    """Get an invidious link from a youtube link."""
+    video = yurl.split("/")[-1]
+    instance = random.choice(invidious_instances)
+    return f"https://{instance}/watch?v={video}"
+
+
+def get_yurl(path):
+    """Get a youtube link from a path."""
+    yurl = f"https://youtu.be/{path}"
+    return yurl
+
+
+class AngelBot(ClientXMPP):
+    """AngelBot class."""
+
+    messages = defaultdict(
+        lambda: {
+            "messages": Lifo(100),
+            "links": Lifo(10),
+            "previews": Lifo(10),
+        }
+    )
+
+    def get_urls(self, msg):
+        """Get urls from a message."""
+        str_list = msg["body"].strip().split()
+        urls = [u for u in str_list if any(r in u for r in req_list)]
+        return urls
+
+    def send_youtube_info(self, uri, sender, mtype):
+        """Send youtube info to the sender."""
+        yurl = None
+        if uri.netloc == youtube_link:
+            yurl = get_yurl(uri.path)
+        elif "v" in (query := parse_qs(uri.query)):
+            if v := query["v"]:
+                yurl = get_yurl(v[0])
+            else:
+                return
+
+        invidious = get_invidious_link(yurl)
+
+        if output := get_youtube_title(invidious):
+            if output in self.messages[sender]["previews"]:
+                return
+            self.messages[sender]["previews"].add(output)
+
+            self.send_message(mto=sender, mbody=f"*{output}*", mtype=mtype)
+            self.send_message(mto=sender, mbody=invidious, mtype=mtype)
+
+    async def parse_uri(self, uri, sender, mtype):
+        """Parse a uri and send the result to the sender."""
+        netloc = uri.netloc
+        if netloc in (youtube_links + [youtube_link]):
+            self.send_youtube_info(uri, sender, mtype)
+        elif netloc.split(":")[0] in block_list:
+            return
+        else:
+            await self.process_link(uri, sender, mtype)
+
+    async def process_link(self, uri, sender, mtype):
+        """Process a link and send the result to the sender."""
+        url = urlunparse(uri)
+        r = requests.get(url, stream=True, headers=headers, timeout=6)
+        if not r.ok:
+            return
+
+        ftype = normalize_mimetype(r.headers.get("content-type"))
+
+        if not ftype:
+            return
+
+        if ftype in html_files:
+            data = ""
+            for i in r.iter_content(chunk_size=1024, decode_unicode=False):
+                data += i.decode("utf-8", errors="ignore")
+                if len(data) > data_limit or "</head>" in data.lower():
+                    break
+            soup = bs4.BeautifulSoup(data, parser)
+            if title := soup.find("title"):
+                output = title.text.strip()
+                if output:
+                    output = f"*{output}*" if ("\n" not in output) else output
+                    if output in self.messages[sender]["previews"]:
+                        return
+
+                    self.messages[sender]["previews"].add(output)
+                    if r.history:
+                        self.send_message(mto=sender, mbody=r.url, mtype=mtype)
+                    self.send_message(mto=sender, mbody=output, mtype=mtype)
+
+        else:
+            try:
+                lenght = 0
+                outfile = io.BytesIO()
+                for chunk in r.iter_content(
+                    chunk_size=512,
+                    decode_unicode=False,
+                ):
+                    lenght += 512
+                    if lenght >= data_limit:
+                        return
+                    outfile.write(chunk)
+
+                content_disposition = r.headers.get("content-disposition")
+                filename = None
+                if content_disposition:
+                    _, params = cgi.parse_header(content_disposition)
+                    filename = params.get("filename")
+                else:
+                    filename = os.path.basename(uri.path)
+
+                ext = os.path.splitext(filename)[1] if filename else ".txt"
+                fname = filename if filename else f"file{ext}"
+                await self.embed_file(url, sender, mtype, ftype, fname, outfile)
+            except Exception as e:
+                print(e)
+
+    async def embed_file(self, url, sender, mtype, ftype, fname, outfile):
+        """Embed a file and send the result to the sender."""
+        furl = await self.plugin["xep_0363"].upload_file(
+            fname, content_type=ftype, input_file=outfile
+        )
+        self.messages[sender]["links"].add(furl)
+        message = self.make_message(sender)
+        message["body"] = furl
+        message["type"] = mtype
+        message["oob"]["url"] = furl
+        message.send()
+
+    async def parse_urls(self, msg, urls, sender, mtype):
+        """Parse urls and send the result to the sender."""
+        body = msg["body"].lower()
+        if "nsfw" in body: return
+        if "nsfl" in body: return
+        for u in urls:
+            if u in self.messages[sender]["links"]:
+                continue
+            else:
+                self.messages[sender]["links"].add(u)
+
+            uri = urlparse(u)
+            await self.parse_uri(uri, sender, mtype)
+
+    def sed_command(self, msg, sender, mtype):
+        """Process sed command."""
+        try:
+            text = msg["body"]
+            if not sed_cmd.search(text):
+                self.messages[sender]["messages"].add(text)
+                return
+            sed_args = sed_parse.split(text)
+
+            if len(sed_args) < 4:
+                return
+
+            sed = Sed()
+            sed.load_string(text)
+
+            for message in self.messages[sender]["messages"]:
+                if not re.match(sed_args[1], message):
+                    continue
+                msg = io.StringIO(message)
+                res = "\n".join(sed.apply(msg, None))
+                self.messages[sender]["messages"].add(res)
+                return self.send_message(
+                    mto=sender,
+                    mbody=res,
+                    mtype=mtype,
+                )
+
+        except Exception as e:
+            print(e)
+
+    def __init__(self, jid, password, nick="angel", autojoin=None):
+        """Initialize the bot."""
+        ClientXMPP.__init__(self, jid, password)
+        self.jid = jid
+        self.nick = nick
+        self.autojoin = autojoin or []
+        self.register_plugin("xep_0030")
+        self.register_plugin("xep_0060")
+        self.register_plugin("xep_0054")
+        self.register_plugin("xep_0045")
+        self.register_plugin("xep_0066")
+        self.register_plugin("xep_0084")
+        self.register_plugin("xep_0153")
+        self.register_plugin("xep_0363")
+
+        self.add_event_handler("session_start", self.session_start)
+        self.add_event_handler("message", self.message)
+        self.add_event_handler("groupchat_message", self.muc_message)
+        # self.add_event_handler("vcard_avatar_update", self.debug_event)
+        # self.add_event_handler("stream_error", self.debug_event)
+        self.add_event_handler("disconnected", lambda _: self.connect())
+
+    async def session_start(self, event):
+        """Start the bot."""
+        self.send_presence()
+        await self.get_roster()
+        await self.update_info()
+        for channel in self.autojoin:
+            try:
+                self.plugin["xep_0045"].join_muc(channel, self.nick)
+            except Exception as e:
+                print(e)
+
+    async def update_info(self):
+        """Update the bot info."""
+        with open("angel.png", "rb") as avatar_file:
+            avatar = avatar_file.read()
+
+        avatar_type = "image/png"
+        avatar_id = self.plugin["xep_0084"].generate_id(avatar)
+        avatar_bytes = len(avatar)
+
+        asyncio.gather(self.plugin["xep_0084"].publish_avatar(avatar))
+
+        asyncio.gather(
+            self.plugin["xep_0153"].set_avatar(
+                avatar=avatar,
+                mtype=avatar_type,
+            )
+        )
+
+        info = {
+            "id": avatar_id,
+            "type": avatar_type,
+            "bytes": avatar_bytes,
+        }
+
+        asyncio.gather(self.plugin["xep_0084"].publish_avatar_metadata([info]))
+
+        vcard = self.plugin["xep_0054"].make_vcard()
+
+        vcard["URL"] = "https://wiki.kalli.st/Angel"
+        vcard["DESC"] = "Angel is a bot that can do link previews and embeds."
+        vcard["NICKNAME"] = "Angel"
+        vcard["FN"] = "Angel"
+
+        asyncio.gather(self.plugin["xep_0054"].publish_vcard(vcard))
+
+    async def message(self, msg):
+        """Process a message."""
+        if msg["type"] in ("chat", "normal"):
+            edit = "urn:xmpp:message-correct:0" in str(msg)
+            if edit:
+                return
+
+            mtype = msg["type"]
+            sender = msg["from"].bare
+
+            try:
+                if not msg["oob"]["url"]:
+                    if urls := self.get_urls(msg):
+                        await self.parse_urls(msg, urls, sender, mtype)
+            except Exception as e:
+                print(e)
+
+            self.sed_command(msg, sender, mtype)
+
+    async def muc_message(self, msg):
+        """Process a groupchat message."""
+        if msg["type"] in ("groupchat", "normal"):
+            edit = "urn:xmpp:message-correct:0" in str(msg)
+
+            if edit:
+                return
+
+            if msg["mucnick"] == self.nick:
+                return
+
+            mtype = msg["type"]
+            sender = msg["from"].bare
+
+
+            try:
+                if not msg["oob"]["url"]:
+                    if urls := self.get_urls(msg):
+                        await self.parse_urls(msg, urls, sender, mtype)
+            except Exception as e:
+                print(e)
+
+            self.sed_command(msg, sender, mtype)
+
+
+if __name__ == "__main__":
+    config = configparser.ConfigParser()
+    config.read("config.ini")
+    jid = config["angel"]["jid"]
+    password = config["angel"]["password"]
+    autojoin = config["angel"]["autojoin"].split()
+    nick = config["angel"]["nick"]
+    bot = AngelBot(jid, password, nick=nick, autojoin=autojoin)
+
+    bot.connect()
+    bot.process(forever=True)