From e4ba87171b3311345702c5150a4173164623f7cc Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Tue, 11 Oct 2022 19:40:33 -0700 Subject: [PATCH] basic discord bot stuff --- poetry.lock | 95 +++++++++++++++++++++++++- pyproject.toml | 1 + wiki_postbot/clients/__init__.py | 8 +++ wiki_postbot/clients/discord_client.py | 85 +++++++++++++++++++++++ wiki_postbot/creds.py | 9 +++ wiki_postbot/patterns/wikilink.py | 11 ++- 6 files changed, 205 insertions(+), 4 deletions(-) create mode 100644 wiki_postbot/clients/__init__.py create mode 100644 wiki_postbot/clients/discord_client.py diff --git a/poetry.lock b/poetry.lock index b70f3a9..ee13059 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,42 @@ +[[package]] +name = "aiohttp" +version = "3.8.3" +description = "Async http client/server framework (asyncio)" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = ">=4.0.0a3,<5.0" +attrs = ">=17.3.0" +charset-normalizer = ">=2.0,<3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["aiodns", "brotli", "cchardet"] + +[[package]] +name = "aiosignal" +version = "1.2.0" +description = "aiosignal: a list of registered asynchronous callbacks" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "async-timeout" +version = "4.0.2" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "atomicwrites" version = "1.4.0" @@ -10,7 +49,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" name = "attrs" version = "21.4.0" description = "Classes Without Boilerplate" -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" @@ -58,6 +97,23 @@ python-versions = "*" [package.extras] test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] +[[package]] +name = "discord.py" +version = "2.0.1" +description = "A Python wrapper for the Discord API" +category = "main" +optional = false +python-versions = ">=3.8.0" + +[package.dependencies] +aiohttp = ">=3.7.4,<4" + +[package.extras] +voice = ["PyNaCl (>=1.3.0,<1.6)"] +test = ["typing-extensions (>=4.3,<5)", "pytest-mock", "pytest-cov", "pytest-asyncio", "pytest", "coverage"] +speed = ["cchardet (==2.1.7)", "brotli", "aiodns (>=1.1)", "orjson (>=3.5.4)"] +docs = ["typing-extensions (>=4.3,<5)", "sphinxcontrib-websupport", "sphinxcontrib-trio (==1.1.2)", "sphinx (==4.4.0)"] + [[package]] name = "faker" version = "15.1.0" @@ -69,6 +125,14 @@ python-versions = ">=3.7" [package.dependencies] python-dateutil = ">=2.4" +[[package]] +name = "frozenlist" +version = "1.3.1" +description = "A list-like structure which implements collections.abc.MutableSequence" +category = "main" +optional = false +python-versions = ">=3.7" + [[package]] name = "idna" version = "3.3" @@ -85,6 +149,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "multidict" +version = "6.0.2" +description = "multidict implementation" +category = "main" +optional = false +python-versions = ">=3.7" + [[package]] name = "oauthlib" version = "3.2.0" @@ -316,12 +388,27 @@ brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +[[package]] +name = "yarl" +version = "1.8.1" +description = "Yet another URL library" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "c5c99285ff355e2be2ef7a9133813b15c6b445b52f191f5720911d059b937750" +content-hash = "a12006faa296b74e34d600ab7300f2926d58c153fa76c15a5b73f7c91077b6ab" [metadata.files] +aiohttp = [] +aiosignal = [] +async-timeout = [] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, @@ -346,7 +433,9 @@ commonmark = [ {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, ] +"discord.py" = [] faker = [] +frozenlist = [] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, @@ -355,6 +444,7 @@ iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] +multidict = [] oauthlib = [ {file = "oauthlib-3.2.0-py3-none-any.whl", hash = "sha256:6db33440354787f9b7f3a6dbd4febf5d0f93758354060e802f6c06cb493022fe"}, {file = "oauthlib-3.2.0.tar.gz", hash = "sha256:23a8208d75b902797ea29fd31fa80a15ed9dc2c6c16fe73f5d346f83f6fa27a2"}, @@ -419,3 +509,4 @@ urllib3 = [ {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, ] +yarl = [] diff --git a/pyproject.toml b/pyproject.toml index 49bebd0..c052856 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ rich = "^12.4.4" parse = "^1.19.0" pywikibot = "^7.7.0" pyparsing = "^3.0.9" +"discord.py" = "^2.0.1" [tool.poetry.dev-dependencies] pytest = "^7.1.2" diff --git a/wiki_postbot/clients/__init__.py b/wiki_postbot/clients/__init__.py new file mode 100644 index 0000000..85071a2 --- /dev/null +++ b/wiki_postbot/clients/__init__.py @@ -0,0 +1,8 @@ +""" +Bots that listen to different sources. + +Quick and dirty for now, not bothering to generalize this, but in the future +would be nice to be able to have multiple clients listening to different sources, +apply (contextually modified) actions, and then optionally send on to some interface/format +""" + diff --git a/wiki_postbot/clients/discord_client.py b/wiki_postbot/clients/discord_client.py new file mode 100644 index 0000000..9530f16 --- /dev/null +++ b/wiki_postbot/clients/discord_client.py @@ -0,0 +1,85 @@ +import discord +from discord import Client, Intents +from wiki_postbot.creds import Discord_Creds +from wiki_postbot.patterns.wikilink import Wikilink + +from discord.ext import commands +from discord import Emoji + +intents = Intents.default() +intents.message_content = True + +bot = commands.Bot(command_prefix='/', intents=intents) + +DEBUG = False + +@bot.event +async def on_message(message:discord.Message): + print(message) + + if message.content == 'ping': + await message.channel.send('pong') + + if 'good bot' in message.content: + await message.add_reaction("❤️‍🔥") + + wl = Wikilink.parse(message.content) + if len(wl) > 0: + if DEBUG: + await message.channel.send(f"Wikilinks detected: \n" + '\n'.join([str(l) for l in wl])) + else: + await message.add_reaction("⏳") + + await bot.process_commands(message) + +@bot.command() +async def debug(ctx:discord.ext.commands.Context, arg): + print('debug command') + global DEBUG + if arg == "on": + DEBUG = True + await ctx.message.add_reaction("🧪") + elif arg == "off": + DEBUG = False + await ctx.message.add_reaction("🤐") + else: + await ctx.message.reply("usage: /debug off or /debug on") +# @bot.command + + +# +# class MyClient(Client): +# +# def __init__(self, intents=None, **kwargs): +# if intents is None: +# intents = Intents.default() +# intents.message_content = True +# +# super(MyClient, self).__init__(intents=intents, **kwargs) +# +# +# async def on_ready(self): +# print('Logged on as', self.user) +# +# async def on_message(self, message): +# print(message) +# # don't respond to ourselves +# if message.author == self.user: +# return +# +# if message.content == 'ping': +# await message.channel.send('pong') +# +# wl = Wikilink.parse(message.content) +# if len(wl)>0: +# await message.channel.send(f"Wikilinks detected: \n" + '\n'.join([str(l) for l in wl])) + + + +if __name__ == "__main__": + creds = Discord_Creds.from_json('discord_creds.json') + # client = MyClient() + # client.run(creds.token) + + + bot.run(creds.token) diff --git a/wiki_postbot/creds.py b/wiki_postbot/creds.py index e4afcd3..2779792 100644 --- a/wiki_postbot/creds.py +++ b/wiki_postbot/creds.py @@ -32,3 +32,12 @@ class Zenodo_Creds: creds = json.load(jfile) return Zenodo_Creds(**creds) +@dataclass +class Discord_Creds: + token:str + + @classmethod + def from_json(cls, path:Path) -> 'Discord_Creds': + with open(path, 'r') as jfile: + creds = json.load(jfile) + return Discord_Creds(**creds) diff --git a/wiki_postbot/patterns/wikilink.py b/wiki_postbot/patterns/wikilink.py index fee1583..7770b41 100644 --- a/wiki_postbot/patterns/wikilink.py +++ b/wiki_postbot/patterns/wikilink.py @@ -1,8 +1,9 @@ import re -from wiki_postbot.patterns import Pattern +from wiki_postbot.patterns.patterns import Pattern from dataclasses import dataclass from typing import Optional, Union, List import pyparsing as pp +from pprint import pformat WIKILINK = re.compile(r'\[\[(.*?)\]\]', re.IGNORECASE) @@ -73,6 +74,9 @@ class NBack: def __eq__(self, other:'NBack'): return all([getattr(self, f) == getattr(other, f) for f in self.FIELDS]) + def __repr__(self) -> str: + return pformat({f:getattr(self, f) for f in self.FIELDS}) + class Wikilink(Pattern): """ Pattern for detecting wikilinks! @@ -84,7 +88,7 @@ class Wikilink(Pattern): In each of the following examples, `LINK` is a placeholder for the text of the wikilink to be made. - # N-Back Links + # N-Back Links (see :class:`.NBack`) For all of these, whitespace in-between the n-back specifier and the link text will be ignored. So `[[^LINK]]` and `[[^ LINK]]` are both valid. @@ -171,5 +175,8 @@ class Wikilink(Pattern): def __eq__(self, other:'Wikilink'): return all(getattr(self, f) == getattr(other, f) for f in self.FIELDS) + def __repr__(self) -> str: + return pformat({f:getattr(self, f) for f in self.FIELDS if getattr(self, f) is not None}) +