diff --git a/requirements.txt b/requirements.txt
index a30a623..6a1a32e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -10,5 +10,6 @@ docker
aiohttp
psycopg2-binary
pyTelegramBotAPI
+discord.py
py-leap@git+https://github.com/guilledk/py-leap.git@v0.1a14
diff --git a/skynet.ini.example b/skynet.ini.example
index b498dd8..1f9eab0 100644
--- a/skynet.ini.example
+++ b/skynet.ini.example
@@ -22,3 +22,14 @@ hyperion_url = https://skynet.ancap.tech
ipfs_url = /ip4/169.197.140.154/tcp/4001/p2p/12D3KooWKWogLFNEcNNMKnzU7Snrnuj84RZdMBg3sLiQSQc51oEv
token = XXXXXXXXXX:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
+[skynet.discord]
+account = discord
+permission = active
+key = 5Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
+node_url = https://skynet.ancap.tech
+hyperion_url = https://skynet.ancap.tech
+ipfs_url = /ip4/169.197.140.154/tcp/4001/p2p/12D3KooWKWogLFNEcNNMKnzU7Snrnuj84RZdMBg3sLiQSQc51oEv
+
+token = XXXXXXXXXX:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
diff --git a/skynet/cli.py b/skynet/cli.py
index 1f7d24c..385b056 100755
--- a/skynet/cli.py
+++ b/skynet/cli.py
@@ -24,6 +24,7 @@ from .config import *
from .nodeos import open_cleos, open_nodeos
from .constants import *
from .frontend.telegram import SkynetTelegramFrontend
+from .frontend.discord import SkynetDiscordFrontend
@click.group()
@@ -43,7 +44,7 @@ def skynet(*args, **kwargs):
@click.option('--seed', '-S', default=None)
def txt2img(*args, **kwargs):
from . import utils
- _, hf_token, _ = init_env_from_config()
+ _, hf_token, _, _ = init_env_from_config()
utils.txt2img(hf_token, **kwargs)
@click.command()
@@ -58,7 +59,7 @@ def txt2img(*args, **kwargs):
@click.option('--seed', '-S', default=None)
def img2img(model, prompt, input, output, strength, guidance, steps, seed):
from . import utils
- _, hf_token, _ = init_env_from_config()
+ _, hf_token, _, _ = init_env_from_config()
utils.img2img(
hf_token,
model=model,
@@ -86,7 +87,7 @@ def upscale(input, output, model):
@skynet.command()
def download():
from . import utils
- _, hf_token, _ = init_env_from_config()
+ _, hf_token, _, _ = init_env_from_config()
utils.download_all_models(hf_token)
@skynet.command()
@@ -408,7 +409,7 @@ def telegram(
):
logging.basicConfig(level=loglevel)
- _, _, tg_token = init_env_from_config()
+ _, _, tg_token, _ = init_env_from_config()
key, account, permission = load_account_info(
'telegram', key, account, permission)
@@ -435,6 +436,66 @@ def telegram(
asyncio.run(_async_main())
+@run.command()
+@click.option('--loglevel', '-l', default='INFO', help='logging level')
+@click.option(
+ '--account', '-a', default='discord')
+@click.option(
+ '--permission', '-p', default='active')
+@click.option(
+ '--key', '-k', default=None)
+@click.option(
+ '--hyperion-url', '-y', default=f'https://{DEFAULT_DOMAIN}')
+@click.option(
+ '--node-url', '-n', default=f'https://{DEFAULT_DOMAIN}')
+@click.option(
+ '--ipfs-url', '-i', default=DEFAULT_IPFS_REMOTE)
+@click.option(
+ '--db-host', '-h', default='localhost:5432')
+@click.option(
+ '--db-user', '-u', default='skynet')
+@click.option(
+ '--db-pass', '-u', default='password')
+def discord(
+ loglevel: str,
+ account: str,
+ permission: str,
+ key: str | None,
+ hyperion_url: str,
+ ipfs_url: str,
+ node_url: str,
+ db_host: str,
+ db_user: str,
+ db_pass: str
+):
+ logging.basicConfig(level=loglevel)
+
+ _, _, _, dc_token = init_env_from_config()
+
+ key, account, permission = load_account_info(
+ 'discord', key, account, permission)
+
+ node_url, _, ipfs_url = load_endpoint_info(
+ 'discord', node_url, None, None)
+
+ async def _async_main():
+ frontend = SkynetDiscordFrontend(
+ # dc_token,
+ account,
+ permission,
+ node_url,
+ hyperion_url,
+ db_host, db_user, db_pass,
+ remote_ipfs_node=ipfs_url,
+ key=key
+ )
+
+ async with frontend.open():
+ await frontend.bot.start(dc_token)
+
+ asyncio.run(_async_main())
+
+
@run.command()
@click.option('--loglevel', '-l', default='INFO', help='logging level')
@click.option('--name', '-n', default='skynet-ipfs', help='container name')
diff --git a/skynet/config.py b/skynet/config.py
index b025feb..d668a41 100755
--- a/skynet/config.py
+++ b/skynet/config.py
@@ -23,6 +23,7 @@ def init_env_from_config(
hf_token: str | None = None,
hf_home: str | None = None,
tg_token: str | None = None,
+ dc_token: str | None = None,
file_path=DEFAULT_CONFIG_PATH
):
config = load_skynet_ini(file_path=file_path)
@@ -52,7 +53,14 @@ def init_env_from_config(
if 'token' in sub_config:
tg_token = sub_config['token']
- return hf_home, hf_token, tg_token
+ if 'DC_TOKEN' in os.environ:
+ dc_token = os.environ['DC_TOKEN']
+ elif 'skynet.discord' in config:
+ sub_config = config['skynet.discord']
+ if 'token' in sub_config:
+ dc_token = sub_config['token']
+
+ return hf_home, hf_token, tg_token, dc_token
def load_account_info(
diff --git a/skynet/frontend/discord/__init__.py b/skynet/frontend/discord/__init__.py
new file mode 100644
index 0000000..a9518b3
--- /dev/null
+++ b/skynet/frontend/discord/__init__.py
@@ -0,0 +1,280 @@
+#!/usr/bin/python
+
+from json import JSONDecodeError
+import random
+import logging
+import asyncio
+
+from decimal import Decimal
+from hashlib import sha256
+from datetime import datetime
+from contextlib import ExitStack, AsyncExitStack
+from contextlib import asynccontextmanager as acm
+
+from leap.cleos import CLEOS
+from leap.sugar import Name, asset_from_str, collect_stdout
+from leap.hyperion import HyperionAPI
+# from telebot.types import InputMediaPhoto
+
+# import discord
+
+from skynet.db import open_new_database, open_database_connection
+from skynet.ipfs import get_ipfs_file
+from skynet.ipfs.docker import open_ipfs_node
+from skynet.constants import *
+
+from . import *
+from .bot import DiscordBot
+
+from .utils import *
+from .handlers import create_handler_context
+
+
+class SkynetDiscordFrontend:
+
+ def __init__(
+ self,
+ token: str,
+ account: str,
+ permission: str,
+ node_url: str,
+ hyperion_url: str,
+ db_host: str,
+ db_user: str,
+ # db_pass: str,
+ remote_ipfs_node: str,
+ key: str
+ ):
+ self.token = token
+ self.account = account
+ self.permission = permission
+ self.node_url = node_url
+ self.hyperion_url = hyperion_url
+ self.db_host = db_host
+ self.db_user = db_user
+ # self.db_pass = db_pass
+ self.remote_ipfs_node = remote_ipfs_node
+ self.key = key
+
+ self.bot = DiscordBot()
+ self.cleos = CLEOS(None, None, url=node_url, remote=node_url)
+ self.hyperion = HyperionAPI(hyperion_url)
+
+ self._exit_stack = ExitStack()
+ self._async_exit_stack = AsyncExitStack()
+
+ async def start(self):
+ self.ipfs_node = self._exit_stack.enter_context(
+ open_ipfs_node())
+
+ self.ipfs_node.connect(self.remote_ipfs_node)
+ logging.info(
+ f'connected to remote ipfs node: {self.remote_ipfs_node}')
+
+ # self.db_call = await self._async_exit_stack.enter_async_context(
+ # open_database_connection(
+ # self.db_user, self.db_pass, self.db_host))
+
+ create_handler_context(self)
+
+ async def stop(self):
+ await self._async_exit_stack.aclose()
+ self._exit_stack.close()
+
+ @acm
+ async def open(self):
+ await self.start()
+ yield self
+ await self.stop()
+
+ # async def update_status_message(
+ # self, status_msg, new_text: str, **kwargs
+ # ):
+ # await self.db_call(
+ # 'update_user_request_by_sid', status_msg.id, new_text)
+ # return await self.bot.edit_message_text(
+ # new_text,
+ # chat_id=status_msg.chat.id,
+ # message_id=status_msg.id,
+ # **kwargs
+ # )
+
+ # async def append_status_message(
+ # self, status_msg, add_text: str, **kwargs
+ # ):
+ # request = await self.db_call('get_user_request_by_sid', status_msg.id)
+ # await self.update_status_message(
+ # status_msg,
+ # request['status'] + add_text,
+ # **kwargs
+ # )
+
+ async def work_request(
+ self,
+ user,
+ status_msg,
+ method: str,
+ params: dict,
+ file_id: str | None = None,
+ binary_data: str = ''
+ ):
+ if params['seed'] == None:
+ params['seed'] = random.randint(0, 0xFFFFFFFF)
+
+ sanitized_params = {}
+ for key, val in params.items():
+ if isinstance(val, Decimal):
+ val = str(val)
+
+ sanitized_params[key] = val
+
+ body = json.dumps({
+ 'method': 'diffuse',
+ 'params': sanitized_params
+ })
+ request_time = datetime.now().isoformat()
+
+ # await self.update_status_message(
+ # status_msg,
+ # f'processing a \'{method}\' request by {tg_user_pretty(user)}\n'
+ # f'[{timestamp_pretty()}] broadcasting transaction to chain...',
+ # parse_mode='HTML'
+ # )
+
+ reward = '20.0000 GPU'
+ res = await self.cleos.a_push_action(
+ 'telos.gpu',
+ 'enqueue',
+ {
+ 'user': Name(self.account),
+ 'request_body': body,
+ 'binary_data': binary_data,
+ 'reward': asset_from_str(reward),
+ 'min_verification': 1
+ },
+ self.account, self.key, permission=self.permission
+ )
+
+ if 'code' in res or 'statusCode' in res:
+ logging.error(json.dumps(res, indent=4))
+ # await self.update_status_message(
+ # status_msg,
+ # 'skynet has suffered an internal error trying to fill this request')
+ # return
+
+ enqueue_tx_id = res['transaction_id']
+ enqueue_tx_link = hlink(
+ 'Your request on Skynet Explorer',
+ f'https://explorer.{DEFAULT_DOMAIN}/v2/explore/transaction/{enqueue_tx_id}'
+ )
+
+ # await self.append_status_message(
+ # status_msg,
+ # f' broadcasted!\n'
+ # f'{enqueue_tx_link}\n'
+ # f'[{timestamp_pretty()}] workers are processing request...',
+ # parse_mode='HTML'
+ # )
+
+ out = collect_stdout(res)
+
+ request_id, nonce = out.split(':')
+
+ request_hash = sha256(
+ (nonce + body + binary_data).encode('utf-8')).hexdigest().upper()
+
+ request_id = int(request_id)
+
+ logging.info(f'{request_id} enqueued.')
+
+ tx_hash = None
+ ipfs_hash = None
+ for i in range(60):
+ try:
+ submits = await self.hyperion.aget_actions(
+ account=self.account,
+ filter='telos.gpu:submit',
+ sort='desc',
+ after=request_time
+ )
+ actions = [
+ action
+ for action in submits['actions']
+ if action[
+ 'act']['data']['request_hash'] == request_hash
+ ]
+ if len(actions) > 0:
+ tx_hash = actions[0]['trx_id']
+ data = actions[0]['act']['data']
+ ipfs_hash = data['ipfs_hash']
+ worker = data['worker']
+ logging.info('Found matching submit!')
+ break
+
+ except JSONDecodeError:
+ logging.error(f'network error while getting actions, retry..')
+
+ await asyncio.sleep(1)
+
+ if not ipfs_hash:
+ # await self.update_status_message(
+ # status_msg,
+ # f'\n[{timestamp_pretty()}] timeout processing request',
+ # parse_mode='HTML'
+ # )
+ return
+
+ tx_link = hlink(
+ 'Your result on Skynet Explorer',
+ f'https://explorer.{DEFAULT_DOMAIN}/v2/explore/transaction/{tx_hash}'
+ )
+
+ # await self.append_status_message(
+ # status_msg,
+ # f' request processed!\n'
+ # f'{tx_link}\n'
+ # f'[{timestamp_pretty()}] trying to download image...\n',
+ # parse_mode='HTML'
+ # )
+
+ # attempt to get the image and send it
+ ipfs_link = f'https://ipfs.{DEFAULT_DOMAIN}/ipfs/{ipfs_hash}/image.png'
+ resp = await get_ipfs_file(ipfs_link)
+
+ caption = generate_reply_caption(
+ user, params, tx_hash, worker, reward)
+
+ if not resp or resp.status_code != 200:
+ logging.error(f'couldn\'t get ipfs hosted image at {ipfs_link}!')
+ # await self.update_status_message(
+ # status_msg,
+ # caption,
+ # reply_markup=build_redo_menu(),
+ # parse_mode='HTML'
+ # )
+ #
+ else:
+ logging.info(f'success! sending generated image')
+ # await self.bot.delete_message(
+ # chat_id=status_msg.chat.id, message_id=status_msg.id)
+ # if file_id: # img2img
+ # await self.bot.send_media_group(
+ # status_msg.chat.id,
+ # media=[
+ # InputMediaPhoto(file_id),
+ # InputMediaPhoto(
+ # resp.raw,
+ # caption=caption,
+ # parse_mode='HTML'
+ # )
+ # ],
+ # )
+ #
+ # else: # txt2img
+ # await self.bot.send_photo(
+ # status_msg.chat.id,
+ # caption=caption,
+ # photo=resp.raw,
+ # reply_markup=build_redo_menu(),
+ # parse_mode='HTML'
+ # )
diff --git a/skynet/frontend/discord/bot.py b/skynet/frontend/discord/bot.py
new file mode 100644
index 0000000..340b89d
--- /dev/null
+++ b/skynet/frontend/discord/bot.py
@@ -0,0 +1,73 @@
+# import os
+import discord
+# import asyncio
+# from dotenv import load_dotenv
+# from pathlib import Path
+from discord.ext import commands
+
+
+# # Auth
+# current_dir = Path(__file__).resolve().parent
+# # parent_dir = current_dir.parent
+# env_file_path = current_dir / ".env"
+# load_dotenv(dotenv_path=env_file_path)
+#
+# discordToken = os.getenv("DISCORD_TOKEN")
+
+
+# Actual Discord bot.
+class DiscordBot(commands.Bot):
+
+ def __init__(self, *args, **kwargs):
+ intents = discord.Intents(
+ messages=True,
+ guilds=True,
+ typing=True,
+ members=True,
+ presences=True,
+ reactions=True,
+ message_content=True,
+ voice_states=True
+ )
+ super().__init__(command_prefix='\\', intents=intents, *args, **kwargs)
+
+ # async def setup_hook(self):
+ # db.poll_db.start()
+
+ async def on_ready(self):
+ print(f'{self.user.name} has connected to Discord!')
+ for guild in self.guilds:
+ for channel in guild.channels:
+ if channel.name == "skynet":
+ await channel.send('Skynet bot online')
+
+ print("\n==============")
+ print("Logged in as")
+ print(self.user.name)
+ print(self.user.id)
+ print("==============")
+
+ async def on_command_error(self, ctx, error):
+ if isinstance(error, commands.MissingRequiredArgument):
+ await ctx.send('You missed a required argument, please try again.')
+
+ # async def on_message(self, message):
+ # print(f"message from {message.author} what he said {message.content}")
+ # await message.channel.send(message.content)
+
+# bot=DiscordBot()
+# @bot.command(name='config', help='Responds with the configuration')
+# async def config(ctx):
+# response = "This is the bot configuration" # Put your bot configuration here
+# await ctx.send(response)
+#
+# @bot.command(name='helper', help='Responds with a help')
+# async def helper(ctx):
+# response = "This is help information" # Put your help response here
+# await ctx.send(response)
+#
+# @bot.command(name='txt2img', help='Responds with an image')
+# async def txt2img(ctx, *, arg):
+# response = f"This is your prompt: {arg}"
+# await ctx.send(response)
+# bot.run(discordToken)
diff --git a/skynet/frontend/discord/handlers.py b/skynet/frontend/discord/handlers.py
new file mode 100644
index 0000000..8ac46a5
--- /dev/null
+++ b/skynet/frontend/discord/handlers.py
@@ -0,0 +1,372 @@
+#!/usr/bin/python
+
+import io
+import json
+import logging
+
+from datetime import datetime, timedelta
+
+from PIL import Image
+# from telebot.types import CallbackQuery, Message
+
+from skynet.frontend import validate_user_config_request
+from skynet.constants import *
+
+
+def create_handler_context(frontend: 'SkynetDiscordFrontend'):
+
+ bot = frontend.bot
+ cleos = frontend.cleos
+ # db_call = frontend.db_call
+ work_request = frontend.work_request
+
+ ipfs_node = frontend.ipfs_node
+
+
+ @bot.command(name='config', help='Responds with the configuration')
+ async def config(ctx):
+ response = "This is the bot configuration" # Put your bot configuration here
+ await ctx.send(response)
+
+ @bot.command(name='helper', help='Responds with a help')
+ async def helper(ctx):
+ response = "This is help information" # Put your help response here
+ await ctx.send(response)
+
+ @bot.command(name='txt2img', help='Responds with an image')
+ async def send_txt2img(ctx, *, arg):
+ user = 'tests'
+ status_msg = 'status'
+ params = {
+ 'prompt': prompt,
+ }
+ ec = await work_request(user, status_msg, 'txt2img', params)
+ print(ec)
+
+ # if ec == 0:
+ # await db_call('increment_generated', user.id)
+
+ response = f"This is your prompt: {arg}"
+ await ctx.send(response)
+
+ # generic / simple handlers
+
+ # @bot.message_handler(commands=['help'])
+ # async def send_help(message):
+ # splt_msg = message.text.split(' ')
+ #
+ # if len(splt_msg) == 1:
+ # await bot.reply_to(message, HELP_TEXT)
+ #
+ # else:
+ # param = splt_msg[1]
+ # if param in HELP_TOPICS:
+ # await bot.reply_to(message, HELP_TOPICS[param])
+ #
+ # else:
+ # await bot.reply_to(message, HELP_UNKWNOWN_PARAM)
+ #
+ # @bot.message_handler(commands=['cool'])
+ # async def send_cool_words(message):
+ # await bot.reply_to(message, '\n'.join(COOL_WORDS))
+ #
+ # @bot.message_handler(commands=['queue'])
+ # async def queue(message):
+ # an_hour_ago = datetime.now() - timedelta(hours=1)
+ # queue = await cleos.aget_table(
+ # 'telos.gpu', 'telos.gpu', 'queue',
+ # index_position=2,
+ # key_type='i64',
+ # sort='desc',
+ # lower_bound=int(an_hour_ago.timestamp())
+ # )
+ # await bot.reply_to(
+ # message, f'Total requests on skynet queue: {len(queue)}')
+
+
+ # @bot.message_handler(commands=['config'])
+ # async def set_config(message):
+ # user = message.from_user.id
+ # try:
+ # attr, val, reply_txt = validate_user_config_request(
+ # message.text)
+ #
+ # logging.info(f'user config update: {attr} to {val}')
+ # await db_call('update_user_config', user, attr, val)
+ # logging.info('done')
+ #
+ # except BaseException as e:
+ # reply_txt = str(e)
+ #
+ # finally:
+ # await bot.reply_to(message, reply_txt)
+ #
+ # @bot.message_handler(commands=['stats'])
+ # async def user_stats(message):
+ # user = message.from_user.id
+ #
+ # await db_call('get_or_create_user', user)
+ # generated, joined, role = await db_call('get_user_stats', user)
+ #
+ # stats_str = f'generated: {generated}\n'
+ # stats_str += f'joined: {joined}\n'
+ # stats_str += f'role: {role}\n'
+ #
+ # await bot.reply_to(
+ # message, stats_str)
+ #
+ # @bot.message_handler(commands=['donate'])
+ # async def donation_info(message):
+ # await bot.reply_to(
+ # message, DONATION_INFO)
+ #
+ # @bot.message_handler(commands=['say'])
+ # async def say(message):
+ # chat = message.chat
+ # user = message.from_user
+ #
+ # if (chat.type == 'group') or (user.id != 383385940):
+ # return
+ #
+ # await bot.send_message(GROUP_ID, message.text[4:])
+
+
+ # generic txt2img handler
+
+ # async def _generic_txt2img(message_or_query):
+ # if isinstance(message_or_query, CallbackQuery):
+ # query = message_or_query
+ # message = query.message
+ # user = query.from_user
+ # chat = query.message.chat
+ #
+ # else:
+ # message = message_or_query
+ # user = message.from_user
+ # chat = message.chat
+ #
+ # reply_id = None
+ # if chat.type == 'group' and chat.id == GROUP_ID:
+ # reply_id = message.message_id
+ #
+ # user_row = await db_call('get_or_create_user', user.id)
+ #
+ # # init new msg
+ # init_msg = 'started processing txt2img request...'
+ # status_msg = await bot.reply_to(message, init_msg)
+ # await db_call(
+ # 'new_user_request', user.id, message.id, status_msg.id, status=init_msg)
+ #
+ # prompt = ' '.join(message.text.split(' ')[1:])
+ #
+ # if len(prompt) == 0:
+ # await bot.edit_message_text(
+ # 'Empty text prompt ignored.',
+ # chat_id=status_msg.chat.id,
+ # message_id=status_msg.id
+ # )
+ # await db_call('update_user_request', status_msg.id, 'Empty text prompt ignored.')
+ # return
+ #
+ # logging.info(f'mid: {message.id}')
+ #
+ # user_config = {**user_row}
+ # del user_config['id']
+ #
+ # params = {
+ # 'prompt': prompt,
+ # **user_config
+ # }
+ #
+ # await db_call(
+ # 'update_user_stats', user.id, 'txt2img', last_prompt=prompt)
+ #
+ # ec = await work_request(user, status_msg, 'txt2img', params)
+
+ # if ec == 0:
+ # await db_call('increment_generated', user.id)
+ #
+ #
+ # # generic img2img handler
+ #
+ # async def _generic_img2img(message_or_query):
+ # if isinstance(message_or_query, CallbackQuery):
+ # query = message_or_query
+ # message = query.message
+ # user = query.from_user
+ # chat = query.message.chat
+ #
+ # else:
+ # message = message_or_query
+ # user = message.from_user
+ # chat = message.chat
+ #
+ # reply_id = None
+ # if chat.type == 'group' and chat.id == GROUP_ID:
+ # reply_id = message.message_id
+ #
+ # user_row = await db_call('get_or_create_user', user.id)
+ #
+ # # init new msg
+ # init_msg = 'started processing txt2img request...'
+ # status_msg = await bot.reply_to(message, init_msg)
+ # await db_call(
+ # 'new_user_request', user.id, message.id, status_msg.id, status=init_msg)
+ #
+ # if not message.caption.startswith('/img2img'):
+ # await bot.reply_to(
+ # message,
+ # 'For image to image you need to add /img2img to the beggining of your caption'
+ # )
+ # return
+ #
+ # prompt = ' '.join(message.caption.split(' ')[1:])
+ #
+ # if len(prompt) == 0:
+ # await bot.reply_to(message, 'Empty text prompt ignored.')
+ # return
+ #
+ # file_id = message.photo[-1].file_id
+ # file_path = (await bot.get_file(file_id)).file_path
+ # image_raw = await bot.download_file(file_path)
+
+ # with Image.open(io.BytesIO(image_raw)) as image:
+ # w, h = image.size
+ #
+ # if w > 512 or h > 512:
+ # logging.warning(f'user sent img of size {image.size}')
+ # image.thumbnail((512, 512))
+ # logging.warning(f'resized it to {image.size}')
+ #
+ # image.save(f'ipfs-docker-staging/image.png', format='PNG')
+ #
+ # ipfs_hash = ipfs_node.add('image.png')
+ # ipfs_node.pin(ipfs_hash)
+ #
+ # logging.info(f'published input image {ipfs_hash} on ipfs')
+ #
+ # logging.info(f'mid: {message.id}')
+ #
+ # user_config = {**user_row}
+ # del user_config['id']
+ #
+ # params = {
+ # 'prompt': prompt,
+ # **user_config
+ # }
+ #
+ # await db_call(
+ # 'update_user_stats',
+ # user.id,
+ # 'img2img',
+ # last_file=file_id,
+ # last_prompt=prompt,
+ # last_binary=ipfs_hash
+ # )
+ #
+ # ec = await work_request(
+ # user, status_msg, 'img2img', params,
+ # file_id=file_id,
+ # binary_data=ipfs_hash
+ # )
+ #
+ # if ec == 0:
+ # await db_call('increment_generated', user.id)
+ #
+
+ # generic redo handler
+
+ # async def _redo(message_or_query):
+ # is_query = False
+ # if isinstance(message_or_query, CallbackQuery):
+ # is_query = True
+ # query = message_or_query
+ # message = query.message
+ # user = query.from_user
+ # chat = query.message.chat
+ #
+ # elif isinstance(message_or_query, Message):
+ # message = message_or_query
+ # user = message.from_user
+ # chat = message.chat
+ #
+ # init_msg = 'started processing redo request...'
+ # if is_query:
+ # status_msg = await bot.send_message(chat.id, init_msg)
+ #
+ # else:
+ # status_msg = await bot.reply_to(message, init_msg)
+ #
+ # method = await db_call('get_last_method_of', user.id)
+ # prompt = await db_call('get_last_prompt_of', user.id)
+ #
+ # file_id = None
+ # binary = ''
+ # if method == 'img2img':
+ # file_id = await db_call('get_last_file_of', user.id)
+ # binary = await db_call('get_last_binary_of', user.id)
+ #
+ # if not prompt:
+ # await bot.reply_to(
+ # message,
+ # 'no last prompt found, do a txt2img cmd first!'
+ # )
+ # return
+ #
+ #
+ # user_row = await db_call('get_or_create_user', user.id)
+ # await db_call(
+ # 'new_user_request', user.id, message.id, status_msg.id, status=init_msg)
+ # user_config = {**user_row}
+ # del user_config['id']
+ #
+ # params = {
+ # 'prompt': prompt,
+ # **user_config
+ # }
+ #
+ # await work_request(
+ # user, status_msg, 'redo', params,
+ # file_id=file_id,
+ # binary_data=binary
+ # )
+
+
+ # "proxy" handlers just request routers
+
+ # @bot.message_handler(commands=['txt2img'])
+ # async def send_txt2img(message):
+ # await _generic_txt2img(message)
+ #
+ # @bot.message_handler(func=lambda message: True, content_types=[
+ # 'photo', 'document'])
+ # async def send_img2img(message):
+ # await _generic_img2img(message)
+ #
+ # @bot.message_handler(commands=['img2img'])
+ # async def img2img_missing_image(message):
+ # await bot.reply_to(
+ # message,
+ # 'seems you tried to do an img2img command without sending image'
+ # )
+ #
+ # @bot.message_handler(commands=['redo'])
+ # async def redo(message):
+ # await _redo(message)
+ #
+ # @bot.callback_query_handler(func=lambda call: True)
+ # async def callback_query(call):
+ # msg = json.loads(call.data)
+ # logging.info(call.data)
+ # method = msg.get('method')
+ # match method:
+ # case 'redo':
+ # await _redo(call)
+
+
+ # catch all handler for things we dont support
+
+ # @bot.message_handler(func=lambda message: True)
+ # async def echo_message(message):
+ # if message.text[0] == '/':
+ # await bot.reply_to(message, UNKNOWN_CMD_TEXT)
diff --git a/skynet/frontend/discord/utils.py b/skynet/frontend/discord/utils.py
new file mode 100644
index 0000000..ad08bba
--- /dev/null
+++ b/skynet/frontend/discord/utils.py
@@ -0,0 +1,106 @@
+#!/usr/bin/python
+
+import json
+import logging
+import traceback
+
+from datetime import datetime, timezone
+
+from telebot.types import InlineKeyboardButton, InlineKeyboardMarkup
+from telebot.async_telebot import ExceptionHandler
+from telebot.formatting import hlink
+
+from skynet.constants import *
+
+
+def timestamp_pretty():
+ return datetime.now(timezone.utc).strftime('%H:%M:%S')
+
+
+def tg_user_pretty(tguser):
+ if tguser.username:
+ return f'@{tguser.username}'
+ else:
+ return f'{tguser.first_name} id: {tguser.id}'
+
+
+class SKYExceptionHandler(ExceptionHandler):
+
+ def handle(exception):
+ traceback.print_exc()
+
+
+def build_redo_menu():
+ btn_redo = InlineKeyboardButton("Redo", callback_data=json.dumps({'method': 'redo'}))
+ inline_keyboard = InlineKeyboardMarkup()
+ inline_keyboard.add(btn_redo)
+ return inline_keyboard
+
+
+def prepare_metainfo_caption(tguser, worker: str, reward: str, meta: dict) -> str:
+ prompt = meta["prompt"]
+ if len(prompt) > 256:
+ prompt = prompt[:256]
+
+
+ meta_str = f'by {tg_user_pretty(tguser)}\n'
+ meta_str += f'performed by {worker}\n'
+ meta_str += f'reward: {reward}\n'
+
+ meta_str += f'prompt:
{prompt}\n'
+ meta_str += f'seed: {meta["seed"]}
\n'
+ meta_str += f'step: {meta["step"]}
\n'
+ meta_str += f'guidance: {meta["guidance"]}
\n'
+ if meta['strength']:
+ meta_str += f'strength: {meta["strength"]}
\n'
+ meta_str += f'algo: {meta["model"]}
\n'
+ if meta['upscaler']:
+ meta_str += f'upscaler: {meta["upscaler"]}
\n'
+
+ meta_str += f'Made with Skynet v{VERSION}\n'
+ meta_str += f'JOIN THE SWARM: @skynetgpu'
+ return meta_str
+
+
+def generate_reply_caption(
+ tguser, # telegram user
+ params: dict,
+ tx_hash: str,
+ worker: str,
+ reward: str
+):
+ explorer_link = hlink(
+ 'SKYNET Transaction Explorer',
+ f'https://explorer.{DEFAULT_DOMAIN}/v2/explore/transaction/{tx_hash}'
+ )
+
+ meta_info = prepare_metainfo_caption(tguser, worker, reward, params)
+
+ final_msg = '\n'.join([
+ 'Worker finished your task!',
+ explorer_link,
+ f'PARAMETER INFO:\n{meta_info}'
+ ])
+
+ final_msg = '\n'.join([
+ f'{explorer_link}',
+ f'{meta_info}'
+ ])
+
+ logging.info(final_msg)
+
+ return final_msg
+
+
+async def get_global_config(cleos):
+ return (await cleos.aget_table(
+ 'telos.gpu', 'telos.gpu', 'config'))[0]
+
+async def get_user_nonce(cleos, user: str):
+ return (await cleos.aget_table(
+ 'telos.gpu', 'telos.gpu', 'users',
+ index_position=1,
+ key_type='name',
+ lower_bound=user,
+ upper_bound=user
+ ))[0]['nonce']