mirror of
https://gitlab.com/octospacc/WinDog.git
synced 2025-06-05 22:09:20 +02:00
Initial NodeBB support; Add /wikipedia, /octospacc commands
This commit is contained in:
58
LibWinDog/Platforms/NodeBB/NodeBB.py
Normal file
58
LibWinDog/Platforms/NodeBB/NodeBB.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# ==================================== #
|
||||||
|
# WinDog multi-purpose chatbot #
|
||||||
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
|
# ==================================== #
|
||||||
|
|
||||||
|
""" # windog config start #
|
||||||
|
|
||||||
|
# NodeBBUrl = "https://nodebb.example.com"
|
||||||
|
# NodeBBToken = "abcdefgh-abcd-efgh-ijkl-mnopqrstuvwx"
|
||||||
|
|
||||||
|
# end windog config # """
|
||||||
|
|
||||||
|
import polling
|
||||||
|
|
||||||
|
NodeBBUrl = NodeBBToken = None
|
||||||
|
NodeBBStamps = {}
|
||||||
|
|
||||||
|
def nodebb_request(room_id:int='', method:str="GET", body:dict=None):
|
||||||
|
return json.loads(HttpReq(f"{NodeBBUrl}/api/v3/chats/{room_id}", method, headers={
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": f"Bearer {NodeBBToken}",
|
||||||
|
}, body=(body and json.dumps(body).encode())).read().decode())
|
||||||
|
|
||||||
|
def NodeBBMain(path:str) -> bool:
|
||||||
|
def handler():
|
||||||
|
try:
|
||||||
|
for room in nodebb_request()["response"]["rooms"]:
|
||||||
|
room_id = room["roomId"]
|
||||||
|
if "roomId" not in NodeBBStamps:
|
||||||
|
NodeBBStamps[room_id] = 0
|
||||||
|
if room["teaser"]["timestamp"] > NodeBBStamps[room_id]:
|
||||||
|
message = nodebb_request(room_id)["response"]["messages"][-1]
|
||||||
|
NodeBBStamps[room_id] = message["timestamp"]
|
||||||
|
if not message["self"]:
|
||||||
|
text_plain = BeautifulSoup(message["content"]).get_text()
|
||||||
|
data = InputMessageData(
|
||||||
|
# id = message["timestamp"],
|
||||||
|
text_html = message["content"],
|
||||||
|
text_plain = text_plain,
|
||||||
|
room = SafeNamespace(
|
||||||
|
id = room_id,
|
||||||
|
),
|
||||||
|
user = UserData(
|
||||||
|
settings = UserSettingsData(),
|
||||||
|
),
|
||||||
|
command = TextCommandData(text_plain, "nodebb"),
|
||||||
|
)
|
||||||
|
on_input_message_parsed(data)
|
||||||
|
call_endpoint(EventContext(platform="nodebb"), data)
|
||||||
|
except Exception:
|
||||||
|
app_log()
|
||||||
|
Thread(target=lambda:polling.poll(handler, step=3, poll_forever=True)).start()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def NodeBBSender(context:EventContext, data:OutputMessageData):
|
||||||
|
nodebb_request(context.data.room.id, "POST", {"message": data["text_plain"]})
|
||||||
|
|
||||||
|
register_platform(name="NodeBB", main=NodeBBMain, sender=NodeBBSender)
|
1
LibWinDog/Platforms/NodeBB/requirements.txt
Normal file
1
LibWinDog/Platforms/NodeBB/requirements.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
urllib3
|
@ -27,6 +27,12 @@ from hmac import new as hmac_new
|
|||||||
|
|
||||||
TelegramClient = None
|
TelegramClient = None
|
||||||
|
|
||||||
|
# def telegram_trim_message(text:str) -> str:
|
||||||
|
# return trim_message(text, 4096)
|
||||||
|
|
||||||
|
# def telegram_trim_caption(text:str) -> str:
|
||||||
|
# return trim_message(text, 1024)
|
||||||
|
|
||||||
def TelegramMain(path:str) -> bool:
|
def TelegramMain(path:str) -> bool:
|
||||||
if not TelegramToken:
|
if not TelegramToken:
|
||||||
return False
|
return False
|
||||||
|
@ -11,7 +11,7 @@ WebConfig = {
|
|||||||
"anti_drop_interval": 15,
|
"anti_drop_interval": 15,
|
||||||
}
|
}
|
||||||
|
|
||||||
WebTokens = {}
|
WebTokens = {} # Generate new tokens with secrets.token_urlsafe()
|
||||||
|
|
||||||
""" # end windog config # """
|
""" # end windog config # """
|
||||||
|
|
||||||
|
@ -42,7 +42,8 @@ def cPing(context:EventContext, data:InputMessageData):
|
|||||||
# nice experiment, but it won't work with Telegram since time is not to milliseconds (?)
|
# nice experiment, but it won't work with Telegram since time is not to milliseconds (?)
|
||||||
#time_diff = (time_now := int(time.time())) - (time_sent := data.datetime)
|
#time_diff = (time_now := int(time.time())) - (time_sent := data.datetime)
|
||||||
#send_message(context, OutputMessageData(text_html=f"<b>Pong!</b>\n\n{time_sent} → {time_now} = {time_diff}"))
|
#send_message(context, OutputMessageData(text_html=f"<b>Pong!</b>\n\n{time_sent} → {time_now} = {time_diff}"))
|
||||||
send_message(context, OutputMessageData(text_html="<b>Pong!</b>"))
|
word = (obj_get({"dick": "cock"}, data.command.name) or data.command.name.replace('i', 'o'))
|
||||||
|
send_message(context, OutputMessageData(text_html=f"<b>{word[0].upper()}{word[1:]}!</b>"))
|
||||||
|
|
||||||
#def cTime(update:Update, context:CallbackContext) -> None:
|
#def cTime(update:Update, context:CallbackContext) -> None:
|
||||||
# update.message.reply_markdown_v2(
|
# update.message.reply_markdown_v2(
|
||||||
@ -58,7 +59,7 @@ register_module(name="Base", endpoints=[
|
|||||||
"get": True,
|
"get": True,
|
||||||
}),
|
}),
|
||||||
#SafeNamespace(names=["gdpr"], summary="Operations for european citizens regarding your personal data.", handler=cGdpr),
|
#SafeNamespace(names=["gdpr"], summary="Operations for european citizens regarding your personal data.", handler=cGdpr),
|
||||||
SafeNamespace(names=["ping"], handler=cPing),
|
SafeNamespace(names=["ping", "bing", "ding", "dick"], handler=cPing),
|
||||||
#SafeNamespace(names=["eval"], summary="Execute a Python command (or safe literal operation) in the current context. Currently not implemented.", handler=cEval),
|
#SafeNamespace(names=["eval"], summary="Execute a Python command (or safe literal operation) in the current context. Currently not implemented.", handler=cEval),
|
||||||
#SafeNamespace(names=["format"], summary="Reformat text using an handful of rules. Not yet implemented.", handler=cFormat),
|
#SafeNamespace(names=["format"], summary="Reformat text using an handful of rules. Not yet implemented.", handler=cFormat),
|
||||||
#SafeNamespace(names=["frame"], summary="Frame someone's message into a platform-styled image. Not yet implemented.", handler=cFrame),
|
#SafeNamespace(names=["frame"], summary="Frame someone's message into a platform-styled image. Not yet implemented.", handler=cFrame),
|
||||||
|
@ -12,6 +12,7 @@ MicrosoftBingSettings = {}
|
|||||||
from urlextract import URLExtract
|
from urlextract import URLExtract
|
||||||
from urllib import parse as urlparse
|
from urllib import parse as urlparse
|
||||||
from urllib.request import urlopen, Request
|
from urllib.request import urlopen, Request
|
||||||
|
from translate_shell.translate import translate as ts_translate
|
||||||
|
|
||||||
def RandomHexString(length:int) -> str:
|
def RandomHexString(length:int) -> str:
|
||||||
return ''.join([randchoice('0123456789abcdef') for i in range(length)])
|
return ''.join([randchoice('0123456789abcdef') for i in range(length)])
|
||||||
@ -50,32 +51,71 @@ def cEmbedded(context:EventContext, data:InputMessageData):
|
|||||||
# elif urlDomain == "vm.tiktok.com":
|
# elif urlDomain == "vm.tiktok.com":
|
||||||
# urlDomain = "vm.vxtiktok.com"
|
# urlDomain = "vm.vxtiktok.com"
|
||||||
# url = (urlDomain + '/' + '/'.join(url.split('/')[1:]))
|
# url = (urlDomain + '/' + '/'.join(url.split('/')[1:]))
|
||||||
if urlDomain in ("facebook.com", "www.facebook.com", "m.facebook.com", "instagram.com", "www.instagram.com", "twitter.com", "x.com", "vm.tiktok.com", "tiktok.com", "www.tiktok.com"):
|
if urlDomain.startswith("www."):
|
||||||
|
urlDomain = '.'.join(urlDomain.split('.')[1:])
|
||||||
|
if urlDomain in ("facebook.com", "m.facebook.com", "instagram.com", "twitter.com", "x.com", "vm.tiktok.com", "tiktok.com", "threads.net", "threads.com"):
|
||||||
url = f"https://proxatore.octt.eu.org/{url}"
|
url = f"https://proxatore.octt.eu.org/{url}"
|
||||||
proto = ''
|
proto = ''
|
||||||
|
elif urlDomain in ("youtube.com",):
|
||||||
|
url = f"https://proxatore.octt.eu.org/{url}{'' if url.endswith('?') else '?'}&proxatore-htmlmedia=true&proxatore-mediaproxy=video"
|
||||||
|
proto = ''
|
||||||
return send_message(context, {"text_plain": f"{{{proto}{url}}}"})
|
return send_message(context, {"text_plain": f"{{{proto}{url}}}"})
|
||||||
return send_message(context, {"text_plain": "No links found."})
|
return send_message(context, {"text_plain": "No links found."})
|
||||||
|
|
||||||
|
def search_duckduckgo(query:str) -> dict:
|
||||||
|
url = f"https://html.duckduckgo.com/html?q={urlparse.quote(query)}"
|
||||||
|
request = HttpReq(url)
|
||||||
|
results = []
|
||||||
|
for line in request.read().decode().replace('\t', ' ').splitlines():
|
||||||
|
if ' class="result__a" ' in line and ' href="//duckduckgo.com/l/?uddg=' in line:
|
||||||
|
link = urlparse.unquote(line.split(' href="//duckduckgo.com/l/?uddg=')[1].split('&rut=')[0])
|
||||||
|
title = line.strip().split('</a>')[0].strip().split('</span>')[-1].strip().split('>')
|
||||||
|
if len(title) > 1:
|
||||||
|
results.append({"title": html_unescape(title[1].strip()), "link": link})
|
||||||
|
return results
|
||||||
|
|
||||||
|
def format_search_result(link:str, title:str, index:int) -> str:
|
||||||
|
return f'[{index + 1}] {title} : {{{link}}}\n\n'
|
||||||
|
|
||||||
def cWeb(context:EventContext, data:InputMessageData):
|
def cWeb(context:EventContext, data:InputMessageData):
|
||||||
|
language = data.user.settings.language
|
||||||
|
if not (query := data.command.body):
|
||||||
|
return send_status_400(context, language)
|
||||||
|
try:
|
||||||
|
text = f'🦆🔎 "{query}": https://duckduckgo.com/?q={urlparse.quote(query)}\n\n'
|
||||||
|
for i,e in enumerate(search_duckduckgo(query)):
|
||||||
|
text += format_search_result(e["link"], e["title"], i)
|
||||||
|
return send_message(context, {"text_plain": trim_text(text, 4096, True), "text_mode": "trim"})
|
||||||
|
except Exception:
|
||||||
|
return send_status_error(context, language)
|
||||||
|
|
||||||
|
def cWikipedia(context:EventContext, data:InputMessageData):
|
||||||
|
language = data.user.settings.language
|
||||||
|
if not (query := data.command.body):
|
||||||
|
return send_status_400(context, language)
|
||||||
|
try:
|
||||||
|
result = search_duckduckgo(f"site:wikipedia.org {query}")[0]
|
||||||
|
# TODO try to use API: https://*.wikipedia.org/w/api.php?action=parse&page={title}&prop=text&formatversion=2 (?)
|
||||||
|
soup = BeautifulSoup(HttpReq(result["link"]).read().decode(), "html.parser").select('#mw-content-text')[0]
|
||||||
|
if len(elems := soup.select('.infobox')):
|
||||||
|
elems[0].decompose()
|
||||||
|
for elem in soup.select('.mw-editsection'):
|
||||||
|
elem.decompose()
|
||||||
|
text = (f'{result["title"]}\n{{{result["link"]}}}\n\n' + soup.get_text().strip())
|
||||||
|
return send_message(context, {"text_plain": trim_text(text, 4096, True), "text_mode": "trim"})
|
||||||
|
except Exception:
|
||||||
|
return send_status_error(context, language)
|
||||||
|
|
||||||
|
def cFrittoMistoOctoSpacc(context:EventContext, data:InputMessageData):
|
||||||
language = data.user.settings.language
|
language = data.user.settings.language
|
||||||
if not (query := data.command.body):
|
if not (query := data.command.body):
|
||||||
return send_status_400(context, language)
|
return send_status_400(context, language)
|
||||||
try:
|
try:
|
||||||
query_url = urlparse.quote(query)
|
query_url = urlparse.quote(query)
|
||||||
request = HttpReq(f'https://html.duckduckgo.com/html?q={query_url}')
|
text = f'🍤🔎 "{query}": https://octospacc.altervista.org/?s={query_url}\n\n'
|
||||||
caption = f'🦆🔎 "{query}": https://duckduckgo.com/?q={query_url}\n\n'
|
for i,e in enumerate(json.loads(HttpReq(f"https://octospacc.altervista.org/wp-json/wp/v2/posts?search={query_url}").read().decode())):
|
||||||
index = 0
|
text += format_search_result(e["link"], (e["title"]["rendered"] or e["slug"]), i)
|
||||||
for line in request.read().decode().replace('\t', ' ').splitlines():
|
return send_message(context, {"text_html": trim_text(text, 4096, True), "text_mode": "trim"})
|
||||||
if ' class="result__a" ' in line and ' href="//duckduckgo.com/l/?uddg=' in line:
|
|
||||||
index += 1
|
|
||||||
link = urlparse.unquote(line.split(' href="//duckduckgo.com/l/?uddg=')[1].split('&rut=')[0])
|
|
||||||
title = line.strip().split('</a>')[0].strip().split('</span>')[-1].strip().split('>')
|
|
||||||
if len(title) > 1:
|
|
||||||
title = html_unescape(title[1].strip())
|
|
||||||
caption += f'[{index}] {title} : {{{link}}}\n\n'
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
return send_message(context, {"text_plain": f'{caption}...'})
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return send_status_error(context, language)
|
return send_status_error(context, language)
|
||||||
|
|
||||||
@ -87,14 +127,15 @@ def cNews(context:EventContext, data:InputMessageData):
|
|||||||
|
|
||||||
def cTranslate(context:EventContext, data:InputMessageData):
|
def cTranslate(context:EventContext, data:InputMessageData):
|
||||||
language = data.user.settings.language
|
language = data.user.settings.language
|
||||||
instances = ["lingva.ml", "lingva.lunar.icu"]
|
#instances = ["lingva.ml", "lingva.lunar.icu"]
|
||||||
language_to = data.command.arguments.language_to
|
language_to = data.command.arguments.language_to
|
||||||
text_input = (data.command.body or (data.quoted and data.quoted.text_plain))
|
text_input = (data.command.body or (data.quoted and data.quoted.text_plain))
|
||||||
if not (text_input and language_to):
|
if not (text_input and language_to):
|
||||||
return send_status_400(context, language)
|
return send_status_400(context, language)
|
||||||
try:
|
try:
|
||||||
result = json.loads(HttpReq(f'https://{randchoice(instances)}/api/v1/auto/{language_to}/{urlparse.quote(text_input)}').read())
|
#result = json.loads(HttpReq(f'https://{randchoice(instances)}/api/v1/auto/{language_to}/{urlparse.quote(text_input)}').read())
|
||||||
return send_message(context, {"text_plain": f"[{result['info']['detectedSource']} (auto) -> {language_to}]\n\n{result['translation']}"})
|
#return send_message(context, {"text_plain": f"[{result['info']['detectedSource']} (auto) -> {language_to}]\n\n{result['translation']}"})
|
||||||
|
return send_message(context, {"text_plain": f'[auto -> {language_to}]\n\n{ts_translate(text_input, language_to).results[0].paraphrase}'})
|
||||||
except Exception:
|
except Exception:
|
||||||
return send_status_error(context, language)
|
return send_status_error(context, language)
|
||||||
|
|
||||||
@ -146,8 +187,10 @@ def cSafebooru(context:EventContext, data:InputMessageData):
|
|||||||
return send_status_error(context, language)
|
return send_status_error(context, language)
|
||||||
|
|
||||||
register_module(name="Internet", endpoints=[
|
register_module(name="Internet", endpoints=[
|
||||||
SafeNamespace(names=["embedded"], handler=cEmbedded, body=False, quoted=False),
|
SafeNamespace(names=["embedded", "embed", "proxy", "proxatore", "sborratore"], handler=cEmbedded, body=False, quoted=False),
|
||||||
SafeNamespace(names=["web"], handler=cWeb, body=True),
|
SafeNamespace(names=["web", "search", "duck", "duckduckgo"], handler=cWeb, body=True),
|
||||||
|
SafeNamespace(names=["wikipedia", "wokipedia", "wiki"], handler=cWikipedia, body=True),
|
||||||
|
SafeNamespace(names=["frittomistodioctospacc", "fmos", "frittomisto", "octospacc"], handler=cFrittoMistoOctoSpacc, body=True),
|
||||||
SafeNamespace(names=["translate"], handler=cTranslate, body=False, quoted=False, arguments={
|
SafeNamespace(names=["translate"], handler=cTranslate, body=False, quoted=False, arguments={
|
||||||
"language_to": True,
|
"language_to": True,
|
||||||
"language_from": False,
|
"language_from": False,
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
urllib3
|
urllib3
|
||||||
urlextract
|
urlextract
|
||||||
|
translate-shell
|
80
WinDog.py
80
WinDog.py
@ -23,7 +23,7 @@ from LibWinDog.Database import *
|
|||||||
from LibWinDog.Utils import *
|
from LibWinDog.Utils import *
|
||||||
|
|
||||||
def app_log(text:str=None, level:str="?", *, newline:bool|None=None, inline:bool=False) -> None:
|
def app_log(text:str=None, level:str="?", *, newline:bool|None=None, inline:bool=False) -> None:
|
||||||
if not text:
|
if text == None:
|
||||||
text = get_exception_text(full=True)
|
text = get_exception_text(full=True)
|
||||||
endline = '\n'
|
endline = '\n'
|
||||||
if newline == False or (inline and newline == None):
|
if newline == False or (inline and newline == None):
|
||||||
@ -220,6 +220,15 @@ def send_status_error(context:EventContext, lang:str=None, code:int=500, extra:s
|
|||||||
app_log()
|
app_log()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def trim_text(text:str, limit:int, always_footer:bool=False) -> str:
|
||||||
|
ending = "…"
|
||||||
|
footer = "\n\n…"
|
||||||
|
if len(text) > limit:
|
||||||
|
text = (text[:(limit - len(ending + footer))].rstrip() + footer)
|
||||||
|
elif always_footer:
|
||||||
|
text = (text.rstrip() + footer)
|
||||||
|
return text
|
||||||
|
|
||||||
def get_link(context:EventContext, data:InputMessageData):
|
def get_link(context:EventContext, data:InputMessageData):
|
||||||
data = (InputMessageData(**data) if type(data) == dict else data)
|
data = (InputMessageData(**data) if type(data) == dict else data)
|
||||||
if (data.room and data.room.id):
|
if (data.room and data.room.id):
|
||||||
@ -374,42 +383,49 @@ def app_main() -> None:
|
|||||||
if platform.main(f"./LibWinDog/Platforms/{platform.name}"):
|
if platform.main(f"./LibWinDog/Platforms/{platform.name}"):
|
||||||
app_log(f"{platform.name}, ", inline=True)
|
app_log(f"{platform.name}, ", inline=True)
|
||||||
app_log("...Done. ✅️", inline=True, newline=True)
|
app_log("...Done. ✅️", inline=True, newline=True)
|
||||||
app_log("🐶️ WinDog Ready!")
|
|
||||||
while True:
|
|
||||||
time.sleep(9**9)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app_log("🌞️ WinDog Starting...")
|
app_log("🌞️ WinDog Starting...")
|
||||||
GlobalStrings = good_yaml_load(open("./WinDog.yaml", 'r').read())
|
try:
|
||||||
Platforms, Modules, ModuleGroups, Endpoints = {}, {}, {}, {}
|
GlobalStrings = good_yaml_load(open("./WinDog.yaml", 'r').read())
|
||||||
|
Platforms, Modules, ModuleGroups, Endpoints = {}, {}, {}, {}
|
||||||
|
|
||||||
for folder in ("LibWinDog/Platforms", "ModWinDog"):
|
for folder in ("LibWinDog/Platforms", "ModWinDog"):
|
||||||
match folder:
|
match folder:
|
||||||
case "LibWinDog/Platforms":
|
case "LibWinDog/Platforms":
|
||||||
app_log("📩️ Loading Platforms... ", newline=False)
|
app_log("📩️ Loading Platforms... ", newline=False)
|
||||||
case "ModWinDog":
|
case "ModWinDog":
|
||||||
app_log("🔩️ Loading Modules... ", newline=False)
|
app_log("🔩️ Loading Modules... ", newline=False)
|
||||||
for name in listdir(f"./{folder}"):
|
for name in listdir(f"./{folder}"):
|
||||||
path = f"./{folder}/{name}"
|
path = f"./{folder}/{name}"
|
||||||
if path.endswith(".py") and isfile(path):
|
if path.endswith(".py") and isfile(path):
|
||||||
exec(open(path).read())
|
exec(open(path).read())
|
||||||
elif isdir(path):
|
elif isdir(path):
|
||||||
files = listdir(path)
|
files = listdir(path)
|
||||||
if f"{name}.py" in files:
|
if f"{name}.py" in files:
|
||||||
files.remove(f"{name}.py")
|
files.remove(f"{name}.py")
|
||||||
exec(open(f"{path}/{name}.py", 'r').read())
|
exec(open(f"{path}/{name}.py", 'r').read())
|
||||||
#for file in files:
|
#for file in files:
|
||||||
# if file.endswith(".py"):
|
# if file.endswith(".py"):
|
||||||
# exec(open(f"{path}/{file}", 'r').read())
|
# exec(open(f"{path}/{file}", 'r').read())
|
||||||
app_log("...Done. ✅️", inline=True, newline=True)
|
app_log("...Done. ✅️", inline=True, newline=True)
|
||||||
|
|
||||||
app_log("💽️ Loading Configuration... ", newline=False)
|
app_log("💽️ Loading Configuration... ", newline=False)
|
||||||
if isfile("./Data/Config.py"):
|
if isfile("./Data/Config.py"):
|
||||||
exec(open("./Data/Config.py", 'r').read())
|
exec(open("./Data/Config.py", 'r').read())
|
||||||
else:
|
else:
|
||||||
write_new_config()
|
write_new_config()
|
||||||
app_log("Done. ✅️", inline=True, newline=True)
|
app_log("Done. ✅️", inline=True, newline=True)
|
||||||
|
|
||||||
app_main()
|
app_main()
|
||||||
|
except Exception:
|
||||||
|
app_log('')
|
||||||
|
app_log()
|
||||||
|
app_log("🛑 Error starting WinDog. Stopping...")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
app_log("🐶️ WinDog Ready!")
|
||||||
|
while True:
|
||||||
|
time.sleep(9**9)
|
||||||
app_log("🌚️ WinDog Stopping...")
|
app_log("🌚️ WinDog Stopping...")
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user