101 lines
2.8 KiB
Nim
101 lines
2.8 KiB
Nim
# toxio: turn any CLI program/script into a Tox bot
|
|
# deps: toxcore, nim-toxcore
|
|
# created by Luxferre in 2024, released into public domain
|
|
|
|
import toxcore, times, sets, osproc, streams
|
|
from threadpool import spawn
|
|
from std/os import sleep
|
|
from std/cmdline import paramCount, paramStr
|
|
|
|
# command line parameters
|
|
if paramCount() < 4:
|
|
echo "Usage: toxio name datafile [w|r] cmd"
|
|
quit(1)
|
|
let
|
|
botname = paramStr(1)
|
|
toxdatafile = paramStr(2)
|
|
writeperm: bool = (paramStr(3) == "w")
|
|
subcmd = paramStr(4)
|
|
|
|
# read Tox save data
|
|
var toxconfig = ""
|
|
try:
|
|
toxconfig = readFile(toxdatafile)
|
|
except IOError:
|
|
stderr.writeLine "Cannot read the Tox data file!"
|
|
quit(1)
|
|
|
|
# bootstrap constants
|
|
const
|
|
bootstrapHost = "46.101.197.175"
|
|
bootstrapKey = "CD133B521159541FB1D326DE9850F5E56A6C724B5B8E5EB5CD8D950408E95707".toPublicKey
|
|
bootstrapPort = 33445
|
|
|
|
# declare the bot type to wrap GC-safe data
|
|
type Bot = ref object
|
|
tox: Tox
|
|
datafile: string
|
|
activeFriends: HashSet[Friend]
|
|
sproc: Process
|
|
|
|
# input loop procedure
|
|
proc inputloop(bot: Bot) =
|
|
var gline = ""
|
|
while bot.sproc.outputStream.readLine(gline):
|
|
for f in bot.activeFriends.items:
|
|
bot.tox.send(f, gline, TOX_MESSAGE_TYPE_NORMAL)
|
|
bot.sproc.outputStream.flush()
|
|
|
|
# GC-safe bot callbacks
|
|
proc cbs(bot: Bot) =
|
|
# what do do on a new friend request (only in write-enable mode)
|
|
if writeperm:
|
|
bot.tox.onFriendRequest do (pk: PublicKey; msg: string):
|
|
discard bot.tox.addFriendNoRequest(pk)
|
|
try:
|
|
writeFile(bot.datafile, bot.tox.saveData)
|
|
except IOError:
|
|
stderr.writeLine "Cannot write the Tox data file!"
|
|
|
|
# what to do on a new message
|
|
bot.tox.onFriendMessage do (f: Friend; msg: string; kind: MessageType):
|
|
if msg == "/toxiostop": # temporarily unsubscribe from the output
|
|
bot.activeFriends.excl(f)
|
|
else:
|
|
bot.activeFriends.incl(f) # first-time activation
|
|
bot.sproc.inputStream.writeLine msg
|
|
bot.sproc.inputStream.flush()
|
|
|
|
# bot options description
|
|
proc botopt(opts: Options) =
|
|
opts.savedata_type = TOX_SAVEDATA_TYPE_TOX_SAVE
|
|
opts.saveData = toxconfig
|
|
|
|
# spawn the subprocess
|
|
var subproc:Process
|
|
|
|
try:
|
|
subproc = startProcess(subcmd, options={poEvalCommand,poStdErrToStdOut,poInteractive,poUsePath})
|
|
stderr.writeLine "Subprocess started"
|
|
except:
|
|
stderr.writeLine "Cannot spawn the subprocess command!"
|
|
quit(1)
|
|
|
|
# init the bot
|
|
let iobot = Bot(tox:initTox(botopt))
|
|
iobot.tox.name = botname
|
|
iobot.datafile = toxdatafile
|
|
iobot.cbs()
|
|
iobot.tox.bootstrap(bootstrapHost, bootstrapKey, bootstrapPort)
|
|
iobot.tox.statusMessage = "Serving " & subcmd
|
|
iobot.sproc = subproc
|
|
stderr.writeLine iobot.tox.name, " online at ", iobot.tox.address
|
|
|
|
# run the input loop
|
|
spawn inputloop(iobot)
|
|
|
|
# run the toxcore loop
|
|
while true:
|
|
iterate(iobot.tox)
|
|
sleep inMilliseconds(iobot.tox.iterationInterval)
|