mirror of
https://gitlab.com/octospacc/WinDog.git
synced 2025-06-05 22:09:20 +02:00
More work on Matrix, move commands to new HTML locales, fix Mastodon
This commit is contained in:
0
Assets/ImageCreator-CodeOfConduct.png
Normal file → Executable file
0
Assets/ImageCreator-CodeOfConduct.png
Normal file → Executable file
Before Width: | Height: | Size: 839 KiB After Width: | Height: | Size: 839 KiB |
@ -22,29 +22,42 @@ def MastodonMain() -> bool:
|
|||||||
Mastodon = mastodon.Mastodon(api_base_url=MastodonUrl, access_token=MastodonToken)
|
Mastodon = mastodon.Mastodon(api_base_url=MastodonUrl, access_token=MastodonToken)
|
||||||
class MastodonListener(mastodon.StreamListener):
|
class MastodonListener(mastodon.StreamListener):
|
||||||
def on_notification(self, event):
|
def on_notification(self, event):
|
||||||
MastodonHandler(event)
|
MastodonHandler(event, Mastodon)
|
||||||
Mastodon.stream_user(MastodonListener(), run_async=True)
|
Mastodon.stream_user(MastodonListener(), run_async=True)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def MastodonHandler(event):
|
def MastodonMakeInputMessageData(status:dict) -> InputMessageData:
|
||||||
if event['type'] == 'mention':
|
data = InputMessageData(
|
||||||
#OnMessageParsed()
|
message_id = ("mastodon:" + strip_url_scheme(status["uri"])),
|
||||||
message = BeautifulSoup(event['status']['content'], 'html.parser').get_text(' ').strip().replace('\t', ' ')
|
text_html = status["content"],
|
||||||
if not message.split('@')[0]:
|
)
|
||||||
message = ' '.join('@'.join(message.split('@')[1:]).strip().split(' ')[1:]).strip()
|
data.text_plain = BeautifulSoup(data.text_html, "html.parser").get_text()
|
||||||
if message[0] in CmdPrefixes:
|
data.text_auto = GetWeightedText(data.text_html, data.text_plain)
|
||||||
command = ParseCmd(message)
|
command_tokens = data.text_plain.strip().replace("\t", " ").split(" ")
|
||||||
if command:
|
while command_tokens[0].strip().startswith('@') or not command_tokens[0]:
|
||||||
command.messageId = event['status']['id']
|
command_tokens.pop(0)
|
||||||
if command.Name in Endpoints:
|
data.command = ParseCommand(" ".join(command_tokens))
|
||||||
CallEndpoint(command.Name, EventContext(platform="mastodon", event=event, manager=Mastodon), command)
|
data.user = SafeNamespace(
|
||||||
|
id = ("mastodon:" + strip_url_scheme(status["account"]["uri"])),
|
||||||
|
name = status["account"]["display_name"],
|
||||||
|
)
|
||||||
|
data.user.settings = (GetUserSettings(data.user.id) or SafeNamespace())
|
||||||
|
return data
|
||||||
|
|
||||||
|
def MastodonHandler(event, Mastodon):
|
||||||
|
if event["type"] == "mention":
|
||||||
|
data = MastodonMakeInputMessageData(event["status"])
|
||||||
|
OnMessageParsed(data)
|
||||||
|
if (command := ObjGet(data, "command.name")):
|
||||||
|
CallEndpoint(command, EventContext(platform="mastodon", event=event, manager=Mastodon), data)
|
||||||
|
|
||||||
def MastodonSender(context:EventContext, data:OutputMessageData, destination) -> None:
|
def MastodonSender(context:EventContext, data:OutputMessageData, destination) -> None:
|
||||||
media_results = None
|
media_results = None
|
||||||
if data.media:
|
if data.media:
|
||||||
media_results = []
|
media_results = []
|
||||||
for medium in data.media[:4]: # Mastodon limits posts to 4 attachments
|
# TODO support media by url (do we have to upload them or can just pass the original URL?)
|
||||||
medium_result = context.manager.media_post(medium, Magic(mime=True).from_buffer(medium))
|
for medium in data.media[:4]: # Mastodon limits posts to 4 attachments, so we drop any more
|
||||||
|
medium_result = context.manager.media_post(medium["bytes"], Magic(mime=True).from_buffer(medium["bytes"]))
|
||||||
while medium_result["url"] == "null":
|
while medium_result["url"] == "null":
|
||||||
medium_result = context.manager.media(medium_result)
|
medium_result = context.manager.media(medium_result)
|
||||||
media_results.append(medium_result)
|
media_results.append(medium_result)
|
||||||
|
@ -7,30 +7,64 @@
|
|||||||
|
|
||||||
# MatrixUrl = "https://matrix.example.com"
|
# MatrixUrl = "https://matrix.example.com"
|
||||||
# MatrixUsername = "username"
|
# MatrixUsername = "username"
|
||||||
|
|
||||||
|
# Provide either your password, or an active access_token below.
|
||||||
# MatrixPassword = "hunter2"
|
# MatrixPassword = "hunter2"
|
||||||
|
|
||||||
|
# If logging in via password, a token will be automatically generated and saved to Config.
|
||||||
|
# MatrixToken = ""
|
||||||
|
|
||||||
# end windog config # """
|
# end windog config # """
|
||||||
|
|
||||||
MatrixUrl, MatrixUsername, MatrixPassword = None, None, None
|
MatrixUrl, MatrixUsername, MatrixPassword, MatrixToken = None, None, None, None
|
||||||
|
|
||||||
|
from asyncio import run as asyncio_run
|
||||||
import nio
|
import nio
|
||||||
import simplematrixbotlib as MatrixBotLib
|
#from nio import AsyncClient, MatrixRoom, RoomMessageText
|
||||||
from threading import Thread
|
#import simplematrixbotlib as MatrixBotLib
|
||||||
|
|
||||||
|
async def MatrixMessageHandler(room:nio.MatrixRoom, event:nio.RoomMessage) -> None:
|
||||||
|
data = MatrixMakeInputMessageData(room, event)
|
||||||
|
|
||||||
|
def MatrixMakeInputMessageData(room:nio.MatrixRoom, event:nio.RoomMessage) -> InputMessageData:
|
||||||
|
data = InputMessageData(
|
||||||
|
message_id = f"matrix:{event.event_id}",
|
||||||
|
room = SafeNamespace(
|
||||||
|
id = f"matrix:{room.room_id}",
|
||||||
|
name = room.display_name,
|
||||||
|
),
|
||||||
|
user = SafeNamespace(
|
||||||
|
id = f"matrix:{event.sender}",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
print(data)
|
||||||
|
return data
|
||||||
|
|
||||||
def MatrixMain() -> bool:
|
def MatrixMain() -> bool:
|
||||||
if not (MatrixUrl and MatrixUsername and MatrixPassword):
|
if not (MatrixUrl and MatrixUsername and (MatrixPassword or MatrixToken)):
|
||||||
return False
|
return False
|
||||||
MatrixBot = MatrixBotLib.Bot(MatrixBotLib.Creds(MatrixUrl, MatrixUsername, MatrixPassword))
|
#MatrixBot = MatrixBotLib.Bot(MatrixBotLib.Creds(MatrixUrl, MatrixUsername, MatrixPassword))
|
||||||
@MatrixBot.listener.on_message_event
|
##@MatrixBot.listener.on_message_event
|
||||||
@MatrixBot.listener.on_custom_event(nio.events.room_events.RoomMessageFile)
|
#@MatrixBot.listener.on_custom_event(nio.RoomMessageText)
|
||||||
async def MatrixMessageListener(room, message) -> None:
|
#async def MatrixMessageListener(room, message, event) -> None:
|
||||||
pass
|
|
||||||
# print(message)
|
# print(message)
|
||||||
#match = MatrixBotLib.MessageMatch(room, message, MatrixBot)
|
# #match = MatrixBotLib.MessageMatch(room, message, MatrixBot)
|
||||||
#OnMessageParsed()
|
# #OnMessageParsed()
|
||||||
#if match.is_not_from_this_bot() and match.command("windogtest"):
|
# #if match.is_not_from_this_bot() and match.command("windogtest"):
|
||||||
# pass #await MatrixBot.api.send_text_message(room.room_id, " ".join(arg for arg in match.args()))
|
# # pass #await MatrixBot.api.send_text_message(room.room_id, " ".join(arg for arg in match.args()))
|
||||||
Thread(target=lambda:MatrixBot.run()).start()
|
#@MatrixBot.listener.on_custom_event(nio.RoomMessageFile)
|
||||||
|
#async def MatrixMessageFileListener(room, event):
|
||||||
|
# print(event)
|
||||||
|
#Thread(target=lambda:MatrixBot.run()).start()
|
||||||
|
async def client() -> None:
|
||||||
|
client = nio.AsyncClient(MatrixUrl, MatrixUsername)
|
||||||
|
login = await client.login(password=MatrixPassword, token=MatrixToken)
|
||||||
|
if MatrixPassword and (not MatrixToken) and (token := ObjGet(login, "access_token")):
|
||||||
|
open("./Config.py", 'a').write(f'\n# Added automatically #\nMatrixToken = "{token}"\n')
|
||||||
|
await client.sync(30000) # resync old messages first to "skip read ones"
|
||||||
|
client.add_event_callback(MatrixMessageHandler, nio.RoomMessage)
|
||||||
|
await client.sync_forever(timeout=30000)
|
||||||
|
Thread(target=lambda:asyncio_run(client())).start()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def MatrixSender() -> None:
|
def MatrixSender() -> None:
|
||||||
|
@ -1 +1,2 @@
|
|||||||
|
matrix-nio
|
||||||
simplematrixbotlib
|
simplematrixbotlib
|
||||||
|
@ -23,7 +23,7 @@ def TelegramMain() -> bool:
|
|||||||
return False
|
return False
|
||||||
updater = telegram.ext.Updater(TelegramToken)
|
updater = telegram.ext.Updater(TelegramToken)
|
||||||
dispatcher = updater.dispatcher
|
dispatcher = updater.dispatcher
|
||||||
dispatcher.add_handler(MessageHandler(Filters.text | Filters.command, TelegramHandlerWrapper))
|
dispatcher.add_handler(MessageHandler(Filters.text | Filters.command, TelegramHandler))
|
||||||
updater.start_polling()
|
updater.start_polling()
|
||||||
#app = Application.builder().token(TelegramToken).build()
|
#app = Application.builder().token(TelegramToken).build()
|
||||||
#app.add_handler(MessageHandler(filters.TEXT | filters.COMMAND, TelegramHandler))
|
#app.add_handler(MessageHandler(filters.TEXT | filters.COMMAND, TelegramHandler))
|
||||||
@ -40,60 +40,33 @@ def TelegramMakeInputMessageData(message:telegram.Message) -> InputMessageData:
|
|||||||
)
|
)
|
||||||
data.text_auto = GetWeightedText(data.text_markdown, data.text_plain)
|
data.text_auto = GetWeightedText(data.text_markdown, data.text_plain)
|
||||||
data.command = ParseCommand(data.text_plain)
|
data.command = ParseCommand(data.text_plain)
|
||||||
data.room = SafeNamespace(
|
|
||||||
id = f"telegram:{message.chat.id}",
|
|
||||||
tag = message.chat.username,
|
|
||||||
name = (message.chat.title or message.chat.first_name),
|
|
||||||
)
|
|
||||||
data.user = SafeNamespace(
|
data.user = SafeNamespace(
|
||||||
id = f"telegram:{message.from_user.id}",
|
id = f"telegram:{message.from_user.id}",
|
||||||
tag = message.from_user.username,
|
tag = message.from_user.username,
|
||||||
name = message.from_user.first_name,
|
name = message.from_user.first_name,
|
||||||
)
|
)
|
||||||
data.user.settings = GetUserSettings(data.user.id)
|
data.user.settings = (GetUserSettings(data.user.id) or SafeNamespace())
|
||||||
|
data.room = SafeNamespace(
|
||||||
|
id = f"telegram:{message.chat.id}",
|
||||||
|
tag = message.chat.username,
|
||||||
|
name = (message.chat.title or message.chat.first_name),
|
||||||
|
)
|
||||||
|
linked = TelegramLinker(data)
|
||||||
|
data.message_url = linked.message
|
||||||
|
data.room.url = linked.room
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def TelegramHandlerWrapper(update:telegram.Update, context:CallbackContext=None) -> None:
|
def TelegramHandler(update:telegram.Update, context:CallbackContext=None) -> None:
|
||||||
Thread(target=lambda:TelegramHandlerCore(update, context)).start()
|
def handler() -> None:
|
||||||
|
|
||||||
def TelegramHandlerCore(update:telegram.Update, context:CallbackContext=None) -> None:
|
|
||||||
if not update.message:
|
if not update.message:
|
||||||
return
|
return
|
||||||
data = TelegramMakeInputMessageData(update.message)
|
data = TelegramMakeInputMessageData(update.message)
|
||||||
if update.message.reply_to_message:
|
if (quoted := update.message.reply_to_message):
|
||||||
data.quoted = TelegramMakeInputMessageData(update.message.reply_to_message)
|
data.quoted = TelegramMakeInputMessageData(quoted)
|
||||||
OnMessageParsed(data)
|
OnMessageParsed(data)
|
||||||
cmd = ParseCmd(update.message.text)
|
if (command := ObjGet(data, "command.name")):
|
||||||
if cmd:
|
CallEndpoint(command, EventContext(platform="telegram", event=update, manager=context), data)
|
||||||
# TODO remove old cmd and just pass the data object
|
Thread(target=handler).start()
|
||||||
cmd.command = data.command
|
|
||||||
cmd.quoted = data.quoted
|
|
||||||
cmd.user = data.user
|
|
||||||
cmd.message_id = data.message_id
|
|
||||||
cmd.messageId = update.message.message_id
|
|
||||||
cmd.TextPlain = cmd.Body
|
|
||||||
cmd.TextMarkdown = update.message.text_markdown_v2
|
|
||||||
cmd.Text = GetWeightedText(cmd.TextMarkdown, cmd.TextPlain)
|
|
||||||
if cmd.Tokens[0][0] in CmdPrefixes and cmd.Name in Endpoints:
|
|
||||||
cmd.User = SimpleNamespace(**{
|
|
||||||
"Name": update.message.from_user.first_name,
|
|
||||||
"Tag": update.message.from_user.username,
|
|
||||||
"Id": f'telegram:{update.message.from_user.id}',
|
|
||||||
})
|
|
||||||
if update.message.reply_to_message:
|
|
||||||
cmd.Quoted = SimpleNamespace(**{
|
|
||||||
"messageId": update.message.reply_to_message.message_id,
|
|
||||||
"Body": update.message.reply_to_message.text,
|
|
||||||
"TextPlain": update.message.reply_to_message.text,
|
|
||||||
"TextMarkdown": update.message.reply_to_message.text_markdown_v2,
|
|
||||||
"Text": GetWeightedText(update.message.reply_to_message.text_markdown_v2, update.message.reply_to_message.text),
|
|
||||||
"User": SimpleNamespace(**{
|
|
||||||
"Name": update.message.reply_to_message.from_user.first_name,
|
|
||||||
"Tag": update.message.reply_to_message.from_user.username,
|
|
||||||
"Id": f'telegram:{update.message.reply_to_message.from_user.id}',
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
CallEndpoint(cmd.Name, EventContext(platform="telegram", event=update, manager=context), cmd)
|
|
||||||
|
|
||||||
def TelegramSender(context:EventContext, data:OutputMessageData, destination):
|
def TelegramSender(context:EventContext, data:OutputMessageData, destination):
|
||||||
result = None
|
result = None
|
||||||
@ -104,7 +77,7 @@ def TelegramSender(context:EventContext, data:OutputMessageData, destination):
|
|||||||
if data.media:
|
if data.media:
|
||||||
for medium in data.media:
|
for medium in data.media:
|
||||||
result = context.event.message.reply_photo(
|
result = context.event.message.reply_photo(
|
||||||
(DictGet(medium, "bytes") or DictGet(medium, "url")),
|
(ObjGet(medium, "bytes") or ObjGet(medium, "url")),
|
||||||
caption=(data.text_html or data.text_markdown or data.text_plain),
|
caption=(data.text_html or data.text_markdown or data.text_plain),
|
||||||
parse_mode=("HTML" if data.text_html else "MarkdownV2" if data.text_markdown else None),
|
parse_mode=("HTML" if data.text_html else "MarkdownV2" if data.text_markdown else None),
|
||||||
reply_to_message_id=replyToId)
|
reply_to_message_id=replyToId)
|
||||||
@ -116,14 +89,17 @@ def TelegramSender(context:EventContext, data:OutputMessageData, destination):
|
|||||||
result = context.event.message.reply_text(data.text_plain, reply_to_message_id=replyToId)
|
result = context.event.message.reply_text(data.text_plain, reply_to_message_id=replyToId)
|
||||||
return TelegramMakeInputMessageData(result)
|
return TelegramMakeInputMessageData(result)
|
||||||
|
|
||||||
|
# TODO support usernames
|
||||||
def TelegramLinker(data:InputMessageData) -> SafeNamespace:
|
def TelegramLinker(data:InputMessageData) -> SafeNamespace:
|
||||||
linked = SafeNamespace()
|
linked = SafeNamespace()
|
||||||
if data.room.id:
|
if data.room.id:
|
||||||
room_id = data.room.id.split("telegram:")[1]
|
# prefix must be dropped for groups and channels, while direct chats apparently can never be linked
|
||||||
linked.room = f"https://t.me/{room_id}"
|
if (room_id := "100".join(data.room.id.split("telegram:")[1].split("100")[1:])):
|
||||||
|
# apparently Telegram doesn't really support links to rooms by id without a message id, so we just use a null one
|
||||||
|
linked.room = f"https://t.me/c/{room_id}/0"
|
||||||
if data.message_id:
|
if data.message_id:
|
||||||
message_id = data.message_id.split("telegram:")[1]
|
message_id = data.message_id.split("telegram:")[1]
|
||||||
linked.message = f"{linked.room}/{message_id}"
|
linked.message = f"https://t.me/c/{room_id}/{message_id}"
|
||||||
return linked
|
return linked
|
||||||
|
|
||||||
RegisterPlatform(name="Telegram", main=TelegramMain, sender=TelegramSender, linker=TelegramLinker, eventClass=telegram.Update)
|
RegisterPlatform(name="Telegram", main=TelegramMain, sender=TelegramSender, linker=TelegramLinker, eventClass=telegram.Update)
|
||||||
|
0
LibWinDog/Types.py
Normal file → Executable file
0
LibWinDog/Types.py
Normal file → Executable file
@ -1,15 +1,7 @@
|
|||||||
{
|
{
|
||||||
"start": [
|
|
||||||
"*Hi* {0}*!*\n\nUse /help to read a list of available commands."
|
|
||||||
],
|
|
||||||
"help": [
|
"help": [
|
||||||
"*There's no one around to help (yet).*"
|
"*There's no one around to help (yet).*"
|
||||||
],
|
],
|
||||||
"echo": {
|
|
||||||
"empty": [
|
|
||||||
"*Echo what? Give me something to repeat.*"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"wish": {
|
"wish": {
|
||||||
"empty": [
|
"empty": [
|
||||||
"*You wished for nothing! ✨*\n\n_Nothing happens..._"
|
"*You wished for nothing! ✨*\n\n_Nothing happens..._"
|
||||||
|
@ -1,15 +1,7 @@
|
|||||||
{
|
{
|
||||||
"start": [
|
|
||||||
"*Ciao* {0}*!*\n\nUsa /help per leggere la lista dei comandi."
|
|
||||||
],
|
|
||||||
"help": [
|
"help": [
|
||||||
"*Non c'è nessuno qui ad aiutarti (per ora).*"
|
"*Non c'è nessuno qui ad aiutarti (per ora).*"
|
||||||
],
|
],
|
||||||
"echo": {
|
|
||||||
"empty": [
|
|
||||||
"*Echo cosa? Dimmi qualcosa da ripetere.*"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"wish": {
|
"wish": {
|
||||||
"empty": [
|
"empty": [
|
||||||
"*Non hai desiderato nulla! ✨*\n\n_Non succede niente..._"
|
"*Non hai desiderato nulla! ✨*\n\n_Non succede niente..._"
|
||||||
|
@ -3,11 +3,6 @@
|
|||||||
# Licensed under AGPLv3 by OctoSpacc #
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
# ==================================== #
|
# ==================================== #
|
||||||
|
|
||||||
import re, subprocess
|
|
||||||
|
|
||||||
def cStart(context:EventContext, data:InputMessageData) -> None:
|
|
||||||
SendMessage(context, {"Text": choice(Locale.__('start')).format(data.User.Name)})
|
|
||||||
|
|
||||||
def cSource(context:EventContext, data:InputMessageData) -> None:
|
def cSource(context:EventContext, data:InputMessageData) -> None:
|
||||||
SendMessage(context, {"TextPlain": ("""\
|
SendMessage(context, {"TextPlain": ("""\
|
||||||
* Original Code: {https://gitlab.com/octospacc/WinDog}
|
* Original Code: {https://gitlab.com/octospacc/WinDog}
|
||||||
@ -20,8 +15,12 @@ def cGdpr(context:EventContext, data:InputMessageData) -> None:
|
|||||||
def cConfig(context:EventContext, data:InputMessageData) -> None:
|
def cConfig(context:EventContext, data:InputMessageData) -> None:
|
||||||
if not (settings := GetUserSettings(data.user.id)):
|
if not (settings := GetUserSettings(data.user.id)):
|
||||||
User.update(settings=EntitySettings.create()).where(User.id == data.user.id).execute()
|
User.update(settings=EntitySettings.create()).where(User.id == data.user.id).execute()
|
||||||
if (get := ObjGet(data, "command.arguments.get")):
|
if (to_set := ObjGet(data, "command.arguments.set")):
|
||||||
SendMessage(context, OutputMessageData(text_plain=str(ObjGet(data.user.settings, get))))
|
pass # TODO set in db, but first we need to ensure data is handled safely
|
||||||
|
if (to_get := ObjGet(data, "command.arguments.get")):
|
||||||
|
# TODO show a hint on possible options?
|
||||||
|
return SendMessage(context, OutputMessageData(text_plain=str(ObjGet(data.user.settings, to_get))))
|
||||||
|
# TODO show general help when no useful parameters are passed
|
||||||
#Cmd = TelegramHandleCmd(update)
|
#Cmd = TelegramHandleCmd(update)
|
||||||
#if not Cmd: return
|
#if not Cmd: return
|
||||||
# ... area: eu, us, ...
|
# ... area: eu, us, ...
|
||||||
@ -39,30 +38,14 @@ def cPing(context:EventContext, data:InputMessageData) -> None:
|
|||||||
def cEval(context:EventContext, data:InputMessageData) -> None:
|
def cEval(context:EventContext, data:InputMessageData) -> None:
|
||||||
SendMessage(context, {"Text": choice(Locale.__('eval'))})
|
SendMessage(context, {"Text": choice(Locale.__('eval'))})
|
||||||
|
|
||||||
def cExec(context:EventContext, data:InputMessageData) -> None:
|
RegisterModule(name="Base", endpoints=[
|
||||||
if len(data.Tokens) >= 2 and data.Tokens[1].lower() in ExecAllowed:
|
|
||||||
cmd = data.Tokens[1].lower()
|
|
||||||
Out = subprocess.run(('sh', '-c', f'export PATH=$PATH:/usr/games; {cmd}'),
|
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.decode()
|
|
||||||
# <https://stackoverflow.com/a/14693789>
|
|
||||||
Caption = (re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])').sub('', Out))
|
|
||||||
SendMessage(context, {
|
|
||||||
"TextPlain": Caption,
|
|
||||||
"TextMarkdown": MarkdownCode(Caption, True),
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
SendMessage(context, {"Text": choice(Locale.__('eval'))})
|
|
||||||
|
|
||||||
RegisterModule(name="Misc", endpoints=[
|
|
||||||
SafeNamespace(names=["start"], summary="Salutes the user, hinting that the bot is working and providing basic quick help.", handler=cStart),
|
|
||||||
SafeNamespace(names=["source"], summary="Provides a copy of the bot source codes and/or instructions on how to get it.", handler=cSource),
|
SafeNamespace(names=["source"], summary="Provides a copy of the bot source codes and/or instructions on how to get it.", handler=cSource),
|
||||||
SafeNamespace(names=["config"], handler=cConfig, arguments={
|
SafeNamespace(names=["config"], handler=cConfig, arguments={
|
||||||
"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"], summary="Responds pong, useful for testing messaging latency.", handler=cPing),
|
SafeNamespace(names=["ping"], summary="Responds pong, useful for testing messaging latency.", 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=["exec"], summary="Execute a system command from the allowed ones and return stdout+stderr.", handler=cExec),
|
|
||||||
#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),
|
||||||
#SafeNamespace(names=["repeat"], summary="I had this planned but I don't remember what this should have done. Not yet implemented.", handler=cRepeat),
|
#SafeNamespace(names=["repeat"], summary="I had this planned but I don't remember what this should have done. Not yet implemented.", handler=cRepeat),
|
||||||
|
0
ModWinDog/Dumper/Dumper.yaml
Normal file → Executable file
0
ModWinDog/Dumper/Dumper.yaml
Normal file → Executable file
14
ModWinDog/Echo/Echo.py
Normal file → Executable file
14
ModWinDog/Echo/Echo.py
Normal file → Executable file
@ -4,22 +4,20 @@
|
|||||||
# ==================================== #
|
# ==================================== #
|
||||||
|
|
||||||
def cEcho(context:EventContext, data:InputMessageData) -> None:
|
def cEcho(context:EventContext, data:InputMessageData) -> None:
|
||||||
text = ObjGet(data, "command.body")
|
if not (text := ObjGet(data, "command.body")):
|
||||||
if text:
|
return SendMessage(context, OutputMessageData(text_html=context.endpoint.get_string("empty", data.user.settings.language)))
|
||||||
prefix = "🗣️ "
|
prefix = f'<a href="{data.message_url}">🗣️</a> '
|
||||||
#prefix = f"[🗣️]({context.linker(data).message}) "
|
#prefix = f"[🗣️]({context.linker(data).message}) "
|
||||||
if len(data.Tokens) == 2:
|
if len(data.command.tokens) == 2:
|
||||||
nonascii = True
|
nonascii = True
|
||||||
for char in data.Tokens[1]:
|
for char in data.command.tokens[1]:
|
||||||
if ord(char) < 256:
|
if ord(char) < 256:
|
||||||
nonascii = False
|
nonascii = False
|
||||||
break
|
break
|
||||||
if nonascii:
|
if nonascii:
|
||||||
# text is not ascii, probably an emoji (altough not necessarily), so just pass as is (useful for Telegram emojis)
|
# text is not ascii, probably an emoji (altough not necessarily), so just pass as is (useful for Telegram emojis)
|
||||||
prefix = ''
|
prefix = ''
|
||||||
SendMessage(context, OutputMessageData(text=(prefix + text)))
|
SendMessage(context, OutputMessageData(text_html=(prefix + html_escape(text))))
|
||||||
else:
|
|
||||||
SendMessage(context, OutputMessageData(text_html=context.endpoint.get_string('empty')))
|
|
||||||
|
|
||||||
RegisterModule(name="Echo", endpoints=[
|
RegisterModule(name="Echo", endpoints=[
|
||||||
SafeNamespace(names=["echo"], handler=cEcho),
|
SafeNamespace(names=["echo"], handler=cEcho),
|
||||||
|
2
ModWinDog/Echo/Echo.yaml
Normal file → Executable file
2
ModWinDog/Echo/Echo.yaml
Normal file → Executable file
@ -1,3 +1,5 @@
|
|||||||
|
summary:
|
||||||
|
en: Tools for repeating messages.
|
||||||
endpoints:
|
endpoints:
|
||||||
echo:
|
echo:
|
||||||
summary:
|
summary:
|
||||||
|
4
ModWinDog/GPT/GPT.py
Normal file → Executable file
4
ModWinDog/GPT/GPT.py
Normal file → Executable file
@ -8,12 +8,12 @@ from g4f.client import Client as G4FClient
|
|||||||
g4fClient = G4FClient()
|
g4fClient = G4FClient()
|
||||||
|
|
||||||
def cGpt(context:EventContext, data:InputMessageData) -> None:
|
def cGpt(context:EventContext, data:InputMessageData) -> None:
|
||||||
if not data.command.body:
|
if not (prompt := data.command.body):
|
||||||
return SendMessage(context, {"Text": "You must type some text."})
|
return SendMessage(context, {"Text": "You must type some text."})
|
||||||
output = None
|
output = None
|
||||||
while not output or output.startswith("sorry, 您的ip已由于触发防滥用检测而被封禁,本服务网址是"): # quick fix for a strange ratelimit message
|
while not output or output.startswith("sorry, 您的ip已由于触发防滥用检测而被封禁,本服务网址是"): # quick fix for a strange ratelimit message
|
||||||
output = ""
|
output = ""
|
||||||
for completion in g4fClient.chat.completions.create(model="gpt-3.5-turbo", messages=[{"role": "user", "content": data.command.body}], stream=True):
|
for completion in g4fClient.chat.completions.create(model="gpt-3.5-turbo", messages=[{"role": "user", "content": prompt}], stream=True):
|
||||||
output += (completion.choices[0].delta.content or "")
|
output += (completion.choices[0].delta.content or "")
|
||||||
return SendMessage(context, {"TextPlain": f"[🤖️ GPT]\n\n{output}"})
|
return SendMessage(context, {"TextPlain": f"[🤖️ GPT]\n\n{output}"})
|
||||||
|
|
||||||
|
0
ModWinDog/GPT/requirements.txt
Normal file → Executable file
0
ModWinDog/GPT/requirements.txt
Normal file → Executable file
@ -11,13 +11,10 @@ def cHash(context:EventContext, data:InputMessageData):
|
|||||||
if not (text_input and (algorithm in hashlib.algorithms_available)):
|
if not (text_input and (algorithm in hashlib.algorithms_available)):
|
||||||
return SendMessage(context, {"Text": choice(Locale.__('hash.usage')).format(data.command.tokens[0], hashlib.algorithms_available)})
|
return SendMessage(context, {"Text": choice(Locale.__('hash.usage')).format(data.command.tokens[0], hashlib.algorithms_available)})
|
||||||
hashed = hashlib.new(algorithm, text_input.encode()).hexdigest()
|
hashed = hashlib.new(algorithm, text_input.encode()).hexdigest()
|
||||||
return SendMessage(context, {
|
return SendMessage(context, OutputMessageData(text_plain=hashed, text_html=f"<pre>{hashed}</pre>"))
|
||||||
"TextPlain": hashed,
|
|
||||||
"TextMarkdown": MarkdownCode(hashed, True),
|
|
||||||
})
|
|
||||||
|
|
||||||
RegisterModule(name="Hashing", group="Geek", summary="Functions for hashing of textual content.", endpoints=[
|
RegisterModule(name="Hashing", group="Geek", endpoints=[
|
||||||
SafeNamespace(names=["hash"], summary="Responds with the hash-sum of a message received.", handler=cHash, arguments={
|
SafeNamespace(names=["hash"], handler=cHash, arguments={
|
||||||
"algorithm": True,
|
"algorithm": True,
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
|
1
ModWinDog/Hashing/Hashing.yaml
Normal file → Executable file
1
ModWinDog/Hashing/Hashing.yaml
Normal file → Executable file
@ -1,6 +1,7 @@
|
|||||||
summary:
|
summary:
|
||||||
en: Functions for calculating hashes of content.
|
en: Functions for calculating hashes of content.
|
||||||
it: Funzioni per calcolare hash di contenuti.
|
it: Funzioni per calcolare hash di contenuti.
|
||||||
|
endpoints:
|
||||||
hash:
|
hash:
|
||||||
summary:
|
summary:
|
||||||
en: Responds with the hash-sum of the received message.
|
en: Responds with the hash-sum of the received message.
|
||||||
|
@ -5,17 +5,18 @@
|
|||||||
|
|
||||||
# TODO: implement /help <commandname> feature
|
# TODO: implement /help <commandname> feature
|
||||||
def cHelp(context:EventContext, data:InputMessageData) -> None:
|
def cHelp(context:EventContext, data:InputMessageData) -> None:
|
||||||
moduleList = ''
|
module_list = ''
|
||||||
|
language = data.user.settings.language
|
||||||
for module in Modules:
|
for module in Modules:
|
||||||
summary = Modules[module].summary
|
summary = Modules[module].get_string("summary", language)#summary
|
||||||
endpoints = Modules[module].endpoints
|
endpoints = Modules[module].endpoints
|
||||||
moduleList += (f"\n\n{module}" + (f": {summary}" if summary else ''))
|
module_list += (f"\n\n{module}" + (f": {summary}" if summary else ''))
|
||||||
for endpoint in endpoints:
|
for endpoint in endpoints:
|
||||||
summary = endpoint.summary
|
summary = Modules[module].get_string(f"endpoints.{endpoint.names[0]}.summary", language)
|
||||||
moduleList += (f"\n* /{', /'.join(endpoint.names)}" + (f": {summary}" if summary else ''))
|
module_list += (f"\n* /{', /'.join(endpoint.names)}" + (f": {summary}" if summary else ''))
|
||||||
SendMessage(context, {"Text": f"[ Available Modules ]{moduleList}"})
|
SendMessage(context, OutputMessageData(text=module_list))
|
||||||
|
|
||||||
RegisterModule(name="Help", group="Basic", endpoints=[
|
RegisterModule(name="Help", group="Basic", endpoints=[
|
||||||
SafeNamespace(names=["help"], summary="Provides help for the bot. For now, it just lists the commands.", handler=cHelp),
|
SafeNamespace(names=["help"], handler=cHelp),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
5
ModWinDog/Help/Help.yaml
Normal file
5
ModWinDog/Help/Help.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
endpoints:
|
||||||
|
help:
|
||||||
|
summary:
|
||||||
|
en: Provides help for the bot. For now, it just lists the commands.
|
||||||
|
|
@ -10,19 +10,18 @@ MicrosoftBingSettings = {}
|
|||||||
""" # end windog config # """
|
""" # end windog config # """
|
||||||
|
|
||||||
from urlextract import URLExtract
|
from urlextract import URLExtract
|
||||||
from urllib import parse as UrlParse
|
|
||||||
from urllib.request import urlopen, Request
|
from urllib.request import urlopen, Request
|
||||||
|
|
||||||
def HttpReq(url:str, method:str|None=None, *, body:bytes=None, headers:dict[str, str]={"User-Agent": WebUserAgent}):
|
def HttpReq(url:str, method:str|None=None, *, body:bytes=None, headers:dict[str, str]={"User-Agent": WebUserAgent}):
|
||||||
return urlopen(Request(url, method=method, data=body, headers=headers))
|
return urlopen(Request(url, method=method, data=body, headers=headers))
|
||||||
|
|
||||||
def cEmbedded(context:EventContext, data:InputMessageData) -> None:
|
def cEmbedded(context:EventContext, data:InputMessageData) -> None:
|
||||||
if len(data.Tokens) >= 2:
|
if len(data.command.tokens) >= 2:
|
||||||
# Find links in command body
|
# Find links in command body
|
||||||
Text = (data.TextMarkdown + ' ' + data.TextPlain)
|
Text = (data.text_markdown + ' ' + data.text_plain)
|
||||||
elif data.Quoted and data.Quoted.Text:
|
elif data.quoted and data.quoted.text_auto:
|
||||||
# Find links in quoted message
|
# Find links in quoted message
|
||||||
Text = (data.Quoted.TextMarkdown + ' ' + data.Quoted.TextPlain)
|
Text = (data.quoted.text_markdown + ' ' + data.quoted.text_plain)
|
||||||
else:
|
else:
|
||||||
# TODO Error message
|
# TODO Error message
|
||||||
return
|
return
|
||||||
@ -41,38 +40,37 @@ def cEmbedded(context:EventContext, data:InputMessageData) -> None:
|
|||||||
url = "https://hlb0.octt.eu.org/cors-main.php/https://" + url
|
url = "https://hlb0.octt.eu.org/cors-main.php/https://" + url
|
||||||
proto = ''
|
proto = ''
|
||||||
else:
|
else:
|
||||||
if urlDomain == "instagram.com":
|
if urlDomain in ("instagram.com", "www.instagram.com"):
|
||||||
urlDomain = "ddinstagram.com"
|
urlDomain = "ddinstagram.com"
|
||||||
elif urlDomain in ("twitter.com", "x.com"):
|
elif urlDomain in ("twitter.com", "x.com"):
|
||||||
urlDomain = "fxtwitter.com"
|
urlDomain = "fxtwitter.com"
|
||||||
elif urlDomain == "vm.tiktok.com":
|
elif urlDomain == "vm.tiktok.com":
|
||||||
urlDomain = "vm.vxtiktok.com"
|
urlDomain = "vm.vxtiktok.com"
|
||||||
url = urlDomain + url[len(urlDomain):]
|
url = (urlDomain + '/' + '/'.join(url.split('/')[1:]))
|
||||||
SendMessage(context, {"TextPlain": f"{{{proto}{url}}}"})
|
SendMessage(context, {"TextPlain": f"{{{proto}{url}}}"})
|
||||||
# else TODO error message?
|
# else TODO error message?
|
||||||
|
|
||||||
def cWeb(context:EventContext, data:InputMessageData) -> None:
|
def cWeb(context:EventContext, data:InputMessageData) -> None:
|
||||||
if data.Body:
|
if not (query := data.command.body):
|
||||||
|
return # TODO show message
|
||||||
try:
|
try:
|
||||||
QueryUrl = UrlParse.quote(data.Body)
|
QueryUrl = urlparse.quote(query)
|
||||||
Req = HttpReq(f'https://html.duckduckgo.com/html?q={QueryUrl}')
|
Req = HttpReq(f'https://html.duckduckgo.com/html?q={QueryUrl}')
|
||||||
Caption = f'🦆🔎 "{data.Body}": https://duckduckgo.com/?q={QueryUrl}\n\n'
|
Caption = f'🦆🔎 "{query}": https://duckduckgo.com/?q={QueryUrl}\n\n'
|
||||||
Index = 0
|
Index = 0
|
||||||
for Line in Req.read().decode().replace('\t', ' ').splitlines():
|
for Line in Req.read().decode().replace('\t', ' ').splitlines():
|
||||||
if ' class="result__a" ' in Line and ' href="//duckduckgo.com/l/?uddg=' in Line:
|
if ' class="result__a" ' in Line and ' href="//duckduckgo.com/l/?uddg=' in Line:
|
||||||
Index += 1
|
Index += 1
|
||||||
Link = UrlParse.unquote(Line.split(' href="//duckduckgo.com/l/?uddg=')[1].split('&rut=')[0])
|
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('>')
|
Title = Line.strip().split('</a>')[0].strip().split('</span>')[-1].strip().split('>')
|
||||||
if len(Title) > 1:
|
if len(Title) > 1:
|
||||||
Title = HtmlUnescape(Title[1].strip())
|
Title = html_unescape(Title[1].strip())
|
||||||
Caption += f'[{Index}] {Title} : {{{Link}}}\n\n'
|
Caption += f'[{Index}] {Title} : {{{Link}}}\n\n'
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
SendMessage(context, {"TextPlain": f'{Caption}...'})
|
SendMessage(context, {"TextPlain": f'{Caption}...'})
|
||||||
except Exception:
|
except Exception:
|
||||||
raise
|
raise
|
||||||
else:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def cImages(context:EventContext, data:InputMessageData) -> None:
|
def cImages(context:EventContext, data:InputMessageData) -> None:
|
||||||
pass
|
pass
|
||||||
@ -82,57 +80,57 @@ def cNews(context:EventContext, data:InputMessageData) -> None:
|
|||||||
|
|
||||||
def cTranslate(context:EventContext, data:InputMessageData) -> None:
|
def cTranslate(context:EventContext, data:InputMessageData) -> None:
|
||||||
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.Body))
|
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 SendMessage(context, {"TextPlain": f"Usage: /translate <to language> <text>"})
|
return SendMessage(context, {"TextPlain": f"Usage: /translate <to language> <text>"})
|
||||||
try:
|
try:
|
||||||
# TODO: Use many different public Lingva instances in rotation to avoid overloading a specific one
|
# TODO: Use many different public Lingva instances in rotation to avoid overloading a specific one
|
||||||
result = json.loads(HttpReq(f'https://lingva.ml/api/v1/auto/{language_to}/{UrlParse.quote(text_input)}').read())
|
result = json.loads(HttpReq(f'https://lingva.ml/api/v1/auto/{language_to}/{urlparse.quote(text_input)}').read())
|
||||||
SendMessage(context, {"TextPlain": f"[{result['info']['detectedSource']} (auto) -> {language_to}]\n\n{result['translation']}"})
|
SendMessage(context, {"TextPlain": f"[{result['info']['detectedSource']} (auto) -> {language_to}]\n\n{result['translation']}"})
|
||||||
except Exception:
|
except Exception:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# unsplash source appears to be deprecated! <https://old.reddit.com/r/unsplash/comments/s13x4h/what_happened_to_sourceunsplashcom/l65epl8/>
|
# unsplash source appears to be deprecated! <https://old.reddit.com/r/unsplash/comments/s13x4h/what_happened_to_sourceunsplashcom/l65epl8/>
|
||||||
def cUnsplash(context:EventContext, data:InputMessageData) -> None:
|
#def cUnsplash(context:EventContext, data:InputMessageData) -> None:
|
||||||
try:
|
# try:
|
||||||
Req = HttpReq(f'https://source.unsplash.com/random/?{UrlParse.quote(data.Body)}')
|
# Req = HttpReq(f'https://source.unsplash.com/random/?{urlparse.quote(data.command.body)}')
|
||||||
ImgUrl = Req.geturl().split('?')[0]
|
# ImgUrl = Req.geturl().split('?')[0]
|
||||||
SendMessage(context, {
|
# SendMessage(context, {
|
||||||
"TextPlain": f'{{{ImgUrl}}}',
|
# "TextPlain": f'{{{ImgUrl}}}',
|
||||||
"TextMarkdown": MarkdownCode(ImgUrl, True),
|
# "TextMarkdown": MarkdownCode(ImgUrl, True),
|
||||||
"Media": Req.read(),
|
# "Media": Req.read(),
|
||||||
})
|
# })
|
||||||
except Exception:
|
# except Exception:
|
||||||
raise
|
# raise
|
||||||
|
|
||||||
def cSafebooru(context:EventContext, data:InputMessageData) -> None:
|
def cSafebooru(context:EventContext, data:InputMessageData) -> None:
|
||||||
ApiUrl = 'https://safebooru.org/index.php?page=dapi&s=post&q=index&limit=100&tags='
|
ApiUrl = 'https://safebooru.org/index.php?page=dapi&s=post&q=index&limit=100&tags='
|
||||||
try:
|
try:
|
||||||
if data.Body:
|
img_id, img_url = None, None
|
||||||
|
if (query := data.command.body):
|
||||||
for i in range(7): # retry a bunch of times if we can't find a really random result
|
for i in range(7): # retry a bunch of times if we can't find a really random result
|
||||||
ImgUrls = HttpReq(f'{ApiUrl}md5:{RandHexStr(3)}%20{UrlParse.quote(data.Body)}').read().decode().split(' file_url="')[1:]
|
ImgUrls = HttpReq(f'{ApiUrl}md5:{RandHexStr(3)}%20{urlparse.quote(query)}').read().decode().split(' file_url="')[1:]
|
||||||
if ImgUrls:
|
if ImgUrls:
|
||||||
break
|
break
|
||||||
if not ImgUrls: # literal search
|
if not ImgUrls: # literal search
|
||||||
ImgUrls = HttpReq(f'{ApiUrl}{UrlParse.quote(data.Body)}').read().decode().split(' file_url="')[1:]
|
ImgUrls = HttpReq(f'{ApiUrl}{urlparse.quote(query)}').read().decode().split(' file_url="')[1:]
|
||||||
if not ImgUrls:
|
if not ImgUrls:
|
||||||
return SendMessage(context, {"Text": "Error: Could not get any result from Safebooru."})
|
return SendMessage(context, {"Text": "Error: Could not get any result from Safebooru."})
|
||||||
ImgXml = choice(ImgUrls)
|
ImgXml = choice(ImgUrls)
|
||||||
ImgUrl = ImgXml.split('"')[0]
|
img_url = ImgXml.split('"')[0]
|
||||||
ImgId = ImgXml.split(' id="')[1].split('"')[0]
|
img_id = ImgXml.split(' id="')[1].split('"')[0]
|
||||||
else:
|
else:
|
||||||
HtmlReq = HttpReq(HttpReq('https://safebooru.org/index.php?page=post&s=random').geturl())
|
HtmlReq = HttpReq(HttpReq('https://safebooru.org/index.php?page=post&s=random').geturl())
|
||||||
for Line in HtmlReq.read().decode().replace('\t', ' ').splitlines():
|
for Line in HtmlReq.read().decode().replace('\t', ' ').splitlines():
|
||||||
if '<img ' in Line and ' id="image" ' in Line and ' src="':
|
if '<img ' in Line and ' id="image" ' in Line and ' src="':
|
||||||
ImgUrl = Line.split(' src="')[1].split('"')[0]
|
img_url = Line.split(' src="')[1].split('"')[0]
|
||||||
ImgId = ImgUrl.split('?')[-1]
|
img_id = img_url.split('?')[-1]
|
||||||
break
|
break
|
||||||
if ImgUrl:
|
if img_url:
|
||||||
SendMessage(context, {
|
SendMessage(context, OutputMessageData(
|
||||||
"TextPlain": f'[{ImgId}]\n{{{ImgUrl}}}',
|
text_plain=f"[{img_id}]\n{{{img_url}}}",
|
||||||
"TextMarkdown": (f'\\[`{ImgId}`\\]\n' + MarkdownCode(ImgUrl, True)),
|
text_html=f"[<code>{img_id}</code>]\n<pre>{img_url}</pre>",
|
||||||
"media": {"url": ImgUrl}, #, "bytes": HttpReq(ImgUrl).read()},
|
media={"url": img_url}))
|
||||||
})
|
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
|
14
ModWinDog/Multifun/Multifun.py
Normal file → Executable file
14
ModWinDog/Multifun/Multifun.py
Normal file → Executable file
@ -4,19 +4,19 @@
|
|||||||
# ==================================== #
|
# ==================================== #
|
||||||
|
|
||||||
def mMultifun(context:EventContext, data:InputMessageData) -> None:
|
def mMultifun(context:EventContext, data:InputMessageData) -> None:
|
||||||
cmdkey = data.Name
|
cmdkey = data.command.name
|
||||||
replyToId = None
|
replyToId = None
|
||||||
if data.Quoted:
|
if data.quoted:
|
||||||
replyFromUid = data.Quoted.User.Id
|
replyFromUid = data.quoted.user.id
|
||||||
# TODO work on all platforms for the bot id
|
# TODO work on all platforms for the bot id
|
||||||
if replyFromUid.split(':')[1] == TelegramToken.split(':')[0] and 'bot' in Locale.__(cmdkey):
|
if replyFromUid.split(':')[1] == TelegramToken.split(':')[0] and 'bot' in Locale.__(cmdkey):
|
||||||
Text = choice(Locale.__(f'{cmdkey}.bot'))
|
Text = choice(Locale.__(f'{cmdkey}.bot'))
|
||||||
elif replyFromUid == data.User.Id and 'self' in Locale.__(cmdkey):
|
elif replyFromUid == data.user.id and 'self' in Locale.__(cmdkey):
|
||||||
Text = choice(Locale.__(f'{cmdkey}.self')).format(data.User.Name)
|
Text = choice(Locale.__(f'{cmdkey}.self')).format(data.user.name)
|
||||||
else:
|
else:
|
||||||
if 'others' in Locale.__(cmdkey):
|
if 'others' in Locale.__(cmdkey):
|
||||||
Text = choice(Locale.__(f'{cmdkey}.others')).format(data.User.Name, data.Quoted.User.Name)
|
Text = choice(Locale.__(f'{cmdkey}.others')).format(data.user.name, data.quoted.user.name)
|
||||||
replyToId = data.Quoted.messageId
|
replyToId = data.quoted.message_id
|
||||||
else:
|
else:
|
||||||
if 'empty' in Locale.__(cmdkey):
|
if 'empty' in Locale.__(cmdkey):
|
||||||
Text = choice(Locale.__(f'{cmdkey}.empty'))
|
Text = choice(Locale.__(f'{cmdkey}.empty'))
|
||||||
|
4
ModWinDog/Percenter/Percenter.py
Normal file → Executable file
4
ModWinDog/Percenter/Percenter.py
Normal file → Executable file
@ -4,8 +4,8 @@
|
|||||||
# ==================================== #
|
# ==================================== #
|
||||||
|
|
||||||
def mPercenter(context:EventContext, data:InputMessageData) -> None:
|
def mPercenter(context:EventContext, data:InputMessageData) -> None:
|
||||||
SendMessage(context, {"Text": choice(Locale.__(f'{data.Name}.{"done" if data.Body else "empty"}')).format(
|
SendMessage(context, {"Text": choice(Locale.__(f'{data.command.name}.{"done" if data.command.body else "empty"}')).format(
|
||||||
Cmd=data.Tokens[0], Percent=RandPercent(), Thing=data.Body)})
|
Cmd=data.command.tokens[0], Percent=RandPercent(), Thing=data.command.body)})
|
||||||
|
|
||||||
RegisterModule(name="Percenter", endpoints=[
|
RegisterModule(name="Percenter", endpoints=[
|
||||||
SafeNamespace(names=["wish", "level"], summary="Provides fun trough percentage-based toys.", handler=mPercenter),
|
SafeNamespace(names=["wish", "level"], summary="Provides fun trough percentage-based toys.", handler=mPercenter),
|
||||||
|
@ -36,8 +36,7 @@ def closeSelenium(index:int, driver:Driver) -> None:
|
|||||||
|
|
||||||
def cDalleSelenium(context:EventContext, data:InputMessageData) -> None:
|
def cDalleSelenium(context:EventContext, data:InputMessageData) -> None:
|
||||||
warning_text = "has been blocked by Microsoft because it violates their content policy. Further attempts might lead to a ban on your profile. Please review the Code of Conduct for Image Creator in this picture or at https://www.bing.com/new/termsofuseimagecreator#content-policy."
|
warning_text = "has been blocked by Microsoft because it violates their content policy. Further attempts might lead to a ban on your profile. Please review the Code of Conduct for Image Creator in this picture or at https://www.bing.com/new/termsofuseimagecreator#content-policy."
|
||||||
prompt = data.command.body
|
if not (prompt := data.command.body):
|
||||||
if not prompt:
|
|
||||||
return SendMessage(context, {"Text": "Please tell me what to generate."})
|
return SendMessage(context, {"Text": "Please tell me what to generate."})
|
||||||
driver_index, driver = None, None
|
driver_index, driver = None, None
|
||||||
try:
|
try:
|
||||||
@ -74,11 +73,10 @@ def cDalleSelenium(context:EventContext, data:InputMessageData) -> None:
|
|||||||
img_url = img_url.get_attribute("src").split('?')[0]
|
img_url = img_url.get_attribute("src").split('?')[0]
|
||||||
img_array.append({"url": img_url}) #, "bytes": HttpReq(img_url).read()})
|
img_array.append({"url": img_url}) #, "bytes": HttpReq(img_url).read()})
|
||||||
page_url = driver.current_url.split('?')[0]
|
page_url = driver.current_url.split('?')[0]
|
||||||
SendMessage(context, {
|
SendMessage(context, OutputMessageData(
|
||||||
"TextPlain": f'"{prompt}"\n{{{page_url}}}',
|
text_plain=f'"{prompt}"\n{{{page_url}}}',
|
||||||
"TextMarkdown": (f'"_{CharEscape(prompt, "MARKDOWN")}_"\n' + MarkdownCode(page_url, True)),
|
text_html=f'"<i>{html_escape(prompt)}</i>"\n<pre>{page_url}</pre>',
|
||||||
"media": img_array,
|
media=img_array))
|
||||||
})
|
|
||||||
return closeSelenium(driver_index, driver)
|
return closeSelenium(driver_index, driver)
|
||||||
raise Exception("VM timed out.")
|
raise Exception("VM timed out.")
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
@ -87,8 +85,7 @@ def cDalleSelenium(context:EventContext, data:InputMessageData) -> None:
|
|||||||
closeSelenium(driver_index, driver)
|
closeSelenium(driver_index, driver)
|
||||||
|
|
||||||
def cCraiyonSelenium(context:EventContext, data:InputMessageData) -> None:
|
def cCraiyonSelenium(context:EventContext, data:InputMessageData) -> None:
|
||||||
prompt = data.command.body
|
if not (prompt := data.command.body):
|
||||||
if not prompt:
|
|
||||||
return SendMessage(context, {"Text": "Please tell me what to generate."})
|
return SendMessage(context, {"Text": "Please tell me what to generate."})
|
||||||
driver_index, driver = None, None
|
driver_index, driver = None, None
|
||||||
try:
|
try:
|
||||||
|
@ -25,7 +25,7 @@ def luaAttributeFilter(obj, attr_name, is_setting):
|
|||||||
# TODO make print behave the same as normal Lua, and expose a function for printing without newlines
|
# TODO make print behave the same as normal Lua, and expose a function for printing without newlines
|
||||||
def cLua(context:EventContext, data:InputMessageData) -> None:
|
def cLua(context:EventContext, data:InputMessageData) -> None:
|
||||||
# TODO update quoted api getting
|
# TODO update quoted api getting
|
||||||
scriptText = (data.command.body or (data.Quoted and data.Quoted.Body))
|
scriptText = (data.command.body or (data.quoted and data.quoted.text_plain))
|
||||||
if not scriptText:
|
if not scriptText:
|
||||||
return SendMessage(context, {"Text": "You must provide some Lua code to execute."})
|
return SendMessage(context, {"Text": "You must provide some Lua code to execute."})
|
||||||
luaRuntime = NewLuaRuntime(max_memory=LuaMemoryLimit, register_eval=False, register_builtins=False, attribute_filter=luaAttributeFilter)
|
luaRuntime = NewLuaRuntime(max_memory=LuaMemoryLimit, register_eval=False, register_builtins=False, attribute_filter=luaAttributeFilter)
|
||||||
|
14
ModWinDog/Start/Start.py
Normal file
14
ModWinDog/Start/Start.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# ==================================== #
|
||||||
|
# WinDog multi-purpose chatbot #
|
||||||
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
|
# ==================================== #
|
||||||
|
|
||||||
|
def cStart(context:EventContext, data:InputMessageData) -> None:
|
||||||
|
SendMessage(context, OutputMessageData(
|
||||||
|
text_html=context.endpoint.get_string(
|
||||||
|
"start", data.user.settings.language).format(data.user.name)))
|
||||||
|
|
||||||
|
RegisterModule(name="Start", endpoints=[
|
||||||
|
SafeNamespace(names=["start"], handler=cStart),
|
||||||
|
])
|
||||||
|
|
16
ModWinDog/Start/Start.yaml
Normal file
16
ModWinDog/Start/Start.yaml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
summary:
|
||||||
|
en: Things related to starting the bot, on supported platforms.
|
||||||
|
endpoints:
|
||||||
|
start:
|
||||||
|
summary:
|
||||||
|
en: Salutes the user, hinting that the bot is working and providing basic quick help.
|
||||||
|
start:
|
||||||
|
en: |
|
||||||
|
<b>Hi</b> {0}<b>!</b>
|
||||||
|
|
||||||
|
Use /help to read a list of available commands.
|
||||||
|
it: |
|
||||||
|
<b>Ciao</b> {0}<b>!</b>
|
||||||
|
|
||||||
|
Usa /help per leggere la lista dei comandi.
|
||||||
|
|
24
ModWinDog/System/System.py
Normal file
24
ModWinDog/System/System.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# ==================================== #
|
||||||
|
# WinDog multi-purpose chatbot #
|
||||||
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
|
# ==================================== #
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
from re import compile as re_compile
|
||||||
|
|
||||||
|
def cExec(context:EventContext, data:InputMessageData) -> None:
|
||||||
|
if not (len(data.command.tokens) >= 2 and data.command.tokens[1].lower() in ExecAllowed):
|
||||||
|
return SendMessage(context, {"Text": choice(Locale.__('eval'))})
|
||||||
|
command = data.command.tokens[1].lower()
|
||||||
|
output = subprocess.run(
|
||||||
|
("sh", "-c", f"export PATH=$PATH:/usr/games; {command}"),
|
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.decode()
|
||||||
|
# <https://stackoverflow.com/a/14693789>
|
||||||
|
text = (re_compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])").sub('', output))
|
||||||
|
SendMessage(context, OutputMessageData(
|
||||||
|
text_plain=text, text_html=f"<pre>{html_escape(text)}</pre>"))
|
||||||
|
|
||||||
|
RegisterModule(name="System", endpoints=[
|
||||||
|
SafeNamespace(names=["exec"], handler=cExec),
|
||||||
|
])
|
||||||
|
|
5
ModWinDog/System/System.yaml
Normal file
5
ModWinDog/System/System.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
endpoints:
|
||||||
|
exec:
|
||||||
|
summary:
|
||||||
|
en: Execute a system command from the allowed ones and return stdout+stderr.
|
||||||
|
|
51
WinDog.py
51
WinDog.py
@ -8,13 +8,15 @@ import json, time
|
|||||||
from binascii import hexlify
|
from binascii import hexlify
|
||||||
from glob import glob
|
from glob import glob
|
||||||
from hashlib import new as hashlib_new
|
from hashlib import new as hashlib_new
|
||||||
|
from html import escape as html_escape, unescape as html_unescape
|
||||||
from os import listdir
|
from os import listdir
|
||||||
from os.path import isfile, isdir
|
from os.path import isfile, isdir
|
||||||
from random import choice, randint
|
from random import choice, choice as randchoice, randint
|
||||||
|
from threading import Thread
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
|
from urllib import parse as urlparse
|
||||||
from yaml import load as yaml_load, BaseLoader as yaml_BaseLoader
|
from yaml import load as yaml_load, BaseLoader as yaml_BaseLoader
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from html import unescape as HtmlUnescape
|
|
||||||
from markdown import markdown
|
from markdown import markdown
|
||||||
from LibWinDog.Types import *
|
from LibWinDog.Types import *
|
||||||
from LibWinDog.Config import *
|
from LibWinDog.Config import *
|
||||||
@ -98,17 +100,21 @@ def ObjGet(node:object, query:str, /) -> any:
|
|||||||
return None
|
return None
|
||||||
return node
|
return node
|
||||||
|
|
||||||
def isinstanceSafe(clazz:any, instance:any) -> bool:
|
def isinstanceSafe(clazz:any, instance:any, /) -> bool:
|
||||||
if instance != None:
|
if instance != None:
|
||||||
return isinstance(clazz, instance)
|
return isinstance(clazz, instance)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def GetString(bank:dict, query:str|dict, lang:str=None):
|
def get_string(bank:dict, query:str|dict, lang:str=None, /):
|
||||||
if not (result := ObjGet(bank, f"{query}.{lang or DefaultLang}")):
|
if not (result := ObjGet(bank, f"{query}.{lang or DefaultLang}")):
|
||||||
if not (result := ObjGet(bank, f"{query}.en")):
|
if not (result := ObjGet(bank, f"{query}.en")):
|
||||||
result = tuple(ObjGet(bank, query).values())[0]
|
result = ObjGet(bank, query)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def strip_url_scheme(url:str) -> str:
|
||||||
|
tokens = urlparse.urlparse(url)
|
||||||
|
return f"{tokens.netloc}{tokens.path}"
|
||||||
|
|
||||||
def CharEscape(String:str, Escape:str='') -> str:
|
def CharEscape(String:str, Escape:str='') -> str:
|
||||||
if Escape == 'MARKDOWN':
|
if Escape == 'MARKDOWN':
|
||||||
return escape_markdown(String, version=2)
|
return escape_markdown(String, version=2)
|
||||||
@ -128,9 +134,6 @@ def InferMdEscape(raw:str, plain:str) -> str:
|
|||||||
chars += char
|
chars += char
|
||||||
return chars
|
return chars
|
||||||
|
|
||||||
def MarkdownCode(text:str, block:bool) -> str:
|
|
||||||
return ('```\n' + text.strip().replace('`', '\`') + '\n```')
|
|
||||||
|
|
||||||
def MdToTxt(md:str) -> str:
|
def MdToTxt(md:str) -> str:
|
||||||
return BeautifulSoup(markdown(md), 'html.parser').get_text(' ')
|
return BeautifulSoup(markdown(md), 'html.parser').get_text(' ')
|
||||||
|
|
||||||
@ -141,23 +144,6 @@ def HtmlEscapeFull(Raw:str) -> str:
|
|||||||
New += f'&#x{Hex[i] + Hex[i+1]};'
|
New += f'&#x{Hex[i] + Hex[i+1]};'
|
||||||
return New
|
return New
|
||||||
|
|
||||||
def GetRawTokens(text:str) -> list:
|
|
||||||
return text.strip().replace('\t', ' ').replace(' ', ' ').replace(' ', ' ').split(' ')
|
|
||||||
|
|
||||||
def ParseCmd(msg) -> SafeNamespace|None:
|
|
||||||
#if not len(msg) or msg[1] not in CmdPrefixes:
|
|
||||||
# return
|
|
||||||
name = msg.replace('\n', ' ').replace('\t', ' ').replace(' ', ' ').replace(' ', ' ').split(' ')[0][1:].split('@')[0]
|
|
||||||
#if not name:
|
|
||||||
# return
|
|
||||||
return SafeNamespace(**{
|
|
||||||
"Name": name.lower(),
|
|
||||||
"Body": name.join(msg.split(name)[1:]).strip(),
|
|
||||||
"Tokens": GetRawTokens(msg),
|
|
||||||
"User": None,
|
|
||||||
"Quoted": None,
|
|
||||||
})
|
|
||||||
|
|
||||||
def GetWeightedText(*texts) -> str|None:
|
def GetWeightedText(*texts) -> str|None:
|
||||||
for text in texts:
|
for text in texts:
|
||||||
if text:
|
if text:
|
||||||
@ -179,6 +165,7 @@ def GetUserSettings(user_id:str) -> SafeNamespace|None:
|
|||||||
except EntitySettings.DoesNotExist:
|
except EntitySettings.DoesNotExist:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# TODO handle @ characters attached to command, e.g. on telegram
|
||||||
def ParseCommand(text:str) -> SafeNamespace|None:
|
def ParseCommand(text:str) -> SafeNamespace|None:
|
||||||
if not text:
|
if not text:
|
||||||
return None
|
return None
|
||||||
@ -227,8 +214,8 @@ def UpdateUserDb(user:SafeNamespace) -> None:
|
|||||||
def DumpMessage(data:InputMessageData) -> None:
|
def DumpMessage(data:InputMessageData) -> None:
|
||||||
if not (Debug and (DumpToFile or DumpToConsole)):
|
if not (Debug and (DumpToFile or DumpToConsole)):
|
||||||
return
|
return
|
||||||
text = (data.text_auto.replace('\n', '\\n') if data.text_auto else '')
|
text = (data.text_plain.replace('\n', '\\n') if data.text_auto else '')
|
||||||
text = f"[{int(time.time())}] [{time.ctime()}] [{data.room.id}] [{data.message_id}] [{data.user.id}] {text}"
|
text = f"[{int(time.time())}] [{time.ctime()}] [{data.room and data.room.id}] [{data.message_id}] [{data.user.id}] {text}"
|
||||||
if DumpToConsole:
|
if DumpToConsole:
|
||||||
print(text, data)
|
print(text, data)
|
||||||
if DumpToFile:
|
if DumpToFile:
|
||||||
@ -247,13 +234,13 @@ def SendMessage(context:EventContext, data:OutputMessageData, destination=None)
|
|||||||
|
|
||||||
if data.text_plain or data.text_markdown or data.text_html:
|
if data.text_plain or data.text_markdown or data.text_html:
|
||||||
if data.text_html and not data.text_plain:
|
if data.text_html and not data.text_plain:
|
||||||
data.text_plain = data.text_html # TODO flatten the HTML to plaintext
|
data.text_plain = BeautifulSoup(data.text_html, "html.parser").get_text()
|
||||||
elif data.text_markdown and not data.text_plain:
|
elif data.text_markdown and not data.text_plain:
|
||||||
data.text_plain = data.text_markdown
|
data.text_plain = data.text_markdown
|
||||||
elif data.text:
|
elif data.text:
|
||||||
# our old system attempts to always receive Markdown and retransform when needed
|
# our old system attempts to always receive Markdown and retransform when needed
|
||||||
data.text_plain = MdToTxt(data.text)
|
data.text_plain = MdToTxt(data.text)
|
||||||
data.text_markdown = CharEscape(HtmlUnescape(data.text), InferMdEscape(HtmlUnescape(data.text), data.text_plain))
|
data.text_markdown = CharEscape(html_unescape(data.text), InferMdEscape(html_unescape(data.text), data.text_plain))
|
||||||
#data.text_html = ???
|
#data.text_html = ???
|
||||||
if data.media:
|
if data.media:
|
||||||
data.media = SureArray(data.media)
|
data.media = SureArray(data.media)
|
||||||
@ -272,10 +259,10 @@ def RegisterPlatform(name:str, main:callable, sender:callable, linker:callable=N
|
|||||||
Log(f"{name}, ", inline=True)
|
Log(f"{name}, ", inline=True)
|
||||||
|
|
||||||
def RegisterModule(name:str, endpoints:dict, *, group:str|None=None, summary:str|None=None) -> None:
|
def RegisterModule(name:str, endpoints:dict, *, group:str|None=None, summary:str|None=None) -> None:
|
||||||
module = SafeNamespace(group=group, endpoints=endpoints)
|
module = SafeNamespace(group=group, endpoints=endpoints, get_string=(lambda query, lang=None, /: None))
|
||||||
if isfile(file := f"./ModWinDog/{name}/{name}.yaml"):
|
if isfile(file := f"./ModWinDog/{name}/{name}.yaml"):
|
||||||
module.strings = yaml_load(open(file, 'r').read().replace("\t", " "), Loader=yaml_BaseLoader)
|
module.strings = yaml_load(open(file, 'r').read().replace("\t", " "), Loader=yaml_BaseLoader)
|
||||||
module.get_string = (lambda query, lang=None: GetString(module.strings, query, lang))
|
module.get_string = (lambda query, lang=None: get_string(module.strings, query, lang))
|
||||||
Modules[name] = module
|
Modules[name] = module
|
||||||
Log(f"{name}, ", inline=True)
|
Log(f"{name}, ", inline=True)
|
||||||
for endpoint in endpoints:
|
for endpoint in endpoints:
|
||||||
@ -287,7 +274,7 @@ def CallEndpoint(name:str, context:EventContext, data:InputMessageData):
|
|||||||
endpoint = Endpoints[name]
|
endpoint = Endpoints[name]
|
||||||
context.endpoint = endpoint
|
context.endpoint = endpoint
|
||||||
context.module = endpoint.module
|
context.module = endpoint.module
|
||||||
context.endpoint.get_string = (lambda query, lang=None: endpoint.module.get_string(f"endpoints.{data.command.name}.{query}", lang))
|
context.endpoint.get_string = (lambda query, lang=None, /: endpoint.module.get_string(f"endpoints.{data.command.name}.{query}", lang))
|
||||||
return endpoint.handler(context, data)
|
return endpoint.handler(context, data)
|
||||||
|
|
||||||
def WriteNewConfig() -> None:
|
def WriteNewConfig() -> None:
|
||||||
|
Reference in New Issue
Block a user