294 lines
14 KiB
Python
294 lines
14 KiB
Python
import discord
|
|
import requests
|
|
import json
|
|
import urllib.parse
|
|
import datetime
|
|
import subprocess
|
|
import os
|
|
import binascii
|
|
|
|
from loguru import logger
|
|
|
|
TOKEN = 'OTQwMDY3MDk5NDI1MDA5NzU1.YgB_9Q.u95iaPy2gix5ZqrQFA_rmyK5Bzw'
|
|
IS_BOT = False # Set to false if this is a selfbot
|
|
DO_TEST = False # Set to True if you want to run test
|
|
ACTIVE_PHRASE = '$rtex' # Change to trigger phrase
|
|
RAW_ACTIVE_PHRASE = '$rawtex' # Change to trigger phrase for raw mode
|
|
EQUATION_ACTIVE_PHRASE = ';;' # Change to trigger phrase for an equation
|
|
WOLFRAM_APPKEY = 'X36K7Y-JT8GXU5698' # Change to your Wolfram|Alpha api key
|
|
|
|
logger.info('initializing', 'bot' if IS_BOT else 'selfbot', 'mode')
|
|
|
|
client = discord.Client()
|
|
|
|
def upload_to_term(content):
|
|
logger.info('uploading content to pastebin')
|
|
try:
|
|
c = subprocess.run(['nc', 'termbin.com', '9999'], capture_output=True, check=True, text=True, input=content)
|
|
except subprocess.CalledProcessError as e:
|
|
logger.error('failed to upload to pastebin: ' + e.content)
|
|
return None
|
|
return c.stdout
|
|
|
|
@client.event
|
|
async def on_ready():
|
|
logger.info('ready')
|
|
await client.change_presence(activity=discord.Game('with math | v1.24.1'))
|
|
|
|
tex_wrapper_start = '\\documentclass{article}\n\\usepackage{xcolor}\n\\usepackage{amsmath}\n\\begin{document}\n\\color{gray}\n'
|
|
tex_wrapper_end = '\n\\pagenumbering{gobble}\n\\end{document}'
|
|
def wrap_tex(tex):
|
|
return tex_wrapper_start + tex + tex_wrapper_end
|
|
|
|
RTEX = 'https://rtex.probablyaweb.site/'
|
|
|
|
def render_tex(tex):
|
|
logger.info('accepted job')
|
|
logger.info('wrapping tex')
|
|
tex = wrap_tex(tex)
|
|
logger.info('building request to rtex api')
|
|
payload = {
|
|
"code": tex,
|
|
"format": "png"
|
|
}
|
|
logger.info('build payload', payload)
|
|
logger.info(f'contacting api ({RTEX})')
|
|
logger.info('waiting for response')
|
|
response = requests.post(RTEX + 'api/v2', data=payload)
|
|
response.raise_for_status()
|
|
logger.info('decoding response')
|
|
data = response.json()
|
|
c = None
|
|
if data['status'] != 'success':
|
|
logger.error('job failed')
|
|
c = upload_to_term(data['log'])
|
|
return (False, c)
|
|
logger.info('fetching file')
|
|
uri = RTEX + 'api/v2/' + data['filename']
|
|
logger.info(f'render job done {uri}')
|
|
return (True, uri)
|
|
|
|
async def wolfram_get_plot_implicit(equ, qurl):
|
|
url = f'https://api.wolframalpha.com/v2/query?appid={WOLFRAM_APPKEY}&input={equ}&format=image&output=json&includepodid=ImplicitPlot'
|
|
logger.info('querying for implicit plot')
|
|
response = requests.get(url)
|
|
response.raise_for_status()
|
|
logger.info('decoding response')
|
|
data = response.json()
|
|
if data['queryresult']['success'] != True:
|
|
logger.error('query failed:', data['queryresult']['error'])
|
|
return (False, 'Sorry, but Wolfram|Alpha returned an error. Try checking on wolframalpha.com: ' + qurl)
|
|
if data['queryresult']['numpods'] > 1:
|
|
logger.error('unexpectected result, more than 1 pod')
|
|
return (False, 'Sorry, but Wolfram|Alpha returned too much data for the bot to process. Try checking on wolframalpha.com: ' + qurl)
|
|
if data['queryresult']['numpods'] < 1:
|
|
logger.warning('no graph')
|
|
return (False, 'Sorry, but I couldn\'t find any plots for that equation.')
|
|
plotpod = data['queryresult']['pods'][0]
|
|
if plotpod['id'] != 'ImplicitPlot':
|
|
logger.error('unexpected result, wrong pod id')
|
|
return (False, 'Sorry, but Wolfram|Alpha returned an unexpected response. Try checking on wolframalpha.com: ' + qurl)
|
|
plotimg = plotpod['subpods'][0]['img']['src']
|
|
return (True, plotimg)
|
|
|
|
async def wolfram_get_plot(equ, qurl):
|
|
url = f'https://api.wolframalpha.com/v2/query?appid={WOLFRAM_APPKEY}&input={equ}&format=image&output=json&includepodid=Plot'
|
|
logger.info('querying for plot')
|
|
response = requests.get(url)
|
|
response.raise_for_status()
|
|
logger.info('decoding response')
|
|
data = response.json()
|
|
if data['queryresult']['success'] != True:
|
|
logger.error('query failed:', data['queryresult']['error'])
|
|
return (False, 'Sorry, but Wolfram|Alpha returned an error. Try checking on wolframalpha.com: ' + qurl)
|
|
if data['queryresult']['numpods'] > 1:
|
|
logger.error('unexpectected result, more than 1 pod')
|
|
return (False, 'Sorry, but Wolfram|Alpha returned too much data for the bot to process. Try checking on wolframalpha.com: ' + qurl)
|
|
if data['queryresult']['numpods'] < 1:
|
|
logger.warning('no graph')
|
|
return (False, 'Sorry, but I couldn\'t find any plots for that equation.')
|
|
plotpod = data['queryresult']['pods'][0]
|
|
if plotpod['id'] != 'Plot':
|
|
logger.error('unexpected result, wrong pod id')
|
|
return (False, 'Sorry, but Wolfram|Alpha returned an unexpected response. Try checking on wolframalpha.com: ' + qurl)
|
|
plotimg = plotpod['subpods'][0]['img']['src']
|
|
return (True, plotimg)
|
|
|
|
|
|
async def wolfram_get_plot_3d(equ, qurl):
|
|
url = f'https://api.wolframalpha.com/v2/query?appid={WOLFRAM_APPKEY}&input={equ}&format=image&output=json&includepodid=3DPlot'
|
|
logger.info('querying for plot')
|
|
response = requests.get(url)
|
|
response.raise_for_status()
|
|
logger.info('decoding response')
|
|
data = response.json()
|
|
if data['queryresult']['success'] != True:
|
|
logger.error('query failed:', data['queryresult']['error'])
|
|
return (False, 'Sorry, but Wolfram|Alpha returned an error. Try checking on wolframalpha.com: ' + qurl)
|
|
if data['queryresult']['numpods'] > 1:
|
|
logger.error('unexpectected result, more than 1 pod')
|
|
return (False, 'Sorry, but Wolfram|Alpha returned too much data for the bot to process. Try checking on wolframalpha.com: ' + qurl)
|
|
if data['queryresult']['numpods'] < 1:
|
|
logger.warning('no graph')
|
|
return (False, 'Sorry, but I couldn\'t find any plots for that equation.')
|
|
plotpod = data['queryresult']['pods'][0]
|
|
if plotpod['id'] != '3DPlot':
|
|
logger.error('unexpected result, wrong pod id')
|
|
return (False, 'Sorry, but Wolfram|Alpha returned an unexpected response. Try checking on wolframalpha.com: ' + qurl)
|
|
plotimg = plotpod['subpods'][0]['img']['src']
|
|
return (True, plotimg)
|
|
|
|
async def wolfram_graph3d(equation, ctx):
|
|
logger.info('job accepted, equ: ' + equation)
|
|
urlencoded = urllib.parse.quote(equation, safe='')
|
|
logger.debug('urlencoded is ' + urlencoded)
|
|
qurl = 'https://wolframalpha.com/input?i=' + urlencoded
|
|
logger.debug('qurl is ' + qurl)
|
|
logger.debug('done, creating embed')
|
|
success, msg = await wolfram_get_plot_3d(urlencoded, qurl)
|
|
if not success:
|
|
logger.error('failed')
|
|
await ctx.reply('Sorry, but something went wrong while rendering your equation:\n```' + msg + '\n```')
|
|
return
|
|
else:
|
|
await ctx.reply(msg)
|
|
return
|
|
|
|
async def wolfram_graph(equation, ctx):
|
|
logger.info('job accepted, equ: ' + equation)
|
|
urlencoded = urllib.parse.quote(equation, safe='')
|
|
logger.debug('urlencoded is ' + urlencoded)
|
|
qurl = 'https://wolframalpha.com/input?i=' + urlencoded
|
|
logger.debug('qurl is ' + qurl)
|
|
logger.debug('done, creating embed')
|
|
success, msg = await wolfram_get_plot(urlencoded, qurl)
|
|
if not success:
|
|
logger.info('try 1 failed, attempting implicit plotting')
|
|
success, msgi = await wolfram_get_plot_implicit(urlencoded, qurl)
|
|
if not success:
|
|
logger.error('failed')
|
|
await ctx.reply('Sorry, but something went wrong while rendering your equation:\n```' + msg + '\n' + msgi + '\n```')
|
|
return
|
|
logger.info('implicit plot found')
|
|
await ctx.reply(msgi)
|
|
return
|
|
else:
|
|
await ctx.reply(msg)
|
|
return
|
|
# selfbots cant use embeds :(
|
|
#imgurl = plotimg
|
|
#queryurl = 'https://wolframalpha.com/input?i=' + urlencoded
|
|
#embed = discord.Embed(
|
|
# title='Graph: ' + equation,
|
|
# url=queryurl,
|
|
# timestamp=datetime.datetime.now(),
|
|
# footer='Made with <3 by c0repwn3r',
|
|
# description='Here\'s your plot!'
|
|
#)
|
|
#embed.set_author(name='Wolfram|Alpha', icon_url='https://wolframalpha.com/favicon.ico')
|
|
#embed.set_image(url=imgurl)
|
|
#await ctx.channel.send(imgurl)
|
|
#return
|
|
|
|
def test_render_engine():
|
|
logger.info('testing rendering engine')
|
|
if render_tex('lbtest')[0] == False:
|
|
logger.warning('render test 1 failed, attempting render 2')
|
|
if render_tex('ftest')[0] == False:
|
|
logger.error('render test failed, exiting')
|
|
exit(-1)
|
|
logger.info('render test successful')
|
|
|
|
if DO_TEST:
|
|
test_render_engine()
|
|
else:
|
|
logger.info('skipping rendering test')
|
|
|
|
logger.info('creating msg event handler')
|
|
|
|
@client.event
|
|
async def on_message(message):
|
|
if message.author == client.user:
|
|
logger.debug('ignoring my own message')
|
|
return
|
|
if message.content in ['.authtest', '.help', '.embed', '.dumpconfig', '.writeconfig'] or message.content.startswith('.graphequ3d') or message.content.startswith('.writeconfig') or ACTIVE_PHRASE in message.content or RAW_ACTIVE_PHRASE in message.content or len(message.content.split(EQUATION_ACTIVE_PHRASE)) > 2 or message.content.startswith('.graphequ'):
|
|
async with message.channel.typing():
|
|
await process_message(message)
|
|
|
|
async def process_message(message):
|
|
global config
|
|
logger.info('processing message')
|
|
if message.author == client.user:
|
|
logger.debug('ignoring my own message')
|
|
return
|
|
if message.content == '.dumpconfig':
|
|
await message.reply(json.dumps(config))
|
|
return
|
|
elif message.content == '.authtest':
|
|
await message.reply(f'Hi there! Unfortunately, I\'m not authorized to issue authentication codes for you. Check with core if you should be able to get one. Sorry :/')
|
|
# await message.reply(f'Hi! Your auth code is `{binascii.b2a_hex(os.urandom(3))[2:-1]}`. It will be valid for 60 seconds.\n**Nobody should be asking you for this code. Ensure that your browser shows the URL https://hotel.security.internalwg.e3t.cc/discord_3fa, otherwise you might compromise access to the VPN.**')
|
|
return
|
|
elif message.content.startswith('.writeconfig'):
|
|
newconfig = ' '.join(message.content.split(' ')[1:])
|
|
config = newconfig
|
|
write_config()
|
|
await message.reply('Updated config.')
|
|
return
|
|
elif message.content.startswith('.graphequ3d'):
|
|
equation = message.content[12:]
|
|
logger.info('job accepted (equation', equation, 'TBR by Wolfram in 3d)')
|
|
await wolfram_graph3d(equation, message)
|
|
return
|
|
elif message.content.startswith('.graphequ'):
|
|
equation = message.content[10:]
|
|
logger.info('job accepted (equation', equation, 'TBR by Wolfram)')
|
|
await wolfram_graph(equation, message)
|
|
return
|
|
elif message.content == '.embed':
|
|
embed=discord.Embed(title="Test Embed", url="https://coredoes.dev", description="This is an embed to figure out what discord.py is doing.")
|
|
await message.channel.send(embed=embed)
|
|
return
|
|
elif message.content == '.help':
|
|
await message.channel.send(":wave: **Hi, I'm c0remath3r**\nI'm a math bot with several functions, but primarily rendering LaTeX expressions and querying Wolfram|Alpha right within discord. Here's how it works!\r**To render an entire message as a LaTeX document^**: place \$rtex *somewhere* in your message. It does not matter where.\nAn easier way to do this, however, is to simply **surround the equation you want to render with \;\;^** which will render it as an equation, and the rest of the message as text.\n*^: some preprocessing is applied.*\n\n**To graph an equation:** simply write `.graphequ <equation>`. This will query Wolfram|Alpha to fetch a (implicit, if nessecary) plot and will send it in the channel.\n**For 3-D graphs:** simply use `.graphequ3d <equation>` instead.\n\nHave fun!")
|
|
return
|
|
content = ''
|
|
if RAW_ACTIVE_PHRASE in message.content:
|
|
logger.info('found raw trigger phrase, processing')
|
|
logger.debug('preprocessing: removing trigger phrase')
|
|
content = message.content.replace(RAW_ACTIVE_PHRASE, '')
|
|
logger.info('rendering')
|
|
url = render_tex(content)
|
|
if url[0]:
|
|
logger.error('rendering failed, notifying user')
|
|
await message.reply('Sorry, but I failed to render your message. Please try again later or contact core.\nYou can also check the render log: ' + url[1])
|
|
else:
|
|
logger.info('rendered, replying')
|
|
await message.reply(url[1])
|
|
if len(message.content.split(EQUATION_ACTIVE_PHRASE)) >= 3:
|
|
logger.info('found equation message, processing')
|
|
logger.debug('preprocessing: modifying equation phrases')
|
|
content = message.content.replace(EQUATION_ACTIVE_PHRASE, '$')
|
|
logger.info('rendering')
|
|
url = render_tex(content)
|
|
if url[0] == False:
|
|
logger.error('rendering failed, notifying user')
|
|
await message.reply('Sorry, but I failed to render your message. Please try again later or contact core.\nYou can also check the render log: ' + url[1])
|
|
else:
|
|
logger.info('rendered, replying')
|
|
await message.reply(url[1])
|
|
if ACTIVE_PHRASE in message.content:
|
|
logger.info('found trigger phrase, processing')
|
|
logger.debug('preprocessing (trigger phrase removal)')
|
|
content = message.content.replace(ACTIVE_PHRASE, '')
|
|
logger.info('rendering')
|
|
url = render_tex(content)
|
|
if url[0] == False:
|
|
logger.error('rendering failed, notifying user')
|
|
await message.reply('Sorry, but I failed to render your message. Please try again later or contact core.\nYou can also check the render log: ' + url[1])
|
|
else:
|
|
logger.info('rendered, replying')
|
|
await message.reply(url[1])
|
|
|
|
logger.info('ready to initialize, logging in')
|
|
client.run(TOKEN)
|