working version of wikibot!!!!
This commit is contained in:
parent
079669a6d3
commit
5e60e5f8b0
6 changed files with 245 additions and 31 deletions
|
@ -37,6 +37,10 @@ class TestStr:
|
|||
"[[^{3}Link]]",
|
||||
Wikilink("Link", nback=NBack(end=3))
|
||||
)
|
||||
section = (
|
||||
"[[^{1,3}Link#With section]]",
|
||||
Wikilink("Link", nback=NBack(1,3), section="With section")
|
||||
)
|
||||
|
||||
|
||||
def pad_garbage(string:str) -> str:
|
||||
|
@ -121,6 +125,17 @@ def test_nback_range_end(test_string, expected):
|
|||
assert len(wl) == 1
|
||||
assert wl[0] == expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"test_string,expected",
|
||||
[TestStr.section])
|
||||
def test_section(test_string, expected):
|
||||
test_string = pad_garbage(test_string)
|
||||
wl = Wikilink.parse(test_string)
|
||||
# pdb.set_trace()
|
||||
assert len(wl) == 1
|
||||
assert wl[0] == expected
|
||||
|
||||
|
||||
def test_triplet_full():
|
||||
pass
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import discord
|
||||
from discord import Client, Intents
|
||||
from wiki_postbot.creds import Discord_Creds
|
||||
from discord import Client, Intents, Embed, Message
|
||||
from wiki_postbot.creds import Discord_Creds, Mediawiki_Creds
|
||||
from wiki_postbot.patterns.wikilink import Wikilink
|
||||
from wiki_postbot.interfaces.mediawiki import Wiki
|
||||
|
||||
from discord.ext import commands
|
||||
from discord import Emoji
|
||||
import pdb
|
||||
#
|
||||
# intents = Intents.default()
|
||||
# intents.message_content = True
|
||||
|
@ -38,30 +40,70 @@ from discord import Emoji
|
|||
|
||||
|
||||
|
||||
class MyClient(Client):
|
||||
class DiscordClient(Client):
|
||||
|
||||
def __init__(self, intents=None, debug:bool=False, **kwargs):
|
||||
def __init__(self, wiki:Wiki, intents=None, debug:bool=False, **kwargs):
|
||||
if intents is None:
|
||||
intents = Intents.default()
|
||||
intents.message_content = True
|
||||
|
||||
self.wiki = wiki
|
||||
if self.wiki.sess is None:
|
||||
raise RuntimeError("Wiki client is not logged in! Login before passing to discord client")
|
||||
|
||||
self.debug = debug
|
||||
|
||||
super(MyClient, self).__init__(intents=intents, **kwargs)
|
||||
super(DiscordClient, self).__init__(intents=intents, **kwargs)
|
||||
|
||||
|
||||
async def on_ready(self):
|
||||
print('Logged on as', self.user)
|
||||
|
||||
async def on_message(self, message):
|
||||
|
||||
async def on_message(self, message:discord.message.Message):
|
||||
print(message)
|
||||
# don't respond to ourselves
|
||||
if message.author == self.user:
|
||||
return
|
||||
|
||||
|
||||
if 'good bot' in message.content:
|
||||
await message.add_reaction("❤️🔥")
|
||||
|
||||
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 self.debug:
|
||||
await message.channel.send(f"Wikilinks detected: \n" + '\n'.join([str(l) for l in wl]))
|
||||
|
||||
await message.add_reaction("⏳")
|
||||
try:
|
||||
result = self.wiki.handle_discord(message)
|
||||
ok = result.ok
|
||||
except:
|
||||
# TODO: Log here!
|
||||
result = None
|
||||
ok = False
|
||||
|
||||
if ok:
|
||||
await message.remove_reaction("⏳", self.user)
|
||||
await message.add_reaction("✅")
|
||||
else:
|
||||
await message.remove_reaction("⏳", self.user)
|
||||
await message.add_reaction("❌")
|
||||
|
||||
if result and result.reply:
|
||||
await message.channel.send(embed=Embed().add_field(name="WikiLinks", value=result.reply))
|
||||
|
||||
|
||||
# TODO: Logging!
|
||||
|
||||
# def add_links(self, links:Wikilink, msg:discord.message.Message):
|
||||
# if 'testing links' in message.content:
|
||||
# await message.channel.send(embed=Embed().add_field(name="Links", value="there are [links](https://example.com)"))
|
||||
# #await message.edit(content=message.content, embed=Embed().add_field(name="Links", value="There are [links](https://example.com) in here"))
|
||||
# #await message.channel.send("Bot is testing if it can [make links](https://example.com)")
|
||||
|
||||
|
||||
|
||||
async def debug(ctx: discord.ext.commands.Context, arg):
|
||||
print('debug command')
|
||||
|
@ -77,9 +119,13 @@ class MyClient(Client):
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
creds = Discord_Creds.from_json('discord_creds.json')
|
||||
# client = MyClient()
|
||||
# client.run(creds.token)
|
||||
discord_creds = Discord_Creds.from_json('discord_creds.json')
|
||||
wiki_creds = Mediawiki_Creds.from_json('mediawiki_creds.json')
|
||||
wiki = Wiki(url="https://cscw.sciop.net")
|
||||
wiki.login(wiki_creds)
|
||||
|
||||
client = DiscordClient(wiki=wiki)
|
||||
client.run(discord_creds.token)
|
||||
|
||||
|
||||
bot.run(creds.token)
|
||||
# bot.run(creds.token)
|
||||
|
|
|
@ -2,4 +2,16 @@
|
|||
Helper functions for dealing with wiki syntax as well as format
|
||||
output posts triggered by :class:`.action.WikiLink`
|
||||
"""
|
||||
from dataclasses import dataclass
|
||||
import wikitextparser as wtp
|
||||
|
||||
@dataclass
|
||||
class WikiPage:
|
||||
title:str
|
||||
source:str
|
||||
content:wtp.WikiText
|
||||
|
||||
@classmethod
|
||||
def from_source(self, title, source) -> 'WikiPage':
|
||||
content = wtp.parse(source)
|
||||
return WikiPage(title, source, content)
|
|
@ -5,21 +5,27 @@ from typing import List
|
|||
from urllib.parse import urljoin
|
||||
from dataclasses import dataclass
|
||||
from wiki_postbot.creds import Mediawiki_Creds
|
||||
from wiki_postbot.formats.wiki import WikiPage
|
||||
from wiki_postbot.templates.wiki import TemplateMessage
|
||||
from wiki_postbot.patterns.wikilink import Wikilink
|
||||
from wiki_postbot.actions import Result
|
||||
from datetime import datetime
|
||||
import requests
|
||||
from discord.message import Message, Embed
|
||||
import pdb
|
||||
|
||||
# creds = Mediawiki_Creds.from_json('mediawiki_creds.json')
|
||||
|
||||
|
||||
|
||||
|
||||
class Wiki:
|
||||
def __init__(self, url:str, api_suffix:str="/api.php"):
|
||||
def __init__(self, url:str, api_suffix:str="/api.php", index_page="Discord Messages"):
|
||||
self.url = url
|
||||
self.api_url = urljoin(self.url, api_suffix)
|
||||
self.sess = None
|
||||
self.index_page = index_page
|
||||
|
||||
|
||||
def login(self, creds:Mediawiki_Creds) -> requests.Session:
|
||||
def login(self, creds:Mediawiki_Creds):
|
||||
# get token to log in
|
||||
sess = requests.Session()
|
||||
|
||||
|
@ -48,9 +54,8 @@ class Wiki:
|
|||
)
|
||||
assert login_result.json()['login']['result'] == "Success"
|
||||
self.sess = sess
|
||||
return sess
|
||||
|
||||
def get_page_content(self, page:str) -> str:
|
||||
def get_page(self, page:str) -> WikiPage:
|
||||
|
||||
content = self.sess.get(
|
||||
self.api_url,
|
||||
|
@ -62,10 +67,21 @@ class Wiki:
|
|||
'format':'json'
|
||||
}
|
||||
).json()
|
||||
return content['parse']['wikitext']
|
||||
return WikiPage.from_source(title=content['parse']['title'], source=content['parse']['wikitext'])
|
||||
|
||||
def insert_text(self, page, section, text):
|
||||
|
||||
# TODO: Move finding section IDs into the page class!
|
||||
page_text = self.get_page(page)
|
||||
|
||||
sections = page_text.content.get_sections()
|
||||
matching_section = -1
|
||||
for i, page_section in enumerate(sections):
|
||||
if page_section.title is not None and page_section.title.strip().lower() == section.lower():
|
||||
matching_section = i
|
||||
break
|
||||
|
||||
|
||||
token = self.sess.get(
|
||||
self.api_url,
|
||||
params={
|
||||
|
@ -76,15 +92,67 @@ class Wiki:
|
|||
verify=False
|
||||
).json()['query']['tokens']['csrftoken']
|
||||
|
||||
result = self.sess.post(
|
||||
self.api_url,
|
||||
data={
|
||||
"action":"edit",
|
||||
"title":page,
|
||||
"section":"new",
|
||||
"sectiontitle":section,
|
||||
"appendtext":text,
|
||||
"format":"json",
|
||||
"token":token
|
||||
}
|
||||
)
|
||||
if matching_section >= 0:
|
||||
print(f'found matching section {matching_section}')
|
||||
result = self.sess.post(
|
||||
self.api_url,
|
||||
data={
|
||||
"action":"edit",
|
||||
"title":page,
|
||||
"section":str(matching_section),
|
||||
"appendtext":text,
|
||||
"format":"json",
|
||||
"token":token
|
||||
}
|
||||
)
|
||||
else:
|
||||
print('making new section')
|
||||
result = self.sess.post(
|
||||
self.api_url,
|
||||
data={
|
||||
"action":"edit",
|
||||
"title":page,
|
||||
"section":"new",
|
||||
"sectiontitle":section,
|
||||
"appendtext":text,
|
||||
"format":"json",
|
||||
"token":token
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
def handle_discord(self, msg:Message) -> Result:
|
||||
"""
|
||||
Not being precious about this, just implementing
|
||||
and will worry about generality later!
|
||||
"""
|
||||
# Get message in mediawiki template formatting
|
||||
template_str = TemplateMessage.format_discord(msg)
|
||||
|
||||
# parse wikilinks, add to each page
|
||||
wikilinks = Wikilink.parse(msg.content)
|
||||
errored_pages = []
|
||||
for link in wikilinks:
|
||||
if link.section is None:
|
||||
section = "Discord"
|
||||
else:
|
||||
section = link.section
|
||||
|
||||
res = self.insert_text(link.link, section, template_str)
|
||||
if res.json()['edit']['result'] != 'Success':
|
||||
errored_pages.append(res.json())
|
||||
|
||||
# Add to index page (only once)
|
||||
self.add_to_index(template_str)
|
||||
|
||||
if len(errored_pages) == 0:
|
||||
# gather links for a reply
|
||||
reply = '\n'.join([f"[{l.link}]({urljoin(self.url, l.link.replace(' ', '_'))})" for l in wikilinks])
|
||||
|
||||
return Result(ok=True, log=f"Successfully posted message to {[l.link for l in wikilinks]}", reply=reply)
|
||||
else:
|
||||
return Result(ok=False, log=f"Got exceptions: {errored_pages}")
|
||||
|
||||
def add_to_index(self, message):
|
||||
section = datetime.today().strftime("%y-%m-%d")
|
||||
self.insert_text(page=self.index_page, section=section, text=message)
|
||||
|
|
|
@ -139,6 +139,9 @@ class Wikilink(Pattern):
|
|||
elif isinstance(nback, pp.ParseResults):
|
||||
nback = NBack(**dict(nback))
|
||||
|
||||
if isinstance(section, pp.ParseResults):
|
||||
section = section[0]
|
||||
|
||||
self.nback = nback
|
||||
self.predicate = predicate
|
||||
self.object = object
|
||||
|
@ -159,8 +162,12 @@ class Wikilink(Pattern):
|
|||
# main wikilink subject text
|
||||
link = pp.Word(pp.printables+ " ", excludeChars="#[]{}|")
|
||||
|
||||
# optional page section
|
||||
hash = pp.Literal("#").suppress()
|
||||
section = hash + link
|
||||
|
||||
# Combine all
|
||||
parser = lbracket + pp.Optional(nback) + link("link") + rbracket
|
||||
parser = lbracket + pp.Optional(nback) + link("link") + pp.Optional(section("section")) + rbracket
|
||||
return parser
|
||||
|
||||
@classmethod
|
||||
|
|
66
wiki_postbot/templates/wiki.py
Normal file
66
wiki_postbot/templates/wiki.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
"""
|
||||
Templates for representing different kinds of messages on mediawiki
|
||||
"""
|
||||
|
||||
from wiki_postbot.formats.wiki import WikiPage
|
||||
from abc import abstractmethod
|
||||
from discord.message import Message
|
||||
|
||||
class WikiTemplate(WikiPage):
|
||||
|
||||
@abstractmethod
|
||||
def format_discord(self, msg:Message) -> str:
|
||||
"""
|
||||
Format a discord message into a template string
|
||||
"""
|
||||
|
||||
class TemplateMessage(WikiTemplate):
|
||||
|
||||
@classmethod
|
||||
def format_discord(self, msg:Message) -> str:
|
||||
return (
|
||||
"{{Message\n"
|
||||
f"|Author={msg.author.name}\n"
|
||||
f"|Avatar={msg.author.avatar.url}\n"
|
||||
f"|Date Sent={msg.created_at.strftime('%y-%m-%d %H:%M:%S')}\n"
|
||||
f"|Channel={msg.channel}\n"
|
||||
f"|Text={msg.content}\n"
|
||||
f"|Link={msg.jump_url}\n"
|
||||
"}}"
|
||||
)
|
||||
|
||||
|
||||
|
||||
template_message = TemplateMessage.from_source(
|
||||
title="Template:Message",
|
||||
source="""<noinclude>
|
||||
<pre>
|
||||
{{Message
|
||||
|Author=
|
||||
|Avatar=
|
||||
|Date Sent=
|
||||
|Channel=(Optional)
|
||||
|Text=
|
||||
|Link=
|
||||
}}
|
||||
</pre>
|
||||
</noinclude>
|
||||
<includeonly>
|
||||
|
||||
{{#subobject:{{{Author}}}-{{{Date Sent}}}
|
||||
|Message topic={{PAGENAME}}
|
||||
|Has author={{{Author}}}
|
||||
|Date sent={{{Date Sent}}}
|
||||
|Has URL={{{Link}}}
|
||||
|Contains text={{{Text}}}
|
||||
}}
|
||||
<div style="border: 1px solid black; border-radius: 5px;padding:5px"
|
||||
>
|
||||
<div style="display:flex; flex-direction:row; align-items:center; border-bottom:1px solid black; gap:10px; padding-bottom:5px;"><img src={{{Avatar|}}} style="width:30px;border-radius:10px"/><span style="font-weight:bold;">{{{Author}}}</span><span><nowiki>#</nowiki>{{{Channel|}}}</span><span style="font-style:italic;color:#999999">[{{{Link}}} {{{Date Sent}}}]</span></div>
|
||||
<div>
|
||||
{{{Text}}}
|
||||
</div>
|
||||
</div>
|
||||
</includeonly>
|
||||
"""
|
||||
)
|
Loading…
Reference in a new issue